diff --git a/src/packages/emmett/src/database/inMemoryDatabase.ts b/src/packages/emmett/src/database/inMemoryDatabase.ts new file mode 100644 index 00000000..c9510ada --- /dev/null +++ b/src/packages/emmett/src/database/inMemoryDatabase.ts @@ -0,0 +1,49 @@ +import { JSONParser } from '../serialization'; + +export interface DocumentsCollection { + store: (id: string, obj: T) => void; + delete: (id: string) => void; + get: (id: string) => T | null; +} + +export type DocumentHandler = + | ((document: T | null) => T | null) + | ((document: T | null) => Promise); + +export interface Database { + collection: (name: string) => DocumentsCollection; +} + +export const getInMemoryDatabase = (): Database => { + const storage = new Map(); + + return { + collection: ( + collectionName: string, + _collectionOptions: { + errors?: { throwOnOperationFailures?: boolean } | undefined; + } = {}, + ): DocumentsCollection => { + const toFullId = (id: string) => `${collectionName}-${id}`; + + const collection = { + store: (id: string, obj: T): void => { + storage.set(toFullId(id), obj); + }, + delete: (id: string): void => { + storage.delete(toFullId(id)); + }, + get: (id: string): T | null => { + const result = storage.get(toFullId(id)); + + return result + ? // Clone to simulate getting new instance on loading + (JSONParser.parse(JSONParser.stringify(result)) as T) + : null; + }, + }; + + return collection; + }, + }; +}; diff --git a/src/packages/emmett/src/eventStore/inMemoryEventStore.ts b/src/packages/emmett/src/eventStore/inMemoryEventStore.ts index 2cd93e97..e1270096 100644 --- a/src/packages/emmett/src/eventStore/inMemoryEventStore.ts +++ b/src/packages/emmett/src/eventStore/inMemoryEventStore.ts @@ -19,14 +19,27 @@ import { } from './eventStore'; import { assertExpectedVersionMatchesCurrent } from './expectedVersion'; import { StreamingCoordinator } from './subscriptions'; +import type { ProjectionRegistration } from '../projections'; export const InMemoryEventStoreDefaultStreamVersion = 0n; export type InMemoryEventStore = EventStore; +export type InMemoryReadEventMetadata = ReadEventMetadataWithGlobalPosition; + +export type InMemoryProjectionHandlerContext = { + eventStore: InMemoryEventStore; +}; + export type InMemoryEventStoreOptions = - DefaultEventStoreOptions; + DefaultEventStoreOptions & { + projections?: ProjectionRegistration< + 'inline', + InMemoryReadEventMetadata, + InMemoryProjectionHandlerContext + >[]; + }; export type InMemoryReadEvent = ReadEvent< EventType, @@ -48,6 +61,10 @@ export const getInMemoryEventStore = ( .reduce((p, c) => p + c, 0); }; + const _inlineProjections = (eventStoreOptions?.projections ?? []) + .filter(({ type }) => type === 'inline') + .map(({ projection }) => projection); + return { async aggregateStream( streamName: string,