Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update import paths and add pagination to InMemoryRepository #70

Merged
merged 7 commits into from
Jan 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
FROM node:18.12

ARG USER_NAME=user
ARG USER_UID=1000
ARG USER_GID=$USER_UID
ARG GITHUB_TOKEN

# Install necessary apps
RUN apt-get update \
&& apt-get install -y git fish sudo

# Create the user
RUN groupadd --gid ${USER_GID} ${USER_NAME} \
&& useradd --uid ${USER_UID} --gid ${USER_GID} -m ${USER_NAME} \
&& echo $USER_NAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/${USER_NAME} \
&& chmod 0440 /etc/sudoers.d/${USER_NAME}
USER ${USER_NAME}

# Copy files
WORKDIR /workspaces
COPY --chown=${USER_UID}:${USER_GID} . .
RUN npm install

# Infinite development loop
CMD ["/bin/bash", "-c", "while true; do sleep 1000; done"]
36 changes: 23 additions & 13 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
{
"extensions": [
"GitHub.vscode-pull-request-github",
"stackbreak.comment-divider",
"dbaeumer.vscode-eslint",
"GitHub.copilot",
"Orta.vscode-jest",
"yzhang.markdown-all-in-one",
"bierner.markdown-mermaid",
"eg2.vscode-npm-script",
"howardzuo.vscode-npm-dependency",
"craig-simko.open-stryker-report",
"jasonnutter.search-node-modules"
]
"name": "Framework",
"dockerComposeFile": [
"docker-compose.yml"
],
"service": "framework",
"workspaceFolder": "/workspaces",
"customizations": {
"vscode": {
"extensions": [
"GitHub.vscode-pull-request-github",
"stackbreak.comment-divider",
"dbaeumer.vscode-eslint",
"GitHub.copilot",
"Orta.vscode-jest",
"yzhang.markdown-all-in-one",
"bierner.markdown-mermaid",
"eg2.vscode-npm-script",
"howardzuo.vscode-npm-dependency",
"craig-simko.open-stryker-report",
"jasonnutter.search-node-modules"
]
}
}
}
14 changes: 14 additions & 0 deletions .devcontainer/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: framework

services:
framework:
build:
context: ..
dockerfile: .devcontainer/Dockerfile
args:
- USER_NAME=${DEVCONTAINER_USER_NAME}
- USER_UID=${DEVCONTAINER_USER_ID:-1000}
- USER_GID=${DEVCONTAINER_USER_GID:-1000}
- GITHUB_TOKEN=${GITHUB_PAT_TOKEN}
volumes:
- ..:/workspaces
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
/* linter */
/* -------------------------------------------------------------------------- */
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
"source.fixAll.eslint": "explicit"
},
"eslint.validate": [
"javascript",
Expand Down
2 changes: 1 addition & 1 deletion lib/domain/models/Builder.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Result } from '@lib/core'
import { Result } from '../../core'

/**
* Builds a new instance of the specified type or returns a failure result.
Expand Down
2 changes: 1 addition & 1 deletion lib/persistence/Query.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Aggregate, AnyIdentity } from '@lib/domain/models'
import { Aggregate, AnyIdentity } from '../domain'

/**
* Comparison operators. These are used to compare a field with a value.
Expand Down
4 changes: 2 additions & 2 deletions lib/persistence/Repository.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Aggregate, AnyIdentity } from '@lib/domain/models'
import { Query } from '@lib/persistence'
import { Aggregate, AnyIdentity } from '../domain/models'
import { Query } from '../persistence'

export interface ResultSetSlice {
start: number
Expand Down
10 changes: 5 additions & 5 deletions lib/persistence/memory/InMemoryQueryProcessor.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Aggregate, AnyIdentity, Identity, Value } from '@lib/domain/models'
import { Aggregate, AnyIdentity, Identity, Value } from '../../domain/models'
import { Binding, Expression, LogicalOperators, Operators, Predicate, Query } from '../Query'

export class InMemoryQueryProcessor<
Expand Down Expand Up @@ -72,14 +72,14 @@ export class InMemoryQueryProcessor<
return a.filter(x => x.includes(b)).length > 0
} else if (typeof a === 'string') {
// Stryker disable next-line all
const normalize = (str) => str.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '')
const normalize = (str: string) => str.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '')
return normalize(a).includes(normalize(b))
}
return false
},
[Operators.In]: (a, b) => {
if (a instanceof Value) { return b.findIndex(x => x.equals(a)) !== -1 }
else if (a instanceof Identity) { return b.findIndex(x => x.equals(a)) !== -1 }
if (a instanceof Value) { return b.findIndex((x: Value<unknown>) => x.equals(a)) !== -1 }
else if (a instanceof Identity) { return b.findIndex((x: Identity<unknown, unknown>) => x.equals(a)) !== -1 }
else return b.includes(a)
},
}
Expand Down Expand Up @@ -112,7 +112,7 @@ export class InMemoryQueryProcessor<
}

private getFieldValue(f: Binding<TEntity>, o: TEntity) {
// get netsed field value by string separated by dots
// get nested field value by string separated by dots
const fields = f.split('.')
let value = o
for (const field of fields) {
Expand Down
29 changes: 23 additions & 6 deletions lib/persistence/memory/InMemoryRepository.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Aggregate, AnyIdentity } from '@lib/domain/models'
import { Aggregate, AnyIdentity } from '../../domain/models'
import { Query } from '../Query'
import { QueryOptions, Repository, ResultSet } from '../Repository'
import { InMemoryQueryProcessor } from './InMemoryQueryProcessor'
Expand All @@ -11,11 +11,15 @@ export class InMemoryRepository<
protected processor = new InMemoryQueryProcessor<TAggregate>()

async all(
// options?: QueryOptions,
options?: QueryOptions,
): Promise<ResultSet<TAggregate>> {
let result = Array.from(this.entities.values())

result = this.slice(result, options?.skip, options?.limit)

return new ResultSet(
Array.from(this.entities.values()),
{ start: 0, count: this.entities.size }
result,
{ start: options?.skip || 0, count: result.length }
)
}

Expand All @@ -40,8 +44,11 @@ export class InMemoryRepository<
options?: QueryOptions,
): Promise<ResultSet<TAggregate>> {
const startIndex = options?.skip || 0
const entities = Array.from(this.entities.values()).slice(startIndex)
const result = this.processor.execute(query, entities)
let result = this.processor
.execute(query, Array.from(this.entities.values()))

result = this.slice(result, options?.skip, options?.limit)

return new ResultSet<TAggregate>(
result, { start: startIndex, count: result.length }
)
Expand All @@ -52,4 +59,14 @@ export class InMemoryRepository<
if (!isExists) { throw new Error(`Entity '${id.value}' not found`) }
this.entities.delete(id.value)
}

private slice(
list: readonly TAggregate[],
skip: number | undefined,
limit: number | undefined
) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return list.slice(skip, (skip + limit) || limit)
}
}
3 changes: 2 additions & 1 deletion tests/domain/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,5 @@ export class Order
const qb = new QueryBuilder<Order>()
export const clientName = (name: string) => qb.eq('clientName', name)
export const address = (street: string, city: string, zip: string,) => qb.eq('deliveryAddress', new Address(street, city, zip))
export const price = (price: number) => qb.eq('price', price)
export const price = (price: number) => qb.eq('price', price)
export const expensiveThan = (price: number) => qb.gt('price', price)
22 changes: 21 additions & 1 deletion tests/persistence/memory/InMemoryRepository.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,26 @@ describe('InMemoryRepository', () => {
const result = await repository.all()
expect(result.entities).toEqual([order1, order2])
})

it('skip', async () => {
const result = await repository.all({ skip: 1})
expect(result.entities).toEqual([order2])
})

it('limit', async () => {
const result = await repository.all({ limit: 1})
expect(result.entities).toEqual([order1])
})

it('skip and limit', async () => {
const result = await repository.all({ skip: 1, limit: 1})
expect(result.entities).toEqual([order2])
})

it('skip to much', async () => {
const result = await repository.all({ skip: 2})
expect(result.entities).toEqual([])
})
})

/* -------------------------------------------------------------------------- */
Expand Down Expand Up @@ -121,7 +141,7 @@ describe('InMemoryRepository', () => {
* Second page should return empty array because there is only one entity with
* client name 'John' and we already fetched it in the "arrange" section.
*/
it('respects bookmark', async () => {
it('skip', async () => {
// arrange:
const query = clientName('John')
let result = await repository.find(query)
Expand Down
Loading