@event-driven-io/emmett-testcontainers 0.42.0-rc.2 → 0.43.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs.map +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/emmett/emmett/src/packages/emmett-testcontainers/dist/index.cjs","../src/eventStore/index.ts","../../emmett/src/validation/index.ts","../../emmett/src/errors/index.ts","../../emmett/src/eventStore/inMemoryEventStore.ts","../../emmett/src/database/inMemoryDatabase.ts","../../emmett/src/utils/retry.ts","../../emmett/src/eventStore/projections/inMemory/inMemoryProjectionSpec.ts","../../emmett/src/processors/processors.ts","../../emmett/src/typing/index.ts","../../emmett/src/taskProcessing/taskProcessor.ts","../../emmett/src/utils/locking/index.ts","../../emmett/src/utils/strings/hashText.ts","../src/eventStore/eventStoreDBContainer.ts","../src/mongodb/mongoDBContainer.ts","../src/postgresql/postgreSQLContainer.ts"],"names":["error","item","container","EventStoreDBClient"],"mappings":"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","file":"/home/runner/work/emmett/emmett/src/packages/emmett-testcontainers/dist/index.cjs","sourcesContent":[null,"import { EventStoreDBClient } from '@eventstore/db-client';\nimport {\n EventStoreDBContainer,\n StartedEventStoreDBContainer,\n} from './eventStoreDBContainer';\n\nexport * from './eventStoreDBContainer';\n\nlet esdbContainer: StartedEventStoreDBContainer;\n\nexport const getEventStoreDBTestClient = async (\n useTestContainers = false,\n): Promise<EventStoreDBClient> => {\n let connectionString;\n\n if (useTestContainers) {\n if (!esdbContainer)\n esdbContainer = await new EventStoreDBContainer().start();\n\n connectionString = esdbContainer.getConnectionString();\n } else {\n // await compose.upAll();\n connectionString = 'esdb://localhost:2113?tls=false';\n }\n\n // That's how EventStoreDB client is setup\n // We're taking the connection string from container\n return EventStoreDBClient.connectionString(connectionString);\n};\n","import { ValidationError } from '../errors';\n\nexport const enum ValidationErrors {\n NOT_A_NONEMPTY_STRING = 'NOT_A_NONEMPTY_STRING',\n NOT_A_POSITIVE_NUMBER = 'NOT_A_POSITIVE_NUMBER',\n NOT_AN_UNSIGNED_BIGINT = 'NOT_AN_UNSIGNED_BIGINT',\n}\n\nexport const isNumber = (val: unknown): val is number =>\n typeof val === 'number' && val === val;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const isBigint = (val: any): val is bigint =>\n typeof val === 'bigint' && val === val;\n\nexport const isString = (val: unknown): val is string =>\n typeof val === 'string';\n\nexport const assertNotEmptyString = (value: unknown): string => {\n if (!isString(value) || value.length === 0) {\n throw new ValidationError(ValidationErrors.NOT_A_NONEMPTY_STRING);\n }\n return value;\n};\n\nexport const assertPositiveNumber = (value: unknown): number => {\n if (!isNumber(value) || value <= 0) {\n throw new ValidationError(ValidationErrors.NOT_A_POSITIVE_NUMBER);\n }\n return value;\n};\n\nexport const assertUnsignedBigInt = (value: string): bigint => {\n const number = BigInt(value);\n if (number < 0) {\n throw new ValidationError(ValidationErrors.NOT_AN_UNSIGNED_BIGINT);\n }\n return number;\n};\n\nexport * from './dates';\n","import { isNumber, isString } from '../validation';\n\nexport type ErrorConstructor<ErrorType extends Error> = new (\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ...args: any[]\n) => ErrorType;\n\nexport const isErrorConstructor = <ErrorType extends Error>(\n // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n expect: Function,\n): expect is ErrorConstructor<ErrorType> => {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return (\n typeof expect === 'function' &&\n expect.prototype &&\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n expect.prototype.constructor === expect\n );\n};\n\nexport class EmmettError extends Error {\n public static readonly Codes = {\n ValidationError: 400,\n IllegalStateError: 403,\n NotFoundError: 404,\n ConcurrencyError: 412,\n InternalServerError: 500,\n };\n\n public errorCode: number;\n\n constructor(\n options?: { errorCode: number; message?: string } | string | number,\n ) {\n const errorCode =\n options && typeof options === 'object' && 'errorCode' in options\n ? options.errorCode\n : isNumber(options)\n ? options\n : EmmettError.Codes.InternalServerError;\n const message =\n options && typeof options === 'object' && 'message' in options\n ? options.message\n : isString(options)\n ? options\n : `Error with status code '${errorCode}' ocurred during Emmett processing`;\n\n super(message);\n this.errorCode = errorCode;\n\n // 👇️ because we are extending a built-in class\n Object.setPrototypeOf(this, EmmettError.prototype);\n }\n\n public static mapFrom(\n error: Error | { message?: string; errorCode?: number },\n ): EmmettError {\n if (EmmettError.isInstanceOf(error)) {\n return error;\n }\n\n return new EmmettError({\n errorCode:\n 'errorCode' in error &&\n error.errorCode !== undefined &&\n error.errorCode !== null\n ? error.errorCode\n : EmmettError.Codes.InternalServerError,\n message: error.message ?? 'An unknown error occurred',\n });\n }\n\n public static isInstanceOf<ErrorType extends EmmettError = EmmettError>(\n error: unknown,\n errorCode?: (typeof EmmettError.Codes)[keyof typeof EmmettError.Codes],\n ): error is ErrorType {\n return (\n typeof error === 'object' &&\n error !== null &&\n 'errorCode' in error &&\n isNumber(error.errorCode) &&\n (errorCode === undefined || error.errorCode === errorCode)\n );\n }\n}\n\nexport class ConcurrencyError extends EmmettError {\n constructor(\n public current: string | undefined,\n public expected: string,\n message?: string,\n ) {\n super({\n errorCode: EmmettError.Codes.ConcurrencyError,\n message:\n message ??\n `Expected version ${expected.toString()} does not match current ${current?.toString()}`,\n });\n\n // 👇️ because we are extending a built-in class\n Object.setPrototypeOf(this, ConcurrencyError.prototype);\n }\n}\n\n// TODO: Make it derive from ConcurrencyError to avoid code duplication\n// Or add additional type to distinguinsh both errors\nexport class ConcurrencyInMemoryDatabaseError extends EmmettError {\n constructor(message?: string) {\n super({\n errorCode: EmmettError.Codes.ConcurrencyError,\n message: message ?? `Expected document state does not match current one!`,\n });\n\n // 👇️ because we are extending a built-in class\n Object.setPrototypeOf(this, ConcurrencyInMemoryDatabaseError.prototype);\n }\n}\n\nexport class ValidationError extends EmmettError {\n constructor(message?: string) {\n super({\n errorCode: EmmettError.Codes.ValidationError,\n message: message ?? `Validation Error ocurred during Emmett processing`,\n });\n\n // 👇️ because we are extending a built-in class\n Object.setPrototypeOf(this, ValidationError.prototype);\n }\n}\n\nexport class IllegalStateError extends EmmettError {\n constructor(message?: string) {\n super({\n errorCode: EmmettError.Codes.IllegalStateError,\n message: message ?? `Illegal State ocurred during Emmett processing`,\n });\n\n // 👇️ because we are extending a built-in class\n Object.setPrototypeOf(this, IllegalStateError.prototype);\n }\n}\n\nexport class NotFoundError extends EmmettError {\n constructor(options?: { id: string; type: string; message?: string }) {\n super({\n errorCode: EmmettError.Codes.NotFoundError,\n message:\n options?.message ??\n (options?.id\n ? options.type\n ? `${options.type} with ${options.id} was not found during Emmett processing`\n : `State with ${options.id} was not found during Emmett processing`\n : options?.type\n ? `${options.type} was not found during Emmett processing`\n : 'State was not found during Emmett processing'),\n });\n\n // 👇️ because we are extending a built-in class\n Object.setPrototypeOf(this, NotFoundError.prototype);\n }\n}\n","import { v4 as uuid } from 'uuid';\nimport {\n getInMemoryDatabase,\n type InMemoryDatabase,\n} from '../database/inMemoryDatabase';\nimport type { ProjectionRegistration } from '../projections';\nimport type {\n BigIntStreamPosition,\n CombinedReadEventMetadata,\n Event,\n ReadEvent,\n ReadEventMetadataWithGlobalPosition,\n} from '../typing';\nimport { tryPublishMessagesAfterCommit } from './afterCommit';\nimport {\n type AggregateStreamOptions,\n type AggregateStreamResult,\n type AppendToStreamOptions,\n type AppendToStreamResult,\n type DefaultEventStoreOptions,\n type EventStore,\n type ReadStreamOptions,\n type ReadStreamResult,\n type StreamExistsResult,\n} from './eventStore';\nimport { assertExpectedVersionMatchesCurrent } from './expectedVersion';\nimport { handleInMemoryProjections } from './projections/inMemory';\nimport { downcastRecordedMessages, upcastRecordedMessages } from './versioning';\n\nexport const InMemoryEventStoreDefaultStreamVersion = 0n;\n\nexport type InMemoryEventStore =\n EventStore<ReadEventMetadataWithGlobalPosition> & {\n database: InMemoryDatabase;\n };\n\nexport type InMemoryReadEventMetadata = ReadEventMetadataWithGlobalPosition;\n\nexport type InMemoryProjectionHandlerContext = {\n eventStore?: InMemoryEventStore;\n database?: InMemoryDatabase;\n};\n\nexport type InMemoryEventStoreOptions =\n DefaultEventStoreOptions<InMemoryEventStore> & {\n projections?: ProjectionRegistration<\n 'inline',\n InMemoryReadEventMetadata,\n InMemoryProjectionHandlerContext\n >[];\n database?: InMemoryDatabase;\n };\n\nexport type InMemoryReadEvent<EventType extends Event = Event> = ReadEvent<\n EventType,\n ReadEventMetadataWithGlobalPosition\n>;\n\nexport const getInMemoryEventStore = (\n eventStoreOptions?: InMemoryEventStoreOptions,\n): InMemoryEventStore => {\n const streams = new Map<\n string,\n ReadEvent<Event, ReadEventMetadataWithGlobalPosition>[]\n >();\n\n const getAllEventsCount = () => {\n return Array.from<ReadEvent[]>(streams.values())\n .map((s) => s.length)\n .reduce((p, c) => p + c, 0);\n };\n\n // Get the database instance to be used for projections\n const database = eventStoreOptions?.database || getInMemoryDatabase();\n\n // Extract inline projections from options\n const inlineProjections = (eventStoreOptions?.projections ?? [])\n .filter(({ type }) => type === 'inline')\n .map(({ projection }) => projection);\n\n // Create the event store object\n const eventStore: InMemoryEventStore = {\n database,\n async aggregateStream<\n State,\n EventType extends Event,\n EventPayloadType extends Event = EventType,\n >(\n streamName: string,\n options: AggregateStreamOptions<\n State,\n EventType,\n ReadEventMetadataWithGlobalPosition,\n EventPayloadType\n >,\n ): Promise<AggregateStreamResult<State>> {\n const { evolve, initialState, read } = options;\n\n const result = await this.readStream<EventType, EventPayloadType>(\n streamName,\n read,\n );\n\n const events = result?.events ?? [];\n\n const state = events.reduce((s, e) => evolve(s, e), initialState());\n\n return {\n currentStreamVersion: BigInt(events.length),\n state,\n streamExists: result.streamExists,\n };\n },\n\n readStream: <\n EventType extends Event,\n EventPayloadType extends Event = EventType,\n >(\n streamName: string,\n options?: ReadStreamOptions<\n BigIntStreamPosition,\n EventType,\n EventPayloadType\n >,\n ): Promise<\n ReadStreamResult<EventType, ReadEventMetadataWithGlobalPosition>\n > => {\n const events = streams.get(streamName);\n const currentStreamVersion = events\n ? BigInt(events.length)\n : InMemoryEventStoreDefaultStreamVersion;\n\n assertExpectedVersionMatchesCurrent(\n currentStreamVersion,\n options?.expectedStreamVersion,\n InMemoryEventStoreDefaultStreamVersion,\n );\n\n const from = Number(options?.from ?? 0);\n const to = Number(\n options?.to ??\n (options?.maxCount\n ? (options.from ?? 0n) + options.maxCount\n : (events?.length ?? 1)),\n );\n\n const resultEvents =\n events !== undefined && events.length > 0\n ? upcastRecordedMessages<\n EventType,\n EventPayloadType,\n ReadEventMetadataWithGlobalPosition\n >(\n events.slice(from, to) as ReadEvent<\n EventPayloadType,\n ReadEventMetadataWithGlobalPosition\n >[],\n options?.schema?.versioning,\n )\n : [];\n\n const result: ReadStreamResult<\n EventType,\n ReadEventMetadataWithGlobalPosition\n > = {\n currentStreamVersion,\n events: resultEvents,\n streamExists: events !== undefined && events.length > 0,\n };\n\n return Promise.resolve(result);\n },\n\n appendToStream: async <\n EventType extends Event,\n EventPayloadType extends Event = EventType,\n >(\n streamName: string,\n events: EventType[],\n options?: AppendToStreamOptions<\n BigIntStreamPosition,\n EventType,\n EventPayloadType\n >,\n ): Promise<AppendToStreamResult> => {\n const currentEvents = streams.get(streamName) ?? [];\n const currentStreamVersion =\n currentEvents.length > 0\n ? BigInt(currentEvents.length)\n : InMemoryEventStoreDefaultStreamVersion;\n\n assertExpectedVersionMatchesCurrent(\n currentStreamVersion,\n options?.expectedStreamVersion,\n InMemoryEventStoreDefaultStreamVersion,\n );\n\n const newEvents: ReadEvent<\n EventType,\n ReadEventMetadataWithGlobalPosition\n >[] = events.map((event, index) => {\n const metadata: ReadEventMetadataWithGlobalPosition = {\n streamName,\n messageId: uuid(),\n streamPosition: BigInt(currentEvents.length + index + 1),\n globalPosition: BigInt(getAllEventsCount() + index + 1),\n };\n return {\n ...event,\n kind: event.kind ?? 'Event',\n metadata: {\n ...('metadata' in event ? (event.metadata ?? {}) : {}),\n ...metadata,\n } as CombinedReadEventMetadata<\n EventType,\n ReadEventMetadataWithGlobalPosition\n >,\n };\n });\n\n const positionOfLastEventInTheStream = BigInt(\n newEvents.slice(-1)[0]!.metadata.streamPosition,\n );\n\n streams.set(streamName, [\n ...currentEvents,\n ...downcastRecordedMessages(newEvents, options?.schema?.versioning),\n ]);\n\n // Process projections if there are any registered\n if (inlineProjections.length > 0) {\n await handleInMemoryProjections({\n projections: inlineProjections,\n events: newEvents,\n database: eventStore.database,\n eventStore,\n });\n }\n\n const result: AppendToStreamResult = {\n nextExpectedStreamVersion: positionOfLastEventInTheStream,\n createdNewStream:\n currentStreamVersion === InMemoryEventStoreDefaultStreamVersion,\n };\n\n await tryPublishMessagesAfterCommit<InMemoryEventStore>(\n newEvents,\n eventStoreOptions?.hooks,\n );\n\n return result;\n },\n\n streamExists: (streamName): Promise<StreamExistsResult> => {\n const events = streams.get(streamName);\n\n return Promise.resolve(events !== undefined && events.length > 0);\n },\n };\n\n return eventStore;\n};\n","import { v7 as uuid } from 'uuid';\nimport { deepEquals } from '../utils';\nimport {\n type DatabaseHandleOptionErrors,\n type DatabaseHandleOptions,\n type DatabaseHandleResult,\n type DeleteResult,\n type Document,\n type DocumentHandler,\n type InsertOneResult,\n type OptionalUnlessRequiredIdAndVersion,\n type ReplaceOneOptions,\n type UpdateResult,\n type WithIdAndVersion,\n type WithoutId,\n} from './types';\nimport { expectedVersionValue, operationResult } from './utils';\n\nexport interface InMemoryDocumentsCollection<T extends Document> {\n handle: (\n id: string,\n handle: DocumentHandler<T>,\n options?: DatabaseHandleOptions,\n ) => Promise<DatabaseHandleResult<T>>;\n findOne: (predicate?: Predicate<T>) => Promise<T | null>;\n find: (predicate?: Predicate<T>) => Promise<T[]>;\n insertOne: (\n document: OptionalUnlessRequiredIdAndVersion<T>,\n ) => Promise<InsertOneResult>;\n deleteOne: (predicate?: Predicate<T>) => Promise<DeleteResult>;\n replaceOne: (\n predicate: Predicate<T>,\n document: WithoutId<T>,\n options?: ReplaceOneOptions,\n ) => Promise<UpdateResult>;\n}\n\nexport interface InMemoryDatabase {\n collection: <T extends Document>(\n name: string,\n ) => InMemoryDocumentsCollection<T>;\n}\n\ntype Predicate<T> = (item: T) => boolean;\ntype CollectionName = string;\n\nexport const getInMemoryDatabase = (): InMemoryDatabase => {\n const storage = new Map<CollectionName, WithIdAndVersion<Document>[]>();\n\n return {\n collection: <T extends Document, CollectionName extends string>(\n collectionName: CollectionName,\n collectionOptions: {\n errors?: DatabaseHandleOptionErrors;\n } = {},\n ): InMemoryDocumentsCollection<T> => {\n const ensureCollectionCreated = () => {\n if (!storage.has(collectionName)) storage.set(collectionName, []);\n };\n\n const errors = collectionOptions.errors;\n\n const collection = {\n collectionName,\n insertOne: async (\n document: OptionalUnlessRequiredIdAndVersion<T>,\n ): Promise<InsertOneResult> => {\n ensureCollectionCreated();\n\n const _id = (document._id as string | undefined | null) ?? uuid();\n const _version = document._version ?? 1n;\n\n const existing = await collection.findOne((c) => c._id === _id);\n\n if (existing) {\n return operationResult<InsertOneResult>(\n {\n successful: false,\n insertedId: null,\n nextExpectedVersion: _version,\n },\n { operationName: 'insertOne', collectionName, errors },\n );\n }\n\n const documentsInCollection = storage.get(collectionName)!;\n const newDocument = { ...document, _id, _version };\n const newCollection = [...documentsInCollection, newDocument];\n storage.set(collectionName, newCollection);\n\n return operationResult<InsertOneResult>(\n {\n successful: true,\n insertedId: _id,\n nextExpectedVersion: _version,\n },\n { operationName: 'insertOne', collectionName, errors },\n );\n },\n findOne: (predicate?: Predicate<T>): Promise<T | null> => {\n ensureCollectionCreated();\n\n const documentsInCollection = storage.get(collectionName);\n const filteredDocuments = predicate\n ? documentsInCollection?.filter((doc) => predicate(doc as T))\n : documentsInCollection;\n\n const firstOne = filteredDocuments?.[0] ?? null;\n\n return Promise.resolve(firstOne as T | null);\n },\n find: (predicate?: Predicate<T>): Promise<T[]> => {\n ensureCollectionCreated();\n\n const documentsInCollection = storage.get(collectionName);\n const filteredDocuments = predicate\n ? documentsInCollection?.filter((doc) => predicate(doc as T))\n : documentsInCollection;\n\n return Promise.resolve(filteredDocuments as T[]);\n },\n deleteOne: (predicate?: Predicate<T>): Promise<DeleteResult> => {\n ensureCollectionCreated();\n\n const documentsInCollection = storage.get(collectionName)!;\n\n if (predicate) {\n const foundIndex = documentsInCollection.findIndex((doc) =>\n predicate(doc as T),\n );\n\n if (foundIndex === -1) {\n return Promise.resolve(\n operationResult<DeleteResult>(\n {\n successful: false,\n matchedCount: 0,\n deletedCount: 0,\n },\n { operationName: 'deleteOne', collectionName, errors },\n ),\n );\n } else {\n const newCollection = documentsInCollection.toSpliced(\n foundIndex,\n 1,\n );\n\n storage.set(collectionName, newCollection);\n\n return Promise.resolve(\n operationResult<DeleteResult>(\n {\n successful: true,\n matchedCount: 1,\n deletedCount: 1,\n },\n { operationName: 'deleteOne', collectionName, errors },\n ),\n );\n }\n }\n\n const newCollection = documentsInCollection.slice(1);\n\n storage.set(collectionName, newCollection);\n\n return Promise.resolve(\n operationResult<DeleteResult>(\n {\n successful: true,\n matchedCount: 1,\n deletedCount: 1,\n },\n { operationName: 'deleteOne', collectionName, errors },\n ),\n );\n },\n replaceOne: (\n predicate: Predicate<T>,\n document: WithoutId<T>,\n options?: ReplaceOneOptions,\n ): Promise<UpdateResult> => {\n ensureCollectionCreated();\n\n const documentsInCollection = storage.get(collectionName)!;\n\n const firstIndex = documentsInCollection.findIndex((doc) =>\n predicate(doc as T),\n );\n\n if (firstIndex === undefined || firstIndex === -1) {\n return Promise.resolve(\n operationResult<UpdateResult>(\n {\n successful: false,\n matchedCount: 0,\n modifiedCount: 0,\n nextExpectedVersion: 0n,\n },\n { operationName: 'replaceOne', collectionName, errors },\n ),\n );\n }\n\n const existing = documentsInCollection[firstIndex]!;\n\n if (\n typeof options?.expectedVersion === 'bigint' &&\n existing._version !== options.expectedVersion\n ) {\n return Promise.resolve(\n operationResult<UpdateResult>(\n {\n successful: false,\n matchedCount: 1,\n modifiedCount: 0,\n nextExpectedVersion: existing._version,\n },\n { operationName: 'replaceOne', collectionName, errors },\n ),\n );\n }\n\n const newVersion = existing._version + 1n;\n\n const newCollection = documentsInCollection.with(firstIndex, {\n _id: existing._id,\n ...document,\n _version: newVersion,\n });\n\n storage.set(collectionName, newCollection);\n\n return Promise.resolve(\n operationResult<UpdateResult>(\n {\n successful: true,\n modifiedCount: 1,\n matchedCount: firstIndex,\n nextExpectedVersion: newVersion,\n },\n { operationName: 'replaceOne', collectionName, errors },\n ),\n );\n },\n handle: async (\n id: string,\n handle: DocumentHandler<T>,\n options?: DatabaseHandleOptions,\n ): Promise<DatabaseHandleResult<T>> => {\n const { expectedVersion: version, ...operationOptions } =\n options ?? {};\n ensureCollectionCreated();\n const existing = await collection.findOne(({ _id }) => _id === id);\n\n const expectedVersion = expectedVersionValue(version);\n\n if (\n (existing == null && version === 'DOCUMENT_EXISTS') ||\n (existing == null && expectedVersion != null) ||\n (existing != null && version === 'DOCUMENT_DOES_NOT_EXIST') ||\n (existing != null &&\n expectedVersion !== null &&\n existing._version !== expectedVersion)\n ) {\n return operationResult<DatabaseHandleResult<T>>(\n {\n successful: false,\n document: existing as WithIdAndVersion<T>,\n },\n { operationName: 'handle', collectionName, errors },\n );\n }\n\n const result = handle(existing !== null ? { ...existing } : null);\n\n if (deepEquals(existing, result))\n return operationResult<DatabaseHandleResult<T>>(\n {\n successful: true,\n document: existing as WithIdAndVersion<T>,\n },\n { operationName: 'handle', collectionName, errors },\n );\n\n if (!existing && result) {\n const newDoc = { ...result, _id: id };\n const insertResult = await collection.insertOne({\n ...newDoc,\n _id: id,\n } as OptionalUnlessRequiredIdAndVersion<T>);\n return {\n ...insertResult,\n document: {\n ...newDoc,\n _version: insertResult.nextExpectedVersion,\n } as unknown as WithIdAndVersion<T>,\n };\n }\n\n if (existing && !result) {\n const deleteResult = await collection.deleteOne(\n ({ _id }) => id === _id,\n );\n return { ...deleteResult, document: null };\n }\n\n if (existing && result) {\n const replaceResult = await collection.replaceOne(\n ({ _id }) => id === _id,\n result,\n {\n ...operationOptions,\n expectedVersion: expectedVersion ?? 'DOCUMENT_EXISTS',\n },\n );\n return {\n ...replaceResult,\n document: {\n ...result,\n _version: replaceResult.nextExpectedVersion,\n } as unknown as WithIdAndVersion<T>,\n };\n }\n\n return operationResult<DatabaseHandleResult<T>>(\n {\n successful: true,\n document: existing as WithIdAndVersion<T>,\n },\n { operationName: 'handle', collectionName, errors },\n );\n },\n };\n\n return collection;\n },\n };\n};\n","import retry from 'async-retry';\nimport { EmmettError } from '../errors';\nimport { JSONParser } from '../serialization';\n\nexport type AsyncRetryOptions<T = unknown> = retry.Options & {\n shouldRetryResult?: (result: T) => boolean;\n shouldRetryError?: (error?: unknown) => boolean;\n};\n\nexport const NoRetries: AsyncRetryOptions = { retries: 0 };\n\nexport const asyncRetry = async <T>(\n fn: () => Promise<T>,\n opts?: AsyncRetryOptions<T>,\n): Promise<T> => {\n if (opts === undefined || opts.retries === 0) return fn();\n\n return retry(\n async (bail) => {\n try {\n const result = await fn();\n\n if (opts?.shouldRetryResult && opts.shouldRetryResult(result)) {\n throw new EmmettError(\n `Retrying because of result: ${JSONParser.stringify(result)}`,\n );\n }\n return result;\n } catch (error) {\n if (opts?.shouldRetryError && !opts.shouldRetryError(error)) {\n bail(error as Error);\n return undefined as unknown as T;\n }\n throw error;\n }\n },\n opts ?? { retries: 0 },\n );\n};\n","import { v4 as uuid } from 'uuid';\nimport {\n handleInMemoryProjections,\n type InMemoryProjectionDefinition,\n} from '.';\nimport {\n getInMemoryDatabase,\n type Document,\n type InMemoryDatabase,\n} from '../../../database';\nimport { isErrorConstructor } from '../../../errors';\nimport { JSONParser } from '../../../serialization';\nimport {\n assertFails,\n AssertionError,\n assertTrue,\n type ThenThrows,\n} from '../../../testing';\nimport type { CombinedReadEventMetadata, ReadEvent } from '../../../typing';\nimport { type Event } from '../../../typing';\nimport type {\n InMemoryEventStore,\n InMemoryReadEventMetadata,\n} from '../../inMemoryEventStore';\n\n// Define a more specific type for T that extends Document\ntype DocumentWithId = Document & { _id?: string | number };\n\nexport type InMemoryProjectionSpecEvent<\n EventType extends Event,\n EventMetaDataType extends InMemoryReadEventMetadata =\n InMemoryReadEventMetadata,\n> = EventType & {\n metadata?: Partial<EventMetaDataType>;\n};\n\nexport type InMemoryProjectionSpecWhenOptions = { numberOfTimes: number };\n\nexport type InMemoryProjectionSpec<EventType extends Event> = (\n givenEvents: InMemoryProjectionSpecEvent<EventType>[],\n) => {\n when: (\n events: InMemoryProjectionSpecEvent<EventType>[],\n options?: InMemoryProjectionSpecWhenOptions,\n ) => {\n then: (assert: InMemoryProjectionAssert, message?: string) => Promise<void>;\n thenThrows: <ErrorType extends Error = Error>(\n ...args: Parameters<ThenThrows<ErrorType>>\n ) => Promise<void>;\n };\n};\n\nexport type InMemoryProjectionAssert = (options: {\n database: InMemoryDatabase;\n}) => Promise<void | boolean>;\n\nexport type InMemoryProjectionSpecOptions<EventType extends Event> = {\n projection: InMemoryProjectionDefinition<EventType>;\n};\n\nexport const InMemoryProjectionSpec = {\n for: <EventType extends Event>(\n options: InMemoryProjectionSpecOptions<EventType>,\n ): InMemoryProjectionSpec<EventType> => {\n const { projection } = options;\n\n return (givenEvents: InMemoryProjectionSpecEvent<EventType>[]) => {\n return {\n when: (\n events: InMemoryProjectionSpecEvent<EventType>[],\n options?: InMemoryProjectionSpecWhenOptions,\n ) => {\n const allEvents: ReadEvent<EventType, InMemoryReadEventMetadata>[] =\n [];\n\n const run = async (database: InMemoryDatabase) => {\n let globalPosition = 0n;\n const numberOfTimes = options?.numberOfTimes ?? 1;\n\n for (const event of [\n ...givenEvents,\n ...Array.from({ length: numberOfTimes }).flatMap(() => events),\n ]) {\n const metadata: InMemoryReadEventMetadata = {\n globalPosition: ++globalPosition,\n streamPosition: globalPosition,\n streamName: event.metadata?.streamName ?? `test-${uuid()}`,\n messageId: uuid(),\n };\n\n allEvents.push({\n ...event,\n kind: 'Event',\n metadata: {\n ...metadata,\n ...('metadata' in event ? (event.metadata ?? {}) : {}),\n } as CombinedReadEventMetadata<\n EventType,\n InMemoryReadEventMetadata\n >,\n });\n }\n\n // Create a minimal mock EventStore implementation\n const mockEventStore = {\n database,\n aggregateStream: async () => {\n return Promise.resolve({\n state: {},\n currentStreamVersion: 0n,\n streamExists: false,\n });\n },\n readStream: async () => {\n return Promise.resolve({\n events: [],\n currentStreamVersion: 0n,\n streamExists: false,\n });\n },\n appendToStream: async () => {\n return Promise.resolve({\n nextExpectedStreamVersion: 0n,\n createdNewStream: false,\n });\n },\n streamExists: async () => {\n return Promise.resolve(false);\n },\n } as InMemoryEventStore;\n\n await handleInMemoryProjections({\n events: allEvents,\n projections: [projection],\n database,\n eventStore: mockEventStore,\n });\n };\n\n return {\n then: async (\n assertFn: InMemoryProjectionAssert,\n message?: string,\n ): Promise<void> => {\n const database = getInMemoryDatabase();\n await run(database);\n\n const succeeded = await assertFn({ database });\n\n if (succeeded !== undefined && succeeded === false) {\n assertFails(\n message ??\n \"Projection specification didn't match the criteria\",\n );\n }\n },\n thenThrows: async <ErrorType extends Error = Error>(\n ...args: Parameters<ThenThrows<ErrorType>>\n ): Promise<void> => {\n const database = getInMemoryDatabase();\n try {\n await run(database);\n throw new AssertionError('Handler did not fail as expected');\n } catch (error) {\n if (error instanceof AssertionError) throw error;\n\n if (args.length === 0) return;\n\n if (!isErrorConstructor(args[0])) {\n assertTrue(\n args[0](error as ErrorType),\n `Error didn't match the error condition: ${error?.toString()}`,\n );\n return;\n }\n\n assertTrue(\n error instanceof args[0],\n `Caught error is not an instance of the expected type: ${error?.toString()}`,\n );\n\n if (args[1]) {\n assertTrue(\n args[1](error as ErrorType),\n `Error didn't match the error condition: ${error?.toString()}`,\n );\n }\n }\n },\n };\n },\n };\n };\n },\n};\n\n// Helper functions for creating events in stream\nexport const eventInStream = <\n EventType extends Event = Event,\n EventMetaDataType extends InMemoryReadEventMetadata =\n InMemoryReadEventMetadata,\n>(\n streamName: string,\n event: InMemoryProjectionSpecEvent<EventType, EventMetaDataType>,\n): InMemoryProjectionSpecEvent<EventType, EventMetaDataType> => {\n return {\n ...event,\n metadata: {\n ...(event.metadata ?? {}),\n streamName: event.metadata?.streamName ?? streamName,\n } as Partial<EventMetaDataType>,\n };\n};\n\nexport const eventsInStream = <\n EventType extends Event = Event,\n EventMetaDataType extends InMemoryReadEventMetadata =\n InMemoryReadEventMetadata,\n>(\n streamName: string,\n events: InMemoryProjectionSpecEvent<EventType, EventMetaDataType>[],\n): InMemoryProjectionSpecEvent<EventType, EventMetaDataType>[] => {\n return events.map((e) => eventInStream(streamName, e));\n};\n\nexport const newEventsInStream = eventsInStream;\n\n// Assertion helpers for checking documents\nexport function documentExists<T extends DocumentWithId>(\n expected: Partial<T>,\n options: { inCollection: string; withId: string | number },\n): InMemoryProjectionAssert {\n return async ({ database }) => {\n const collection = database.collection<T>(options.inCollection);\n\n const document = await collection.findOne((doc) => {\n // Handle both string IDs and numeric IDs in a type-safe way\n const docId = '_id' in doc ? doc._id : undefined;\n return docId === options.withId;\n });\n\n if (!document) {\n assertFails(\n `Document with ID ${options.withId} does not exist in collection ${options.inCollection}`,\n );\n return Promise.resolve(false);\n }\n\n // Check that all expected properties exist with expected values\n for (const [key, value] of Object.entries(expected)) {\n const propKey = key as keyof typeof document;\n if (\n !(key in document) ||\n JSONParser.stringify(document[propKey]) !== JSONParser.stringify(value)\n ) {\n assertFails(`Property ${key} doesn't match the expected value`);\n return Promise.resolve(false);\n }\n }\n\n return Promise.resolve(true);\n };\n}\n\n// Helper for checking document contents\nexport const expectInMemoryDocuments = {\n fromCollection: <T extends DocumentWithId>(collectionName: string) => ({\n withId: (id: string | number) => ({\n toBeEqual: (expected: Partial<T>): InMemoryProjectionAssert =>\n documentExists<T>(expected, {\n inCollection: collectionName,\n withId: id,\n }),\n }),\n }),\n};\n","import { v7 as uuid } from 'uuid';\nimport type { EmmettError } from '../errors';\nimport { upcastRecordedMessage } from '../eventStore';\nimport type { ProjectionDefinition } from '../projections';\nimport {\n defaultTag,\n type AnyEvent,\n type AnyMessage,\n type AnyReadEventMetadata,\n type AnyRecordedMessageMetadata,\n type BatchRecordedMessageHandlerWithContext,\n type CanHandle,\n type DefaultRecord,\n type Event,\n type GlobalPositionTypeOfRecordedMessageMetadata,\n type Message,\n type MessageHandlerResult,\n type RecordedMessage,\n type SingleMessageHandlerWithContext,\n type SingleRecordedMessageHandlerWithContext,\n} from '../typing';\nimport { onShutdown } from '../utils/shutdown';\nimport { isBigint } from '../validation';\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type CurrentMessageProcessorPosition<CheckpointType = any> =\n | { lastCheckpoint: CheckpointType }\n | 'BEGINNING'\n | 'END';\n\nexport type GetCheckpoint<\n MessageType extends AnyMessage = AnyMessage,\n MessageMetadataType extends AnyReadEventMetadata = AnyReadEventMetadata,\n CheckpointType =\n GlobalPositionTypeOfRecordedMessageMetadata<MessageMetadataType>,\n> = (\n message: RecordedMessage<MessageType, MessageMetadataType>,\n) => CheckpointType | null;\n\nexport const getCheckpoint = <\n MessageType extends AnyMessage = AnyMessage,\n MessageMetadataType extends AnyReadEventMetadata = AnyReadEventMetadata,\n CheckpointType =\n GlobalPositionTypeOfRecordedMessageMetadata<MessageMetadataType>,\n>(\n message: RecordedMessage<MessageType, MessageMetadataType>,\n): CheckpointType | null => {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return 'checkpoint' in message.metadata\n ? // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n message.metadata.checkpoint\n : 'globalPosition' in message.metadata &&\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n isBigint(message.metadata.globalPosition)\n ? // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n message.metadata.globalPosition\n : 'streamPosition' in message.metadata &&\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n isBigint(message.metadata.streamPosition)\n ? // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n message.metadata.streamPosition\n : null;\n};\n\nexport const wasMessageHandled = <\n MessageType extends AnyMessage = AnyMessage,\n MessageMetadataType extends AnyReadEventMetadata = AnyReadEventMetadata,\n CheckpointType =\n GlobalPositionTypeOfRecordedMessageMetadata<MessageMetadataType>,\n>(\n message: RecordedMessage<MessageType, MessageMetadataType>,\n checkpoint: CheckpointType | null,\n): boolean => {\n //TODO Make it smarter\n const messageCheckpoint = getCheckpoint(message);\n const checkpointBigint = checkpoint as bigint | null;\n\n return (\n messageCheckpoint !== null &&\n messageCheckpoint !== undefined &&\n checkpointBigint !== null &&\n checkpointBigint !== undefined &&\n messageCheckpoint <= checkpointBigint\n );\n};\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type MessageProcessorStartFrom<CheckpointType = any> =\n | CurrentMessageProcessorPosition<CheckpointType>\n | 'CURRENT';\n\nexport type MessageProcessorType = 'projector' | 'reactor';\nexport const MessageProcessorType = {\n PROJECTOR: 'projector' as MessageProcessorType,\n REACTOR: 'reactor' as MessageProcessorType,\n};\n\nexport type MessageProcessor<\n MessageType extends AnyMessage = AnyMessage,\n MessageMetadataType extends AnyReadEventMetadata = AnyReadEventMetadata,\n HandlerContext extends DefaultRecord | undefined = undefined,\n CheckpointType =\n GlobalPositionTypeOfRecordedMessageMetadata<MessageMetadataType>,\n> = {\n id: string;\n instanceId: string;\n type: string;\n init: (options: Partial<HandlerContext>) => Promise<void>;\n start: (\n options: Partial<HandlerContext>,\n ) => Promise<CurrentMessageProcessorPosition<CheckpointType> | undefined>;\n close: (closeOptions: Partial<HandlerContext>) => Promise<void>;\n isActive: boolean;\n handle: BatchRecordedMessageHandlerWithContext<\n MessageType,\n MessageMetadataType,\n Partial<HandlerContext>\n >;\n};\n\nexport const MessageProcessor = {\n result: {\n skip: (options?: { reason?: string }): MessageHandlerResult => ({\n type: 'SKIP',\n ...(options ?? {}),\n }),\n stop: (options?: {\n reason?: string;\n error?: EmmettError;\n }): MessageHandlerResult => ({\n type: 'STOP',\n ...(options ?? {}),\n }),\n },\n};\n\nexport type MessageProcessingScope<\n HandlerContext extends DefaultRecord | undefined = undefined,\n> = <Result = MessageHandlerResult>(\n handler: (context: HandlerContext) => Result | Promise<Result>,\n partialContext: Partial<HandlerContext>,\n) => Result | Promise<Result>;\n\nexport type Checkpointer<\n MessageType extends AnyMessage = AnyMessage,\n MessageMetadataType extends AnyReadEventMetadata = AnyReadEventMetadata,\n HandlerContext extends DefaultRecord = DefaultRecord,\n CheckpointType =\n GlobalPositionTypeOfRecordedMessageMetadata<MessageMetadataType>,\n> = {\n read: ReadProcessorCheckpoint<CheckpointType, HandlerContext>;\n store: StoreProcessorCheckpoint<\n MessageType,\n MessageMetadataType,\n CheckpointType,\n HandlerContext\n >;\n};\n\nexport type ProcessorHooks<\n HandlerContext extends DefaultRecord = DefaultRecord,\n> = {\n onInit?: OnReactorInitHook<HandlerContext>;\n onStart?: OnReactorStartHook<HandlerContext>;\n onClose?: OnReactorCloseHook<HandlerContext>;\n};\n\nexport type BaseMessageProcessorOptions<\n MessageType extends AnyMessage = AnyMessage,\n MessageMetadataType extends AnyReadEventMetadata = AnyReadEventMetadata,\n HandlerContext extends DefaultRecord = DefaultRecord,\n CheckpointType =\n GlobalPositionTypeOfRecordedMessageMetadata<MessageMetadataType>,\n> = {\n type?: string;\n processorId: string;\n processorInstanceId?: string;\n version?: number;\n partition?: string;\n startFrom?: MessageProcessorStartFrom<CheckpointType>;\n stopAfter?: (\n message: RecordedMessage<MessageType, MessageMetadataType>,\n ) => boolean;\n processingScope?: MessageProcessingScope<HandlerContext>;\n checkpoints?: Checkpointer<\n MessageType,\n MessageMetadataType,\n HandlerContext,\n CheckpointType\n >;\n canHandle?: CanHandle<MessageType>;\n hooks?: ProcessorHooks<HandlerContext>;\n};\n\nexport type HandlerOptions<\n MessageType extends AnyMessage = AnyMessage,\n MessageMetadataType extends AnyReadEventMetadata = AnyReadEventMetadata,\n HandlerContext extends DefaultRecord = DefaultRecord,\n> =\n | {\n eachMessage: SingleRecordedMessageHandlerWithContext<\n MessageType,\n MessageMetadataType,\n HandlerContext\n >;\n eachBatch?: never;\n }\n | {\n eachMessage?: never;\n eachBatch: BatchRecordedMessageHandlerWithContext<\n MessageType,\n MessageMetadataType,\n HandlerContext\n >;\n };\n\nexport type OnReactorInitHook<\n HandlerContext extends DefaultRecord = DefaultRecord,\n> = (context: HandlerContext) => Promise<void>;\n\nexport type OnReactorStartHook<\n HandlerContext extends DefaultRecord = DefaultRecord,\n> = (context: HandlerContext) => Promise<void>;\n\nexport type OnReactorCloseHook<\n HandlerContext extends DefaultRecord = DefaultRecord,\n> = (context: HandlerContext) => Promise<void>;\n\nexport type ReactorOptions<\n MessageType extends AnyMessage = AnyMessage,\n MessageMetadataType extends AnyReadEventMetadata = AnyReadEventMetadata,\n HandlerContext extends DefaultRecord = DefaultRecord,\n CheckpointType =\n GlobalPositionTypeOfRecordedMessageMetadata<MessageMetadataType>,\n MessagePayloadType extends AnyMessage = MessageType,\n> = BaseMessageProcessorOptions<\n MessageType,\n MessageMetadataType,\n HandlerContext,\n CheckpointType\n> &\n HandlerOptions<MessageType, MessageMetadataType, HandlerContext> & {\n messageOptions?: {\n schema?: {\n versioning?: { upcast?: (event: MessagePayloadType) => MessageType };\n };\n };\n };\n\nexport type ProjectorOptions<\n EventType extends AnyEvent = AnyEvent,\n MessageMetadataType extends AnyReadEventMetadata = AnyReadEventMetadata,\n HandlerContext extends DefaultRecord = DefaultRecord,\n CheckpointType =\n GlobalPositionTypeOfRecordedMessageMetadata<MessageMetadataType>,\n EventPayloadType extends Event = EventType,\n> = Omit<\n BaseMessageProcessorOptions<\n EventType,\n MessageMetadataType,\n HandlerContext,\n CheckpointType\n >,\n 'type' | 'processorId'\n> & { processorId?: string } & {\n truncateOnStart?: boolean;\n projection: ProjectionDefinition<\n EventType,\n MessageMetadataType,\n HandlerContext,\n EventPayloadType\n >;\n};\n\nexport const defaultProcessingMessageProcessingScope = <\n HandlerContext = never,\n Result = MessageHandlerResult,\n>(\n handler: (context: HandlerContext) => Result | Promise<Result>,\n partialContext: Partial<HandlerContext>,\n) => handler(partialContext as HandlerContext);\n\nexport type ReadProcessorCheckpointResult<CheckpointType = unknown> = {\n lastCheckpoint: CheckpointType | null;\n};\n\nexport type ReadProcessorCheckpoint<\n CheckpointType = unknown,\n HandlerContext extends DefaultRecord = DefaultRecord,\n> = (\n options: { processorId: string; partition?: string },\n context: HandlerContext,\n) => Promise<ReadProcessorCheckpointResult<CheckpointType>>;\n\nexport type StoreProcessorCheckpointResult<CheckpointType = unknown> =\n | {\n success: true;\n newCheckpoint: CheckpointType;\n }\n | { success: false; reason: 'IGNORED' | 'MISMATCH' | 'CURRENT_AHEAD' };\n\nexport type StoreProcessorCheckpoint<\n MessageType extends Message = AnyMessage,\n MessageMetadataType extends AnyReadEventMetadata = AnyReadEventMetadata,\n CheckpointType = unknown,\n HandlerContext extends DefaultRecord | undefined = undefined,\n> =\n | ((\n options: {\n message: RecordedMessage<MessageType, MessageMetadataType>;\n processorId: string;\n version: number | undefined;\n lastCheckpoint: CheckpointType | null;\n partition?: string;\n },\n context: HandlerContext,\n ) => Promise<StoreProcessorCheckpointResult<CheckpointType | null>>)\n | ((\n options: {\n message: RecordedMessage<MessageType, MessageMetadataType>;\n processorId: string;\n version: number | undefined;\n lastCheckpoint: CheckpointType | null;\n partition?: string;\n },\n context: HandlerContext,\n ) => Promise<StoreProcessorCheckpointResult<CheckpointType>>);\n\nexport const defaultProcessorVersion = 1;\nexport const defaultProcessorPartition = defaultTag;\n\nexport const getProcessorInstanceId = (processorId: string): string =>\n `${processorId}:${uuid()}`;\n\nexport const getProjectorId = (options: { projectionName: string }): string =>\n `emt:processor:projector:${options.projectionName}`;\n\nexport const reactor = <\n MessageType extends Message = AnyMessage,\n MessageMetadataType extends AnyReadEventMetadata = AnyReadEventMetadata,\n HandlerContext extends DefaultRecord = DefaultRecord,\n CheckpointType =\n GlobalPositionTypeOfRecordedMessageMetadata<MessageMetadataType>,\n MessagePayloadType extends Message = MessageType,\n>(\n options: ReactorOptions<\n MessageType,\n MessageMetadataType,\n HandlerContext,\n CheckpointType,\n MessagePayloadType\n >,\n): MessageProcessor<\n MessageType,\n MessageMetadataType,\n HandlerContext,\n CheckpointType\n> => {\n const {\n checkpoints,\n processorId,\n processorInstanceId: instanceId = getProcessorInstanceId(processorId),\n type = MessageProcessorType.REACTOR,\n version = defaultProcessorVersion,\n partition = defaultProcessorPartition,\n hooks = {},\n processingScope = defaultProcessingMessageProcessingScope,\n startFrom,\n canHandle,\n stopAfter,\n } = options;\n\n const eachMessage: SingleMessageHandlerWithContext<\n MessageType,\n MessageMetadataType,\n HandlerContext\n > =\n 'eachMessage' in options && options.eachMessage\n ? options.eachMessage\n : () => Promise.resolve();\n\n let isInitiated = false;\n let isActive = false;\n\n let lastCheckpoint: CheckpointType | null = null;\n let closeSignal: (() => void) | null = null;\n\n const init = async (initOptions: Partial<HandlerContext>): Promise<void> => {\n if (isInitiated) return;\n\n if (hooks.onInit === undefined) {\n isInitiated = true;\n return;\n }\n\n return await processingScope(async (context) => {\n await hooks.onInit!(context);\n isInitiated = true;\n }, initOptions);\n };\n\n const close = async (\n closeOptions: Partial<HandlerContext>,\n ): Promise<void> => {\n // TODO: Align when active is set to false\n // if (!isActive) return;\n\n isActive = false;\n\n if (closeSignal) {\n closeSignal();\n closeSignal = null;\n }\n\n if (hooks.onClose) {\n await processingScope(hooks.onClose, closeOptions);\n }\n };\n\n return {\n // TODO: Consider whether not make it optional or add URN prefix\n id: processorId,\n instanceId,\n type,\n init,\n start: async (\n startOptions: Partial<HandlerContext>,\n ): Promise<CurrentMessageProcessorPosition<CheckpointType> | undefined> => {\n if (isActive) return;\n\n await init(startOptions);\n\n isActive = true;\n\n closeSignal = onShutdown(() => close({}));\n\n if (lastCheckpoint !== null)\n return {\n lastCheckpoint,\n };\n\n return await processingScope(async (context) => {\n if (hooks.onStart) {\n await hooks.onStart(context);\n }\n\n if (startFrom && startFrom !== 'CURRENT') return startFrom;\n\n if (checkpoints) {\n const readResult = await checkpoints?.read(\n {\n processorId: processorId,\n partition,\n },\n { ...startOptions, ...context },\n );\n lastCheckpoint = readResult.lastCheckpoint;\n }\n\n if (lastCheckpoint === null) return 'BEGINNING';\n\n return {\n lastCheckpoint,\n };\n }, startOptions);\n },\n close,\n get isActive() {\n return isActive;\n },\n handle: async (\n messages: RecordedMessage<MessageType, MessageMetadataType>[],\n partialContext: Partial<HandlerContext>,\n ): Promise<MessageHandlerResult> => {\n if (!isActive) return Promise.resolve();\n\n return await processingScope(async (context) => {\n let result: MessageHandlerResult = undefined;\n\n for (const message of messages) {\n if (wasMessageHandled(message, lastCheckpoint)) continue;\n\n const upcasted = upcastRecordedMessage(\n // TODO: Make it smarter\n message as unknown as RecordedMessage<\n MessagePayloadType,\n MessageMetadataType\n >,\n options.messageOptions?.schema?.versioning,\n );\n\n if (canHandle !== undefined && !canHandle.includes(upcasted.type))\n continue;\n\n const messageProcessingResult = await eachMessage(upcasted, context);\n\n if (checkpoints) {\n const storeCheckpointResult: StoreProcessorCheckpointResult<CheckpointType | null> =\n await checkpoints.store(\n {\n processorId,\n version,\n message: upcasted,\n lastCheckpoint,\n partition,\n },\n context,\n );\n\n if (storeCheckpointResult.success) {\n // TODO: Add correct handling of the storing checkpoint\n lastCheckpoint = storeCheckpointResult.newCheckpoint;\n }\n }\n\n if (\n messageProcessingResult &&\n messageProcessingResult.type === 'STOP'\n ) {\n isActive = false;\n result = messageProcessingResult;\n break;\n }\n\n if (stopAfter && stopAfter(upcasted)) {\n isActive = false;\n result = { type: 'STOP', reason: 'Stop condition reached' };\n break;\n }\n\n if (\n messageProcessingResult &&\n messageProcessingResult.type === 'SKIP'\n )\n continue;\n }\n\n return result;\n }, partialContext);\n },\n };\n};\n\nexport const projector = <\n EventType extends Event = Event,\n EventMetaDataType extends AnyRecordedMessageMetadata =\n AnyRecordedMessageMetadata,\n HandlerContext extends DefaultRecord = DefaultRecord,\n CheckpointType =\n GlobalPositionTypeOfRecordedMessageMetadata<EventMetaDataType>,\n EventPayloadType extends Event = EventType,\n>(\n options: ProjectorOptions<\n EventType,\n EventMetaDataType,\n HandlerContext,\n CheckpointType,\n EventPayloadType\n >,\n): MessageProcessor<\n EventType,\n EventMetaDataType,\n HandlerContext,\n CheckpointType\n> => {\n const {\n projection,\n processorId = getProjectorId({\n projectionName: projection.name ?? 'unknown',\n }),\n ...rest\n } = options;\n\n return reactor<\n EventType,\n EventMetaDataType,\n HandlerContext,\n CheckpointType,\n EventPayloadType\n >({\n ...rest,\n type: MessageProcessorType.PROJECTOR,\n canHandle: projection.canHandle,\n processorId,\n messageOptions: options.projection.eventsOptions,\n hooks: {\n onInit: options.hooks?.onInit,\n onStart:\n (options.truncateOnStart && options.projection.truncate) ||\n options.hooks?.onStart\n ? async (context: HandlerContext) => {\n if (options.truncateOnStart && options.projection.truncate)\n await options.projection.truncate(context);\n\n if (options.hooks?.onStart) await options.hooks?.onStart(context);\n }\n : undefined,\n onClose: options.hooks?.onClose,\n },\n eachMessage: async (\n event: RecordedMessage<EventType, EventMetaDataType>,\n context: HandlerContext,\n ) => projection.handle([event], context),\n });\n};\n","export * from './deepReadonly';\n\nexport * from './command';\nexport * from './event';\nexport * from './message';\nexport * from './messageHandling';\n\nexport * from './decider';\nexport * from './workflow';\n\nexport type Brand<K, T> = K & { readonly __brand: T };\nexport type Flavour<K, T> = K & { readonly __brand?: T };\n\nexport type DefaultRecord = Record<string, unknown>;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type AnyRecord = Record<string, any>;\n\nexport type NonNullable<T> = T extends null | undefined ? never : T;\n\nexport const emmettPrefix = 'emt';\n\nexport const globalTag = 'global';\nexport const defaultTag = `${emmettPrefix}:default`;\nexport const unknownTag = `${emmettPrefix}:unknown`;\n","import { EmmettError } from '../errors';\n\nexport type TaskQueue = TaskQueueItem[];\n\nexport type TaskQueueItem = {\n task: () => Promise<void>;\n options?: EnqueueTaskOptions;\n};\n\nexport type TaskProcessorOptions = {\n maxActiveTasks: number;\n maxQueueSize: number;\n maxTaskIdleTime?: number;\n};\n\nexport type Task<T> = (context: TaskContext) => Promise<T>;\n\nexport type TaskContext = {\n ack: () => void;\n};\n\nexport type EnqueueTaskOptions = { taskGroupId?: string };\n\nexport class TaskProcessor {\n private queue: TaskQueue = [];\n private isProcessing = false;\n private activeTasks = 0;\n private activeGroups: Set<string> = new Set();\n\n constructor(private options: TaskProcessorOptions) {}\n\n enqueue<T>(task: Task<T>, options?: EnqueueTaskOptions): Promise<T> {\n if (this.queue.length >= this.options.maxQueueSize) {\n return Promise.reject(\n new EmmettError(\n 'Too many pending connections. Please try again later.',\n ),\n );\n }\n\n return this.schedule(task, options);\n }\n\n waitForEndOfProcessing(): Promise<void> {\n return this.schedule(({ ack }) => Promise.resolve(ack()));\n }\n\n private schedule<T>(task: Task<T>, options?: EnqueueTaskOptions): Promise<T> {\n return promiseWithDeadline(\n (resolve, reject) => {\n const taskWithContext = () => {\n return new Promise<void>((resolveTask, failTask) => {\n const taskPromise = task({\n ack: resolveTask,\n });\n\n taskPromise.then(resolve).catch((err) => {\n // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors\n failTask(err);\n reject(err);\n });\n });\n };\n\n this.queue.push({ task: taskWithContext, options });\n if (!this.isProcessing) {\n this.ensureProcessing();\n }\n },\n { deadline: this.options.maxTaskIdleTime },\n );\n }\n\n private ensureProcessing(): void {\n if (this.isProcessing) return;\n this.isProcessing = true;\n this.processQueue();\n }\n\n private processQueue(): void {\n try {\n while (\n this.activeTasks < this.options.maxActiveTasks &&\n this.queue.length > 0\n ) {\n const item = this.takeFirstAvailableItem();\n\n if (item === null) return;\n\n const groupId = item.options?.taskGroupId;\n\n if (groupId) {\n // Mark the group as active\n this.activeGroups.add(groupId);\n }\n\n this.activeTasks++;\n void this.executeItem(item);\n }\n } catch (error) {\n console.error(error);\n throw error;\n } finally {\n this.isProcessing = false;\n if (\n this.hasItemsToProcess() &&\n this.activeTasks < this.options.maxActiveTasks\n ) {\n this.ensureProcessing();\n }\n }\n }\n\n private async executeItem({ task, options }: TaskQueueItem): Promise<void> {\n try {\n await task();\n } finally {\n this.activeTasks--;\n\n // Mark the group as inactive after task completion\n if (options && options.taskGroupId) {\n this.activeGroups.delete(options.taskGroupId);\n }\n\n this.ensureProcessing();\n }\n }\n\n private takeFirstAvailableItem = (): TaskQueueItem | null => {\n const taskIndex = this.queue.findIndex(\n (item) =>\n !item.options?.taskGroupId ||\n !this.activeGroups.has(item.options.taskGroupId),\n );\n\n if (taskIndex === -1) {\n // All remaining tasks are blocked by active groups\n return null;\n }\n\n // Remove the task from the queue\n const [item] = this.queue.splice(taskIndex, 1);\n\n return item ?? null;\n };\n\n private hasItemsToProcess = (): boolean =>\n this.queue.findIndex(\n (item) =>\n !item.options?.taskGroupId ||\n !this.activeGroups.has(item.options.taskGroupId),\n ) !== -1;\n}\n\nconst DEFAULT_PROMISE_DEADLINE = 2147483647;\n\nconst promiseWithDeadline = <T>(\n executor: (\n resolve: (value: T | PromiseLike<T>) => void,\n reject: (reason?: unknown) => void,\n ) => void,\n options: { deadline?: number },\n) => {\n return new Promise<T>((resolve, reject) => {\n let taskStarted = false;\n\n const maxWaitingTime = options.deadline || DEFAULT_PROMISE_DEADLINE;\n\n let timeoutId: NodeJS.Timeout | null = setTimeout(() => {\n if (!taskStarted) {\n reject(\n new Error('Task was not started within the maximum waiting time'),\n );\n }\n }, maxWaitingTime);\n\n executor((value) => {\n taskStarted = true;\n if (timeoutId) {\n clearTimeout(timeoutId);\n }\n timeoutId = null;\n resolve(value);\n }, reject);\n });\n};\n","import { TaskProcessor } from '../../taskProcessing';\n\nexport type LockOptions = { lockId: number };\n\nexport type AcquireLockOptions = { lockId: string };\nexport type ReleaseLockOptions = { lockId: string };\n\nexport type Lock = {\n acquire(options: AcquireLockOptions): Promise<void>;\n tryAcquire(options: AcquireLockOptions): Promise<boolean>;\n release(options: ReleaseLockOptions): Promise<boolean>;\n withAcquire: <Result = unknown>(\n handle: () => Promise<Result>,\n options: AcquireLockOptions,\n ) => Promise<Result>;\n};\n\nexport const InProcessLock = (): Lock => {\n const taskProcessor = new TaskProcessor({\n maxActiveTasks: Number.MAX_VALUE,\n maxQueueSize: Number.MAX_VALUE,\n });\n\n // Map to store ack functions of currently held locks: lockId -> ack()\n const locks = new Map<string, () => void>();\n\n return {\n async acquire({ lockId }: AcquireLockOptions): Promise<void> {\n // If the lock is already held, we just queue up another task in the same group.\n // TaskProcessor ensures tasks in the same group run one at a time.\n await new Promise<void>((resolve, reject) => {\n taskProcessor\n .enqueue(\n ({ ack }) => {\n // When this task starts, it means the previous lock (if any) was released\n // and now we have exclusive access.\n locks.set(lockId, ack);\n // We do NOT call ack() here. We hold onto the lock.\n resolve();\n return Promise.resolve();\n },\n { taskGroupId: lockId },\n )\n .catch(reject);\n });\n },\n\n async tryAcquire({ lockId }: AcquireLockOptions): Promise<boolean> {\n // If lock is already held, fail immediately\n if (locks.has(lockId)) {\n return false;\n }\n\n // TODO: Check pending queue\n await this.acquire({ lockId });\n\n return true;\n },\n\n release({ lockId }: ReleaseLockOptions): Promise<boolean> {\n const ack = locks.get(lockId);\n if (ack === undefined) {\n return Promise.resolve(true);\n }\n locks.delete(lockId);\n ack();\n return Promise.resolve(true);\n },\n\n async withAcquire<Result = unknown>(\n handle: () => Promise<Result>,\n { lockId }: AcquireLockOptions,\n ): Promise<Result> {\n return taskProcessor.enqueue(\n async ({ ack }) => {\n // When this task starts, it means the previous lock (if any) was released\n // and now we have exclusive access.\n locks.set(lockId, ack);\n\n // We do NOT call ack() here. We hold onto the lock.\n try {\n return await handle();\n } finally {\n locks.delete(lockId);\n ack();\n }\n },\n { taskGroupId: lockId },\n );\n },\n };\n};\n","const textEncoder = new TextEncoder();\n\nexport const hashText = async (text: string): Promise<bigint> => {\n const hashBuffer = await crypto.subtle.digest(\n 'SHA-256',\n textEncoder.encode(text),\n );\n // Create an array with a single element that is a 64-bit signed integer\n // We take the first 8 bytes (so 64 bits) of the SHA-256 hash\n const view = new BigInt64Array(hashBuffer, 0, 1);\n return view[0]!;\n};\n","import { InProcessLock } from '@event-driven-io/emmett';\nimport { EventStoreDBClient } from '@eventstore/db-client';\nimport {\n AbstractStartedContainer,\n GenericContainer,\n Wait,\n type StartedTestContainer,\n} from 'testcontainers';\nimport type { Environment } from 'testcontainers/build/types';\n\nexport const EVENTSTOREDB_PORT = 2113;\nexport const EVENTSTOREDB_IMAGE_NAME = 'eventstore/eventstore';\nexport const EVENTSTOREDB_IMAGE_TAG = '24.10.0-bookworm-slim';\nexport const EVENTSTOREDB_ARM64_IMAGE_TAG = '24.10.0-alpha-arm64v8';\n\nexport const EVENTSTOREDB_DEFAULT_IMAGE = `${EVENTSTOREDB_IMAGE_NAME}:${process.arch !== 'arm64' ? EVENTSTOREDB_IMAGE_TAG : EVENTSTOREDB_ARM64_IMAGE_TAG}`;\n\nexport type EventStoreDBContainerOptions = {\n disableProjections?: boolean;\n isSecure?: boolean;\n useFileStorage?: boolean;\n withReuse?: boolean;\n};\n\nexport const defaultEventStoreDBContainerOptions: EventStoreDBContainerOptions =\n {\n disableProjections: false,\n isSecure: false,\n useFileStorage: false,\n withReuse: false,\n };\n\nexport class EventStoreDBContainer extends GenericContainer {\n constructor(\n image = EVENTSTOREDB_DEFAULT_IMAGE,\n options: EventStoreDBContainerOptions = defaultEventStoreDBContainerOptions,\n ) {\n super(image);\n\n const environment: Environment = {\n ...(!options.disableProjections\n ? {\n EVENTSTORE_RUN_PROJECTIONS: 'ALL',\n }\n : {}),\n ...(!options.isSecure\n ? {\n EVENTSTORE_INSECURE: 'true',\n }\n : {}),\n ...(options.useFileStorage\n ? {\n EVENTSTORE_MEM_DB: 'false',\n EVENTSTORE_DB: '/data/integration-tests',\n }\n : {}),\n EVENTSTORE_CLUSTER_SIZE: '1',\n EVENTSTORE_START_STANDARD_PROJECTIONS: 'true',\n EVENTSTORE_NODE_PORT: `${EVENTSTOREDB_PORT}`,\n EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP: 'true',\n };\n\n this.withEnvironment(environment).withExposedPorts(EVENTSTOREDB_PORT);\n\n if (options.withReuse) this.withReuse();\n\n this.withWaitStrategy(\n Wait.forAll([Wait.forHealthCheck(), Wait.forListeningPorts()]),\n );\n }\n\n async start(): Promise<StartedEventStoreDBContainer> {\n return new StartedEventStoreDBContainer(await super.start());\n }\n}\n\nexport class StartedEventStoreDBContainer extends AbstractStartedContainer {\n constructor(container: StartedTestContainer) {\n super(container);\n }\n\n getConnectionString(): string {\n return `esdb://${this.getHost()}:${this.getMappedPort(2113)}?tls=false`;\n }\n\n getClient(): EventStoreDBClient {\n return EventStoreDBClient.connectionString(this.getConnectionString());\n }\n}\n\nlet container: EventStoreDBContainer | null = null;\nlet startedContainer: StartedEventStoreDBContainer | null = null;\nlet startedCount = 0;\nconst lock = InProcessLock();\n\nexport const getSharedEventStoreDBTestContainer = () =>\n lock.withAcquire(\n async () => {\n if (startedContainer) return startedContainer;\n\n if (!container)\n container = new EventStoreDBContainer(EVENTSTOREDB_DEFAULT_IMAGE);\n\n startedContainer = await container.start();\n startedCount++;\n\n container.withLogConsumer((stream) =>\n stream\n .on('data', (line) => console.log(line))\n .on('err', (line) => console.error(line))\n .on('end', () => console.log('Stream closed')),\n );\n\n return startedContainer;\n },\n { lockId: 'SharedEventStoreDBTestContainer' },\n );\n\nexport const getSharedTestEventStoreDBClient = async () => {\n return (await getSharedEventStoreDBTestContainer()).getClient();\n};\n\nexport const releaseSharedEventStoreDBTestContainer = () =>\n lock.withAcquire(\n async () => {\n const containerToStop = startedContainer;\n if (containerToStop && --startedCount === 0) {\n try {\n startedContainer = null;\n container = null;\n await containerToStop.stop();\n } catch {\n /* do nothing */\n }\n }\n },\n { lockId: 'SharedEventStoreDBTestContainer' },\n );\n","import { MongoDBContainer } from '@testcontainers/mongodb';\n\nexport const getMongoDBContainer = (\n options: { version: string } = { version: '6.0.1' },\n) => {\n return new MongoDBContainer(`mongo:${options.version}`);\n};\n\nexport const getMongoDBStartedContainer = async (\n options: { version: string } = { version: '6.0.1' },\n) => {\n const container = getMongoDBContainer(options);\n return container.start();\n};\n","import { PostgreSqlContainer } from '@testcontainers/postgresql';\n\nexport const getPostgreSQLContainer = (\n options: { version: string } = { version: '18.1' },\n) => {\n return new PostgreSqlContainer(`postgres:${options.version}`);\n};\n\nexport const getPostgreSQLStartedContainer = async (\n options: { version: string } = { version: '18.1' },\n) => {\n const container = getPostgreSQLContainer(options);\n return container.start();\n};\n"]}
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/emmett/emmett/src/packages/emmett-testcontainers/dist/index.cjs","../src/eventStore/index.ts","../../emmett/src/validation/index.ts","../../emmett/src/errors/index.ts","../../emmett/src/eventStore/inMemoryEventStore.ts","../../emmett/src/database/inMemoryDatabase.ts","../../emmett/src/utils/retry.ts","../../emmett/src/eventStore/projections/inMemory/inMemoryProjectionSpec.ts","../../emmett/src/processors/processors.ts","../../emmett/src/typing/index.ts","../../emmett/src/taskProcessing/taskProcessor.ts","../../emmett/src/utils/locking/index.ts","../../emmett/src/utils/strings/hashText.ts","../src/eventStore/eventStoreDBContainer.ts","../src/mongodb/mongoDBContainer.ts","../src/postgresql/postgreSQLContainer.ts"],"names":["error","item","container","EventStoreDBClient"],"mappings":"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","file":"/home/runner/work/emmett/emmett/src/packages/emmett-testcontainers/dist/index.cjs","sourcesContent":[null,"import { EventStoreDBClient } from '@eventstore/db-client';\nimport type { StartedEventStoreDBContainer } from './eventStoreDBContainer';\nimport { EventStoreDBContainer } from './eventStoreDBContainer';\n\nexport * from './eventStoreDBContainer';\n\nlet esdbContainer: StartedEventStoreDBContainer;\n\nexport const getEventStoreDBTestClient = async (\n useTestContainers = false,\n): Promise<EventStoreDBClient> => {\n let connectionString;\n\n if (useTestContainers) {\n if (!esdbContainer)\n esdbContainer = await new EventStoreDBContainer().start();\n\n connectionString = esdbContainer.getConnectionString();\n } else {\n // await compose.upAll();\n connectionString = 'esdb://localhost:2113?tls=false';\n }\n\n // That's how EventStoreDB client is setup\n // We're taking the connection string from container\n return EventStoreDBClient.connectionString(connectionString);\n};\n","import { ValidationError } from '../errors';\n\nexport const enum ValidationErrors {\n NOT_A_NONEMPTY_STRING = 'NOT_A_NONEMPTY_STRING',\n NOT_A_POSITIVE_NUMBER = 'NOT_A_POSITIVE_NUMBER',\n NOT_AN_UNSIGNED_BIGINT = 'NOT_AN_UNSIGNED_BIGINT',\n}\n\nexport const isNumber = (val: unknown): val is number =>\n typeof val === 'number' && val === val;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const isBigint = (val: any): val is bigint =>\n typeof val === 'bigint' && val === val;\n\nexport const isString = (val: unknown): val is string =>\n typeof val === 'string';\n\nexport const assertNotEmptyString = (value: unknown): string => {\n if (!isString(value) || value.length === 0) {\n throw new ValidationError(ValidationErrors.NOT_A_NONEMPTY_STRING);\n }\n return value;\n};\n\nexport const assertPositiveNumber = (value: unknown): number => {\n if (!isNumber(value) || value <= 0) {\n throw new ValidationError(ValidationErrors.NOT_A_POSITIVE_NUMBER);\n }\n return value;\n};\n\nexport const assertUnsignedBigInt = (value: string): bigint => {\n const number = BigInt(value);\n if (number < 0) {\n throw new ValidationError(ValidationErrors.NOT_AN_UNSIGNED_BIGINT);\n }\n return number;\n};\n\nexport * from './dates';\n","import { isNumber, isString } from '../validation';\n\nexport type ErrorConstructor<ErrorType extends Error> = new (\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ...args: any[]\n) => ErrorType;\n\nexport const isErrorConstructor = <ErrorType extends Error>(\n // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n expect: Function,\n): expect is ErrorConstructor<ErrorType> => {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return (\n typeof expect === 'function' &&\n expect.prototype &&\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n expect.prototype.constructor === expect\n );\n};\n\nexport class EmmettError extends Error {\n public static readonly Codes = {\n ValidationError: 400,\n IllegalStateError: 403,\n NotFoundError: 404,\n ConcurrencyError: 412,\n InternalServerError: 500,\n };\n\n public errorCode: number;\n\n constructor(\n options?: { errorCode: number; message?: string } | string | number,\n ) {\n const errorCode =\n options && typeof options === 'object' && 'errorCode' in options\n ? options.errorCode\n : isNumber(options)\n ? options\n : EmmettError.Codes.InternalServerError;\n const message =\n options && typeof options === 'object' && 'message' in options\n ? options.message\n : isString(options)\n ? options\n : `Error with status code '${errorCode}' ocurred during Emmett processing`;\n\n super(message);\n this.errorCode = errorCode;\n\n // 👇️ because we are extending a built-in class\n Object.setPrototypeOf(this, EmmettError.prototype);\n }\n\n public static mapFrom(\n error: Error | { message?: string; errorCode?: number },\n ): EmmettError {\n if (EmmettError.isInstanceOf(error)) {\n return error;\n }\n\n return new EmmettError({\n errorCode:\n 'errorCode' in error &&\n error.errorCode !== undefined &&\n error.errorCode !== null\n ? error.errorCode\n : EmmettError.Codes.InternalServerError,\n message: error.message ?? 'An unknown error occurred',\n });\n }\n\n public static isInstanceOf<ErrorType extends EmmettError = EmmettError>(\n error: unknown,\n errorCode?: (typeof EmmettError.Codes)[keyof typeof EmmettError.Codes],\n ): error is ErrorType {\n return (\n typeof error === 'object' &&\n error !== null &&\n 'errorCode' in error &&\n isNumber(error.errorCode) &&\n (errorCode === undefined || error.errorCode === errorCode)\n );\n }\n}\n\nexport class ConcurrencyError extends EmmettError {\n constructor(\n public current: string | undefined,\n public expected: string,\n message?: string,\n ) {\n super({\n errorCode: EmmettError.Codes.ConcurrencyError,\n message:\n message ??\n `Expected version ${expected.toString()} does not match current ${current?.toString()}`,\n });\n\n // 👇️ because we are extending a built-in class\n Object.setPrototypeOf(this, ConcurrencyError.prototype);\n }\n}\n\n// TODO: Make it derive from ConcurrencyError to avoid code duplication\n// Or add additional type to distinguinsh both errors\nexport class ConcurrencyInMemoryDatabaseError extends EmmettError {\n constructor(message?: string) {\n super({\n errorCode: EmmettError.Codes.ConcurrencyError,\n message: message ?? `Expected document state does not match current one!`,\n });\n\n // 👇️ because we are extending a built-in class\n Object.setPrototypeOf(this, ConcurrencyInMemoryDatabaseError.prototype);\n }\n}\n\nexport class ValidationError extends EmmettError {\n constructor(message?: string) {\n super({\n errorCode: EmmettError.Codes.ValidationError,\n message: message ?? `Validation Error ocurred during Emmett processing`,\n });\n\n // 👇️ because we are extending a built-in class\n Object.setPrototypeOf(this, ValidationError.prototype);\n }\n}\n\nexport class IllegalStateError extends EmmettError {\n constructor(message?: string) {\n super({\n errorCode: EmmettError.Codes.IllegalStateError,\n message: message ?? `Illegal State ocurred during Emmett processing`,\n });\n\n // 👇️ because we are extending a built-in class\n Object.setPrototypeOf(this, IllegalStateError.prototype);\n }\n}\n\nexport class NotFoundError extends EmmettError {\n constructor(options?: { id: string; type: string; message?: string }) {\n super({\n errorCode: EmmettError.Codes.NotFoundError,\n message:\n options?.message ??\n (options?.id\n ? options.type\n ? `${options.type} with ${options.id} was not found during Emmett processing`\n : `State with ${options.id} was not found during Emmett processing`\n : options?.type\n ? `${options.type} was not found during Emmett processing`\n : 'State was not found during Emmett processing'),\n });\n\n // 👇️ because we are extending a built-in class\n Object.setPrototypeOf(this, NotFoundError.prototype);\n }\n}\n","import { v4 as uuid } from 'uuid';\nimport {\n getInMemoryDatabase,\n type InMemoryDatabase,\n} from '../database/inMemoryDatabase';\nimport type { ProjectionRegistration } from '../projections';\nimport type {\n BigIntStreamPosition,\n CombinedReadEventMetadata,\n Event,\n ReadEvent,\n ReadEventMetadataWithGlobalPosition,\n} from '../typing';\nimport { tryPublishMessagesAfterCommit } from './afterCommit';\nimport type {\n AggregateStreamOptions,\n AggregateStreamResult,\n AppendToStreamOptions,\n AppendToStreamResult,\n DefaultEventStoreOptions,\n EventStore,\n ReadStreamOptions,\n ReadStreamResult,\n StreamExistsResult,\n} from './eventStore';\nimport { assertExpectedVersionMatchesCurrent } from './expectedVersion';\nimport { handleInMemoryProjections } from './projections/inMemory';\nimport { downcastRecordedMessages, upcastRecordedMessages } from './versioning';\n\nexport const InMemoryEventStoreDefaultStreamVersion = 0n;\n\nexport type InMemoryEventStore =\n EventStore<ReadEventMetadataWithGlobalPosition> & {\n database: InMemoryDatabase;\n };\n\nexport type InMemoryReadEventMetadata = ReadEventMetadataWithGlobalPosition;\n\nexport type InMemoryProjectionHandlerContext = {\n eventStore?: InMemoryEventStore;\n database?: InMemoryDatabase;\n};\n\nexport type InMemoryEventStoreOptions =\n DefaultEventStoreOptions<InMemoryEventStore> & {\n projections?: ProjectionRegistration<\n 'inline',\n InMemoryReadEventMetadata,\n InMemoryProjectionHandlerContext\n >[];\n database?: InMemoryDatabase;\n };\n\nexport type InMemoryReadEvent<EventType extends Event = Event> = ReadEvent<\n EventType,\n ReadEventMetadataWithGlobalPosition\n>;\n\nexport const getInMemoryEventStore = (\n eventStoreOptions?: InMemoryEventStoreOptions,\n): InMemoryEventStore => {\n const streams = new Map<\n string,\n ReadEvent<Event, ReadEventMetadataWithGlobalPosition>[]\n >();\n\n const getAllEventsCount = () => {\n return Array.from<ReadEvent[]>(streams.values())\n .map((s) => s.length)\n .reduce((p, c) => p + c, 0);\n };\n\n // Get the database instance to be used for projections\n const database = eventStoreOptions?.database || getInMemoryDatabase();\n\n // Extract inline projections from options\n const inlineProjections = (eventStoreOptions?.projections ?? [])\n .filter(({ type }) => type === 'inline')\n .map(({ projection }) => projection);\n\n // Create the event store object\n const eventStore: InMemoryEventStore = {\n database,\n async aggregateStream<\n State,\n EventType extends Event,\n EventPayloadType extends Event = EventType,\n >(\n streamName: string,\n options: AggregateStreamOptions<\n State,\n EventType,\n ReadEventMetadataWithGlobalPosition,\n EventPayloadType\n >,\n ): Promise<AggregateStreamResult<State>> {\n const { evolve, initialState, read } = options;\n\n const result = await this.readStream<EventType, EventPayloadType>(\n streamName,\n read,\n );\n\n const events = result?.events ?? [];\n\n const state = events.reduce((s, e) => evolve(s, e), initialState());\n\n return {\n currentStreamVersion: BigInt(events.length),\n state,\n streamExists: result.streamExists,\n };\n },\n\n readStream: <\n EventType extends Event,\n EventPayloadType extends Event = EventType,\n >(\n streamName: string,\n options?: ReadStreamOptions<\n BigIntStreamPosition,\n EventType,\n EventPayloadType\n >,\n ): Promise<\n ReadStreamResult<EventType, ReadEventMetadataWithGlobalPosition>\n > => {\n const events = streams.get(streamName);\n const currentStreamVersion = events\n ? BigInt(events.length)\n : InMemoryEventStoreDefaultStreamVersion;\n\n assertExpectedVersionMatchesCurrent(\n currentStreamVersion,\n options?.expectedStreamVersion,\n InMemoryEventStoreDefaultStreamVersion,\n );\n\n const from = Number(options?.from ?? 0);\n const to = Number(\n options?.to ??\n (options?.maxCount\n ? (options.from ?? 0n) + options.maxCount\n : (events?.length ?? 1)),\n );\n\n const resultEvents =\n events !== undefined && events.length > 0\n ? upcastRecordedMessages<\n EventType,\n EventPayloadType,\n ReadEventMetadataWithGlobalPosition\n >(\n events.slice(from, to) as ReadEvent<\n EventPayloadType,\n ReadEventMetadataWithGlobalPosition\n >[],\n options?.schema?.versioning,\n )\n : [];\n\n const result: ReadStreamResult<\n EventType,\n ReadEventMetadataWithGlobalPosition\n > = {\n currentStreamVersion,\n events: resultEvents,\n streamExists: events !== undefined && events.length > 0,\n };\n\n return Promise.resolve(result);\n },\n\n appendToStream: async <\n EventType extends Event,\n EventPayloadType extends Event = EventType,\n >(\n streamName: string,\n events: EventType[],\n options?: AppendToStreamOptions<\n BigIntStreamPosition,\n EventType,\n EventPayloadType\n >,\n ): Promise<AppendToStreamResult> => {\n const currentEvents = streams.get(streamName) ?? [];\n const currentStreamVersion =\n currentEvents.length > 0\n ? BigInt(currentEvents.length)\n : InMemoryEventStoreDefaultStreamVersion;\n\n assertExpectedVersionMatchesCurrent(\n currentStreamVersion,\n options?.expectedStreamVersion,\n InMemoryEventStoreDefaultStreamVersion,\n );\n\n const newEvents: ReadEvent<\n EventType,\n ReadEventMetadataWithGlobalPosition\n >[] = events.map((event, index) => {\n const metadata: ReadEventMetadataWithGlobalPosition = {\n streamName,\n messageId: uuid(),\n streamPosition: BigInt(currentEvents.length + index + 1),\n globalPosition: BigInt(getAllEventsCount() + index + 1),\n };\n return {\n ...event,\n kind: event.kind ?? 'Event',\n metadata: {\n ...('metadata' in event ? (event.metadata ?? {}) : {}),\n ...metadata,\n } as CombinedReadEventMetadata<\n EventType,\n ReadEventMetadataWithGlobalPosition\n >,\n };\n });\n\n const positionOfLastEventInTheStream = BigInt(\n newEvents.slice(-1)[0]!.metadata.streamPosition,\n );\n\n streams.set(streamName, [\n ...currentEvents,\n ...downcastRecordedMessages(newEvents, options?.schema?.versioning),\n ]);\n\n // Process projections if there are any registered\n if (inlineProjections.length > 0) {\n await handleInMemoryProjections({\n projections: inlineProjections,\n events: newEvents,\n database: eventStore.database,\n eventStore,\n });\n }\n\n const result: AppendToStreamResult = {\n nextExpectedStreamVersion: positionOfLastEventInTheStream,\n createdNewStream:\n currentStreamVersion === InMemoryEventStoreDefaultStreamVersion,\n };\n\n await tryPublishMessagesAfterCommit<InMemoryEventStore>(\n newEvents,\n eventStoreOptions?.hooks,\n );\n\n return result;\n },\n\n streamExists: (streamName): Promise<StreamExistsResult> => {\n const events = streams.get(streamName);\n\n return Promise.resolve(events !== undefined && events.length > 0);\n },\n };\n\n return eventStore;\n};\n","import { v7 as uuid } from 'uuid';\nimport { deepEquals } from '../utils';\nimport type {\n DatabaseHandleOptionErrors,\n DatabaseHandleOptions,\n DatabaseHandleResult,\n DeleteResult,\n Document,\n DocumentHandler,\n InsertOneResult,\n OptionalUnlessRequiredIdAndVersion,\n ReplaceOneOptions,\n UpdateResult,\n WithIdAndVersion,\n WithoutId,\n} from './types';\nimport { expectedVersionValue, operationResult } from './utils';\n\nexport interface InMemoryDocumentsCollection<T extends Document> {\n handle: (\n id: string,\n handle: DocumentHandler<T>,\n options?: DatabaseHandleOptions,\n ) => Promise<DatabaseHandleResult<T>>;\n findOne: (predicate?: Predicate<T>) => Promise<T | null>;\n find: (predicate?: Predicate<T>) => Promise<T[]>;\n insertOne: (\n document: OptionalUnlessRequiredIdAndVersion<T>,\n ) => Promise<InsertOneResult>;\n deleteOne: (predicate?: Predicate<T>) => Promise<DeleteResult>;\n replaceOne: (\n predicate: Predicate<T>,\n document: WithoutId<T>,\n options?: ReplaceOneOptions,\n ) => Promise<UpdateResult>;\n}\n\nexport interface InMemoryDatabase {\n collection: <T extends Document>(\n name: string,\n ) => InMemoryDocumentsCollection<T>;\n}\n\ntype Predicate<T> = (item: T) => boolean;\ntype CollectionName = string;\n\nexport const getInMemoryDatabase = (): InMemoryDatabase => {\n const storage = new Map<CollectionName, WithIdAndVersion<Document>[]>();\n\n return {\n collection: <T extends Document, CollectionName extends string>(\n collectionName: CollectionName,\n collectionOptions: {\n errors?: DatabaseHandleOptionErrors;\n } = {},\n ): InMemoryDocumentsCollection<T> => {\n const ensureCollectionCreated = () => {\n if (!storage.has(collectionName)) storage.set(collectionName, []);\n };\n\n const errors = collectionOptions.errors;\n\n const collection = {\n collectionName,\n insertOne: async (\n document: OptionalUnlessRequiredIdAndVersion<T>,\n ): Promise<InsertOneResult> => {\n ensureCollectionCreated();\n\n const _id = (document._id as string | undefined | null) ?? uuid();\n const _version = document._version ?? 1n;\n\n const existing = await collection.findOne((c) => c._id === _id);\n\n if (existing) {\n return operationResult<InsertOneResult>(\n {\n successful: false,\n insertedId: null,\n nextExpectedVersion: _version,\n },\n { operationName: 'insertOne', collectionName, errors },\n );\n }\n\n const documentsInCollection = storage.get(collectionName)!;\n const newDocument = { ...document, _id, _version };\n const newCollection = [...documentsInCollection, newDocument];\n storage.set(collectionName, newCollection);\n\n return operationResult<InsertOneResult>(\n {\n successful: true,\n insertedId: _id,\n nextExpectedVersion: _version,\n },\n { operationName: 'insertOne', collectionName, errors },\n );\n },\n findOne: (predicate?: Predicate<T>): Promise<T | null> => {\n ensureCollectionCreated();\n\n const documentsInCollection = storage.get(collectionName);\n const filteredDocuments = predicate\n ? documentsInCollection?.filter((doc) => predicate(doc as T))\n : documentsInCollection;\n\n const firstOne = filteredDocuments?.[0] ?? null;\n\n return Promise.resolve(firstOne as T | null);\n },\n find: (predicate?: Predicate<T>): Promise<T[]> => {\n ensureCollectionCreated();\n\n const documentsInCollection = storage.get(collectionName);\n const filteredDocuments = predicate\n ? documentsInCollection?.filter((doc) => predicate(doc as T))\n : documentsInCollection;\n\n return Promise.resolve(filteredDocuments as T[]);\n },\n deleteOne: (predicate?: Predicate<T>): Promise<DeleteResult> => {\n ensureCollectionCreated();\n\n const documentsInCollection = storage.get(collectionName)!;\n\n if (predicate) {\n const foundIndex = documentsInCollection.findIndex((doc) =>\n predicate(doc as T),\n );\n\n if (foundIndex === -1) {\n return Promise.resolve(\n operationResult<DeleteResult>(\n {\n successful: false,\n matchedCount: 0,\n deletedCount: 0,\n },\n { operationName: 'deleteOne', collectionName, errors },\n ),\n );\n } else {\n const newCollection = documentsInCollection.toSpliced(\n foundIndex,\n 1,\n );\n\n storage.set(collectionName, newCollection);\n\n return Promise.resolve(\n operationResult<DeleteResult>(\n {\n successful: true,\n matchedCount: 1,\n deletedCount: 1,\n },\n { operationName: 'deleteOne', collectionName, errors },\n ),\n );\n }\n }\n\n const newCollection = documentsInCollection.slice(1);\n\n storage.set(collectionName, newCollection);\n\n return Promise.resolve(\n operationResult<DeleteResult>(\n {\n successful: true,\n matchedCount: 1,\n deletedCount: 1,\n },\n { operationName: 'deleteOne', collectionName, errors },\n ),\n );\n },\n replaceOne: (\n predicate: Predicate<T>,\n document: WithoutId<T>,\n options?: ReplaceOneOptions,\n ): Promise<UpdateResult> => {\n ensureCollectionCreated();\n\n const documentsInCollection = storage.get(collectionName)!;\n\n const firstIndex = documentsInCollection.findIndex((doc) =>\n predicate(doc as T),\n );\n\n if (firstIndex === undefined || firstIndex === -1) {\n return Promise.resolve(\n operationResult<UpdateResult>(\n {\n successful: false,\n matchedCount: 0,\n modifiedCount: 0,\n nextExpectedVersion: 0n,\n },\n { operationName: 'replaceOne', collectionName, errors },\n ),\n );\n }\n\n const existing = documentsInCollection[firstIndex]!;\n\n if (\n typeof options?.expectedVersion === 'bigint' &&\n existing._version !== options.expectedVersion\n ) {\n return Promise.resolve(\n operationResult<UpdateResult>(\n {\n successful: false,\n matchedCount: 1,\n modifiedCount: 0,\n nextExpectedVersion: existing._version,\n },\n { operationName: 'replaceOne', collectionName, errors },\n ),\n );\n }\n\n const newVersion = existing._version + 1n;\n\n const newCollection = documentsInCollection.with(firstIndex, {\n _id: existing._id,\n ...document,\n _version: newVersion,\n });\n\n storage.set(collectionName, newCollection);\n\n return Promise.resolve(\n operationResult<UpdateResult>(\n {\n successful: true,\n modifiedCount: 1,\n matchedCount: firstIndex,\n nextExpectedVersion: newVersion,\n },\n { operationName: 'replaceOne', collectionName, errors },\n ),\n );\n },\n handle: async (\n id: string,\n handle: DocumentHandler<T>,\n options?: DatabaseHandleOptions,\n ): Promise<DatabaseHandleResult<T>> => {\n const { expectedVersion: version, ...operationOptions } =\n options ?? {};\n ensureCollectionCreated();\n const existing = await collection.findOne(({ _id }) => _id === id);\n\n const expectedVersion = expectedVersionValue(version);\n\n if (\n (existing == null && version === 'DOCUMENT_EXISTS') ||\n (existing == null && expectedVersion != null) ||\n (existing != null && version === 'DOCUMENT_DOES_NOT_EXIST') ||\n (existing != null &&\n expectedVersion !== null &&\n existing._version !== expectedVersion)\n ) {\n return operationResult<DatabaseHandleResult<T>>(\n {\n successful: false,\n document: existing as WithIdAndVersion<T>,\n },\n { operationName: 'handle', collectionName, errors },\n );\n }\n\n const result = handle(existing !== null ? { ...existing } : null);\n\n if (deepEquals(existing, result))\n return operationResult<DatabaseHandleResult<T>>(\n {\n successful: true,\n document: existing as WithIdAndVersion<T>,\n },\n { operationName: 'handle', collectionName, errors },\n );\n\n if (!existing && result) {\n const newDoc = { ...result, _id: id };\n const insertResult = await collection.insertOne({\n ...newDoc,\n _id: id,\n } as OptionalUnlessRequiredIdAndVersion<T>);\n return {\n ...insertResult,\n document: {\n ...newDoc,\n _version: insertResult.nextExpectedVersion,\n } as unknown as WithIdAndVersion<T>,\n };\n }\n\n if (existing && !result) {\n const deleteResult = await collection.deleteOne(\n ({ _id }) => id === _id,\n );\n return { ...deleteResult, document: null };\n }\n\n if (existing && result) {\n const replaceResult = await collection.replaceOne(\n ({ _id }) => id === _id,\n result,\n {\n ...operationOptions,\n expectedVersion: expectedVersion ?? 'DOCUMENT_EXISTS',\n },\n );\n return {\n ...replaceResult,\n document: {\n ...result,\n _version: replaceResult.nextExpectedVersion,\n } as unknown as WithIdAndVersion<T>,\n };\n }\n\n return operationResult<DatabaseHandleResult<T>>(\n {\n successful: true,\n document: existing as WithIdAndVersion<T>,\n },\n { operationName: 'handle', collectionName, errors },\n );\n },\n };\n\n return collection;\n },\n };\n};\n","import retry from 'async-retry';\nimport { EmmettError } from '../errors';\nimport { JSONParser } from '../serialization';\n\nexport type AsyncRetryOptions<T = unknown> = retry.Options & {\n shouldRetryResult?: (result: T) => boolean;\n shouldRetryError?: (error?: unknown) => boolean;\n};\n\nexport const NoRetries: AsyncRetryOptions = { retries: 0 };\n\nexport const asyncRetry = async <T>(\n fn: () => Promise<T>,\n opts?: AsyncRetryOptions<T>,\n): Promise<T> => {\n if (opts === undefined || opts.retries === 0) return fn();\n\n return retry(\n async (bail) => {\n try {\n const result = await fn();\n\n if (opts?.shouldRetryResult && opts.shouldRetryResult(result)) {\n throw new EmmettError(\n `Retrying because of result: ${JSONParser.stringify(result)}`,\n );\n }\n return result;\n } catch (error) {\n if (opts?.shouldRetryError && !opts.shouldRetryError(error)) {\n bail(error as Error);\n return undefined as unknown as T;\n }\n throw error;\n }\n },\n opts ?? { retries: 0 },\n );\n};\n","import { v4 as uuid } from 'uuid';\nimport {\n handleInMemoryProjections,\n type InMemoryProjectionDefinition,\n} from '.';\nimport {\n getInMemoryDatabase,\n type Document,\n type InMemoryDatabase,\n} from '../../../database';\nimport { isErrorConstructor } from '../../../errors';\nimport { JSONParser } from '../../../serialization';\nimport {\n assertFails,\n AssertionError,\n assertTrue,\n type ThenThrows,\n} from '../../../testing';\nimport type { CombinedReadEventMetadata, ReadEvent } from '../../../typing';\nimport type { Event } from '../../../typing';\nimport type {\n InMemoryEventStore,\n InMemoryReadEventMetadata,\n} from '../../inMemoryEventStore';\n\n// Define a more specific type for T that extends Document\ntype DocumentWithId = Document & { _id?: string | number };\n\nexport type InMemoryProjectionSpecEvent<\n EventType extends Event,\n EventMetaDataType extends InMemoryReadEventMetadata =\n InMemoryReadEventMetadata,\n> = EventType & {\n metadata?: Partial<EventMetaDataType>;\n};\n\nexport type InMemoryProjectionSpecWhenOptions = { numberOfTimes: number };\n\nexport type InMemoryProjectionSpec<EventType extends Event> = (\n givenEvents: InMemoryProjectionSpecEvent<EventType>[],\n) => {\n when: (\n events: InMemoryProjectionSpecEvent<EventType>[],\n options?: InMemoryProjectionSpecWhenOptions,\n ) => {\n then: (assert: InMemoryProjectionAssert, message?: string) => Promise<void>;\n thenThrows: <ErrorType extends Error = Error>(\n ...args: Parameters<ThenThrows<ErrorType>>\n ) => Promise<void>;\n };\n};\n\nexport type InMemoryProjectionAssert = (options: {\n database: InMemoryDatabase;\n}) => Promise<void | boolean>;\n\nexport type InMemoryProjectionSpecOptions<EventType extends Event> = {\n projection: InMemoryProjectionDefinition<EventType>;\n};\n\nexport const InMemoryProjectionSpec = {\n for: <EventType extends Event>(\n options: InMemoryProjectionSpecOptions<EventType>,\n ): InMemoryProjectionSpec<EventType> => {\n const { projection } = options;\n\n return (givenEvents: InMemoryProjectionSpecEvent<EventType>[]) => {\n return {\n when: (\n events: InMemoryProjectionSpecEvent<EventType>[],\n options?: InMemoryProjectionSpecWhenOptions,\n ) => {\n const allEvents: ReadEvent<EventType, InMemoryReadEventMetadata>[] =\n [];\n\n const run = async (database: InMemoryDatabase) => {\n let globalPosition = 0n;\n const numberOfTimes = options?.numberOfTimes ?? 1;\n\n for (const event of [\n ...givenEvents,\n ...Array.from({ length: numberOfTimes }).flatMap(() => events),\n ]) {\n const metadata: InMemoryReadEventMetadata = {\n globalPosition: ++globalPosition,\n streamPosition: globalPosition,\n streamName: event.metadata?.streamName ?? `test-${uuid()}`,\n messageId: uuid(),\n };\n\n allEvents.push({\n ...event,\n kind: 'Event',\n metadata: {\n ...metadata,\n ...('metadata' in event ? (event.metadata ?? {}) : {}),\n } as CombinedReadEventMetadata<\n EventType,\n InMemoryReadEventMetadata\n >,\n });\n }\n\n // Create a minimal mock EventStore implementation\n const mockEventStore = {\n database,\n aggregateStream: async () => {\n return Promise.resolve({\n state: {},\n currentStreamVersion: 0n,\n streamExists: false,\n });\n },\n readStream: async () => {\n return Promise.resolve({\n events: [],\n currentStreamVersion: 0n,\n streamExists: false,\n });\n },\n appendToStream: async () => {\n return Promise.resolve({\n nextExpectedStreamVersion: 0n,\n createdNewStream: false,\n });\n },\n streamExists: async () => {\n return Promise.resolve(false);\n },\n } as InMemoryEventStore;\n\n await handleInMemoryProjections({\n events: allEvents,\n projections: [projection],\n database,\n eventStore: mockEventStore,\n });\n };\n\n return {\n then: async (\n assertFn: InMemoryProjectionAssert,\n message?: string,\n ): Promise<void> => {\n const database = getInMemoryDatabase();\n await run(database);\n\n const succeeded = await assertFn({ database });\n\n if (succeeded !== undefined && succeeded === false) {\n assertFails(\n message ??\n \"Projection specification didn't match the criteria\",\n );\n }\n },\n thenThrows: async <ErrorType extends Error = Error>(\n ...args: Parameters<ThenThrows<ErrorType>>\n ): Promise<void> => {\n const database = getInMemoryDatabase();\n try {\n await run(database);\n throw new AssertionError('Handler did not fail as expected');\n } catch (error) {\n if (error instanceof AssertionError) throw error;\n\n if (args.length === 0) return;\n\n if (!isErrorConstructor(args[0])) {\n assertTrue(\n args[0](error as ErrorType),\n `Error didn't match the error condition: ${error?.toString()}`,\n );\n return;\n }\n\n assertTrue(\n error instanceof args[0],\n `Caught error is not an instance of the expected type: ${error?.toString()}`,\n );\n\n if (args[1]) {\n assertTrue(\n args[1](error as ErrorType),\n `Error didn't match the error condition: ${error?.toString()}`,\n );\n }\n }\n },\n };\n },\n };\n };\n },\n};\n\n// Helper functions for creating events in stream\nexport const eventInStream = <\n EventType extends Event = Event,\n EventMetaDataType extends InMemoryReadEventMetadata =\n InMemoryReadEventMetadata,\n>(\n streamName: string,\n event: InMemoryProjectionSpecEvent<EventType, EventMetaDataType>,\n): InMemoryProjectionSpecEvent<EventType, EventMetaDataType> => {\n return {\n ...event,\n metadata: {\n ...(event.metadata ?? {}),\n streamName: event.metadata?.streamName ?? streamName,\n } as Partial<EventMetaDataType>,\n };\n};\n\nexport const eventsInStream = <\n EventType extends Event = Event,\n EventMetaDataType extends InMemoryReadEventMetadata =\n InMemoryReadEventMetadata,\n>(\n streamName: string,\n events: InMemoryProjectionSpecEvent<EventType, EventMetaDataType>[],\n): InMemoryProjectionSpecEvent<EventType, EventMetaDataType>[] => {\n return events.map((e) => eventInStream(streamName, e));\n};\n\nexport const newEventsInStream = eventsInStream;\n\n// Assertion helpers for checking documents\nexport function documentExists<T extends DocumentWithId>(\n expected: Partial<T>,\n options: { inCollection: string; withId: string | number },\n): InMemoryProjectionAssert {\n return async ({ database }) => {\n const collection = database.collection<T>(options.inCollection);\n\n const document = await collection.findOne((doc) => {\n // Handle both string IDs and numeric IDs in a type-safe way\n const docId = '_id' in doc ? doc._id : undefined;\n return docId === options.withId;\n });\n\n if (!document) {\n assertFails(\n `Document with ID ${options.withId} does not exist in collection ${options.inCollection}`,\n );\n return Promise.resolve(false);\n }\n\n // Check that all expected properties exist with expected values\n for (const [key, value] of Object.entries(expected)) {\n const propKey = key as keyof typeof document;\n if (\n !(key in document) ||\n JSONParser.stringify(document[propKey]) !== JSONParser.stringify(value)\n ) {\n assertFails(`Property ${key} doesn't match the expected value`);\n return Promise.resolve(false);\n }\n }\n\n return Promise.resolve(true);\n };\n}\n\n// Helper for checking document contents\nexport const expectInMemoryDocuments = {\n fromCollection: <T extends DocumentWithId>(collectionName: string) => ({\n withId: (id: string | number) => ({\n toBeEqual: (expected: Partial<T>): InMemoryProjectionAssert =>\n documentExists<T>(expected, {\n inCollection: collectionName,\n withId: id,\n }),\n }),\n }),\n};\n","import { v7 as uuid } from 'uuid';\nimport type { EmmettError } from '../errors';\nimport { upcastRecordedMessage } from '../eventStore';\nimport type { ProjectionDefinition } from '../projections';\nimport {\n defaultTag,\n type AnyEvent,\n type AnyMessage,\n type AnyReadEventMetadata,\n type AnyRecordedMessageMetadata,\n type BatchRecordedMessageHandlerWithContext,\n type CanHandle,\n type DefaultRecord,\n type Event,\n type GlobalPositionTypeOfRecordedMessageMetadata,\n type Message,\n type MessageHandlerResult,\n type RecordedMessage,\n type SingleMessageHandlerWithContext,\n type SingleRecordedMessageHandlerWithContext,\n} from '../typing';\nimport { onShutdown } from '../utils/shutdown';\nimport { isBigint } from '../validation';\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type CurrentMessageProcessorPosition<CheckpointType = any> =\n | { lastCheckpoint: CheckpointType }\n | 'BEGINNING'\n | 'END';\n\nexport type GetCheckpoint<\n MessageType extends AnyMessage = AnyMessage,\n MessageMetadataType extends AnyReadEventMetadata = AnyReadEventMetadata,\n CheckpointType =\n GlobalPositionTypeOfRecordedMessageMetadata<MessageMetadataType>,\n> = (\n message: RecordedMessage<MessageType, MessageMetadataType>,\n) => CheckpointType | null;\n\nexport const getCheckpoint = <\n MessageType extends AnyMessage = AnyMessage,\n MessageMetadataType extends AnyReadEventMetadata = AnyReadEventMetadata,\n CheckpointType =\n GlobalPositionTypeOfRecordedMessageMetadata<MessageMetadataType>,\n>(\n message: RecordedMessage<MessageType, MessageMetadataType>,\n): CheckpointType | null => {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return 'checkpoint' in message.metadata\n ? // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n message.metadata.checkpoint\n : 'globalPosition' in message.metadata &&\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n isBigint(message.metadata.globalPosition)\n ? // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n message.metadata.globalPosition\n : 'streamPosition' in message.metadata &&\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n isBigint(message.metadata.streamPosition)\n ? // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n message.metadata.streamPosition\n : null;\n};\n\nexport const wasMessageHandled = <\n MessageType extends AnyMessage = AnyMessage,\n MessageMetadataType extends AnyReadEventMetadata = AnyReadEventMetadata,\n CheckpointType =\n GlobalPositionTypeOfRecordedMessageMetadata<MessageMetadataType>,\n>(\n message: RecordedMessage<MessageType, MessageMetadataType>,\n checkpoint: CheckpointType | null,\n): boolean => {\n //TODO Make it smarter\n const messageCheckpoint = getCheckpoint(message);\n const checkpointBigint = checkpoint as bigint | null;\n\n return (\n messageCheckpoint !== null &&\n messageCheckpoint !== undefined &&\n checkpointBigint !== null &&\n checkpointBigint !== undefined &&\n messageCheckpoint <= checkpointBigint\n );\n};\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type MessageProcessorStartFrom<CheckpointType = any> =\n | CurrentMessageProcessorPosition<CheckpointType>\n | 'CURRENT';\n\nexport type MessageProcessorType = 'projector' | 'reactor';\nexport const MessageProcessorType = {\n PROJECTOR: 'projector' as MessageProcessorType,\n REACTOR: 'reactor' as MessageProcessorType,\n};\n\nexport type MessageProcessor<\n MessageType extends AnyMessage = AnyMessage,\n MessageMetadataType extends AnyReadEventMetadata = AnyReadEventMetadata,\n HandlerContext extends DefaultRecord | undefined = undefined,\n CheckpointType =\n GlobalPositionTypeOfRecordedMessageMetadata<MessageMetadataType>,\n> = {\n id: string;\n instanceId: string;\n type: string;\n init: (options: Partial<HandlerContext>) => Promise<void>;\n start: (\n options: Partial<HandlerContext>,\n ) => Promise<CurrentMessageProcessorPosition<CheckpointType> | undefined>;\n close: (closeOptions: Partial<HandlerContext>) => Promise<void>;\n isActive: boolean;\n handle: BatchRecordedMessageHandlerWithContext<\n MessageType,\n MessageMetadataType,\n Partial<HandlerContext>\n >;\n};\n\nexport const MessageProcessor = {\n result: {\n skip: (options?: { reason?: string }): MessageHandlerResult => ({\n type: 'SKIP',\n ...(options ?? {}),\n }),\n stop: (options?: {\n reason?: string;\n error?: EmmettError;\n }): MessageHandlerResult => ({\n type: 'STOP',\n ...(options ?? {}),\n }),\n },\n};\n\nexport type MessageProcessingScope<\n HandlerContext extends DefaultRecord | undefined = undefined,\n> = <Result = MessageHandlerResult>(\n handler: (context: HandlerContext) => Result | Promise<Result>,\n partialContext: Partial<HandlerContext>,\n) => Result | Promise<Result>;\n\nexport type Checkpointer<\n MessageType extends AnyMessage = AnyMessage,\n MessageMetadataType extends AnyReadEventMetadata = AnyReadEventMetadata,\n HandlerContext extends DefaultRecord = DefaultRecord,\n CheckpointType =\n GlobalPositionTypeOfRecordedMessageMetadata<MessageMetadataType>,\n> = {\n read: ReadProcessorCheckpoint<CheckpointType, HandlerContext>;\n store: StoreProcessorCheckpoint<\n MessageType,\n MessageMetadataType,\n CheckpointType,\n HandlerContext\n >;\n};\n\nexport type ProcessorHooks<\n HandlerContext extends DefaultRecord = DefaultRecord,\n> = {\n onInit?: OnReactorInitHook<HandlerContext>;\n onStart?: OnReactorStartHook<HandlerContext>;\n onClose?: OnReactorCloseHook<HandlerContext>;\n};\n\nexport type BaseMessageProcessorOptions<\n MessageType extends AnyMessage = AnyMessage,\n MessageMetadataType extends AnyReadEventMetadata = AnyReadEventMetadata,\n HandlerContext extends DefaultRecord = DefaultRecord,\n CheckpointType =\n GlobalPositionTypeOfRecordedMessageMetadata<MessageMetadataType>,\n> = {\n type?: string;\n processorId: string;\n processorInstanceId?: string;\n version?: number;\n partition?: string;\n startFrom?: MessageProcessorStartFrom<CheckpointType>;\n stopAfter?: (\n message: RecordedMessage<MessageType, MessageMetadataType>,\n ) => boolean;\n processingScope?: MessageProcessingScope<HandlerContext>;\n checkpoints?: Checkpointer<\n MessageType,\n MessageMetadataType,\n HandlerContext,\n CheckpointType\n >;\n canHandle?: CanHandle<MessageType>;\n hooks?: ProcessorHooks<HandlerContext>;\n};\n\nexport type HandlerOptions<\n MessageType extends AnyMessage = AnyMessage,\n MessageMetadataType extends AnyReadEventMetadata = AnyReadEventMetadata,\n HandlerContext extends DefaultRecord = DefaultRecord,\n> =\n | {\n eachMessage: SingleRecordedMessageHandlerWithContext<\n MessageType,\n MessageMetadataType,\n HandlerContext\n >;\n eachBatch?: never;\n }\n | {\n eachMessage?: never;\n eachBatch: BatchRecordedMessageHandlerWithContext<\n MessageType,\n MessageMetadataType,\n HandlerContext\n >;\n };\n\nexport type OnReactorInitHook<\n HandlerContext extends DefaultRecord = DefaultRecord,\n> = (context: HandlerContext) => Promise<void>;\n\nexport type OnReactorStartHook<\n HandlerContext extends DefaultRecord = DefaultRecord,\n> = (context: HandlerContext) => Promise<void>;\n\nexport type OnReactorCloseHook<\n HandlerContext extends DefaultRecord = DefaultRecord,\n> = (context: HandlerContext) => Promise<void>;\n\nexport type ReactorOptions<\n MessageType extends AnyMessage = AnyMessage,\n MessageMetadataType extends AnyReadEventMetadata = AnyReadEventMetadata,\n HandlerContext extends DefaultRecord = DefaultRecord,\n CheckpointType =\n GlobalPositionTypeOfRecordedMessageMetadata<MessageMetadataType>,\n MessagePayloadType extends AnyMessage = MessageType,\n> = BaseMessageProcessorOptions<\n MessageType,\n MessageMetadataType,\n HandlerContext,\n CheckpointType\n> &\n HandlerOptions<MessageType, MessageMetadataType, HandlerContext> & {\n messageOptions?: {\n schema?: {\n versioning?: { upcast?: (event: MessagePayloadType) => MessageType };\n };\n };\n };\n\nexport type ProjectorOptions<\n EventType extends AnyEvent = AnyEvent,\n MessageMetadataType extends AnyReadEventMetadata = AnyReadEventMetadata,\n HandlerContext extends DefaultRecord = DefaultRecord,\n CheckpointType =\n GlobalPositionTypeOfRecordedMessageMetadata<MessageMetadataType>,\n EventPayloadType extends Event = EventType,\n> = Omit<\n BaseMessageProcessorOptions<\n EventType,\n MessageMetadataType,\n HandlerContext,\n CheckpointType\n >,\n 'type' | 'processorId'\n> & { processorId?: string } & {\n truncateOnStart?: boolean;\n projection: ProjectionDefinition<\n EventType,\n MessageMetadataType,\n HandlerContext,\n EventPayloadType\n >;\n};\n\nexport const defaultProcessingMessageProcessingScope = <\n HandlerContext = never,\n Result = MessageHandlerResult,\n>(\n handler: (context: HandlerContext) => Result | Promise<Result>,\n partialContext: Partial<HandlerContext>,\n) => handler(partialContext as HandlerContext);\n\nexport type ReadProcessorCheckpointResult<CheckpointType = unknown> = {\n lastCheckpoint: CheckpointType | null;\n};\n\nexport type ReadProcessorCheckpoint<\n CheckpointType = unknown,\n HandlerContext extends DefaultRecord = DefaultRecord,\n> = (\n options: { processorId: string; partition?: string },\n context: HandlerContext,\n) => Promise<ReadProcessorCheckpointResult<CheckpointType>>;\n\nexport type StoreProcessorCheckpointResult<CheckpointType = unknown> =\n | {\n success: true;\n newCheckpoint: CheckpointType;\n }\n | { success: false; reason: 'IGNORED' | 'MISMATCH' | 'CURRENT_AHEAD' };\n\nexport type StoreProcessorCheckpoint<\n MessageType extends Message = AnyMessage,\n MessageMetadataType extends AnyReadEventMetadata = AnyReadEventMetadata,\n CheckpointType = unknown,\n HandlerContext extends DefaultRecord | undefined = undefined,\n> =\n | ((\n options: {\n message: RecordedMessage<MessageType, MessageMetadataType>;\n processorId: string;\n version: number | undefined;\n lastCheckpoint: CheckpointType | null;\n partition?: string;\n },\n context: HandlerContext,\n ) => Promise<StoreProcessorCheckpointResult<CheckpointType | null>>)\n | ((\n options: {\n message: RecordedMessage<MessageType, MessageMetadataType>;\n processorId: string;\n version: number | undefined;\n lastCheckpoint: CheckpointType | null;\n partition?: string;\n },\n context: HandlerContext,\n ) => Promise<StoreProcessorCheckpointResult<CheckpointType>>);\n\nexport const defaultProcessorVersion = 1;\nexport const defaultProcessorPartition = defaultTag;\n\nexport const getProcessorInstanceId = (processorId: string): string =>\n `${processorId}:${uuid()}`;\n\nexport const getProjectorId = (options: { projectionName: string }): string =>\n `emt:processor:projector:${options.projectionName}`;\n\nexport const reactor = <\n MessageType extends Message = AnyMessage,\n MessageMetadataType extends AnyReadEventMetadata = AnyReadEventMetadata,\n HandlerContext extends DefaultRecord = DefaultRecord,\n CheckpointType =\n GlobalPositionTypeOfRecordedMessageMetadata<MessageMetadataType>,\n MessagePayloadType extends Message = MessageType,\n>(\n options: ReactorOptions<\n MessageType,\n MessageMetadataType,\n HandlerContext,\n CheckpointType,\n MessagePayloadType\n >,\n): MessageProcessor<\n MessageType,\n MessageMetadataType,\n HandlerContext,\n CheckpointType\n> => {\n const {\n checkpoints,\n processorId,\n processorInstanceId: instanceId = getProcessorInstanceId(processorId),\n type = MessageProcessorType.REACTOR,\n version = defaultProcessorVersion,\n partition = defaultProcessorPartition,\n hooks = {},\n processingScope = defaultProcessingMessageProcessingScope,\n startFrom,\n canHandle,\n stopAfter,\n } = options;\n\n const eachMessage: SingleMessageHandlerWithContext<\n MessageType,\n MessageMetadataType,\n HandlerContext\n > =\n 'eachMessage' in options && options.eachMessage\n ? options.eachMessage\n : () => Promise.resolve();\n\n let isInitiated = false;\n let isActive = false;\n\n let lastCheckpoint: CheckpointType | null = null;\n let closeSignal: (() => void) | null = null;\n\n const init = async (initOptions: Partial<HandlerContext>): Promise<void> => {\n if (isInitiated) return;\n\n if (hooks.onInit === undefined) {\n isInitiated = true;\n return;\n }\n\n return await processingScope(async (context) => {\n await hooks.onInit!(context);\n isInitiated = true;\n }, initOptions);\n };\n\n const close = async (\n closeOptions: Partial<HandlerContext>,\n ): Promise<void> => {\n // TODO: Align when active is set to false\n // if (!isActive) return;\n\n isActive = false;\n\n if (closeSignal) {\n closeSignal();\n closeSignal = null;\n }\n\n if (hooks.onClose) {\n await processingScope(hooks.onClose, closeOptions);\n }\n };\n\n return {\n // TODO: Consider whether not make it optional or add URN prefix\n id: processorId,\n instanceId,\n type,\n init,\n start: async (\n startOptions: Partial<HandlerContext>,\n ): Promise<CurrentMessageProcessorPosition<CheckpointType> | undefined> => {\n if (isActive) return;\n\n await init(startOptions);\n\n isActive = true;\n\n closeSignal = onShutdown(() => close({}));\n\n if (lastCheckpoint !== null)\n return {\n lastCheckpoint,\n };\n\n return await processingScope(async (context) => {\n if (hooks.onStart) {\n await hooks.onStart(context);\n }\n\n if (startFrom && startFrom !== 'CURRENT') return startFrom;\n\n if (checkpoints) {\n const readResult = await checkpoints?.read(\n {\n processorId: processorId,\n partition,\n },\n { ...startOptions, ...context },\n );\n lastCheckpoint = readResult.lastCheckpoint;\n }\n\n if (lastCheckpoint === null) return 'BEGINNING';\n\n return {\n lastCheckpoint,\n };\n }, startOptions);\n },\n close,\n get isActive() {\n return isActive;\n },\n handle: async (\n messages: RecordedMessage<MessageType, MessageMetadataType>[],\n partialContext: Partial<HandlerContext>,\n ): Promise<MessageHandlerResult> => {\n if (!isActive) return Promise.resolve();\n\n return await processingScope(async (context) => {\n let result: MessageHandlerResult = undefined;\n\n for (const message of messages) {\n if (wasMessageHandled(message, lastCheckpoint)) continue;\n\n const upcasted = upcastRecordedMessage(\n // TODO: Make it smarter\n message as unknown as RecordedMessage<\n MessagePayloadType,\n MessageMetadataType\n >,\n options.messageOptions?.schema?.versioning,\n );\n\n if (canHandle !== undefined && !canHandle.includes(upcasted.type))\n continue;\n\n const messageProcessingResult = await eachMessage(upcasted, context);\n\n if (checkpoints) {\n const storeCheckpointResult: StoreProcessorCheckpointResult<CheckpointType | null> =\n await checkpoints.store(\n {\n processorId,\n version,\n message: upcasted,\n lastCheckpoint,\n partition,\n },\n context,\n );\n\n if (storeCheckpointResult.success) {\n // TODO: Add correct handling of the storing checkpoint\n lastCheckpoint = storeCheckpointResult.newCheckpoint;\n }\n }\n\n if (\n messageProcessingResult &&\n messageProcessingResult.type === 'STOP'\n ) {\n isActive = false;\n result = messageProcessingResult;\n break;\n }\n\n if (stopAfter && stopAfter(upcasted)) {\n isActive = false;\n result = { type: 'STOP', reason: 'Stop condition reached' };\n break;\n }\n\n if (\n messageProcessingResult &&\n messageProcessingResult.type === 'SKIP'\n )\n continue;\n }\n\n return result;\n }, partialContext);\n },\n };\n};\n\nexport const projector = <\n EventType extends Event = Event,\n EventMetaDataType extends AnyRecordedMessageMetadata =\n AnyRecordedMessageMetadata,\n HandlerContext extends DefaultRecord = DefaultRecord,\n CheckpointType =\n GlobalPositionTypeOfRecordedMessageMetadata<EventMetaDataType>,\n EventPayloadType extends Event = EventType,\n>(\n options: ProjectorOptions<\n EventType,\n EventMetaDataType,\n HandlerContext,\n CheckpointType,\n EventPayloadType\n >,\n): MessageProcessor<\n EventType,\n EventMetaDataType,\n HandlerContext,\n CheckpointType\n> => {\n const {\n projection,\n processorId = getProjectorId({\n projectionName: projection.name ?? 'unknown',\n }),\n ...rest\n } = options;\n\n return reactor<\n EventType,\n EventMetaDataType,\n HandlerContext,\n CheckpointType,\n EventPayloadType\n >({\n ...rest,\n type: MessageProcessorType.PROJECTOR,\n canHandle: projection.canHandle,\n processorId,\n messageOptions: options.projection.eventsOptions,\n hooks: {\n onInit: options.hooks?.onInit,\n onStart:\n (options.truncateOnStart && options.projection.truncate) ||\n options.hooks?.onStart\n ? async (context: HandlerContext) => {\n if (options.truncateOnStart && options.projection.truncate)\n await options.projection.truncate(context);\n\n if (options.hooks?.onStart) await options.hooks?.onStart(context);\n }\n : undefined,\n onClose: options.hooks?.onClose,\n },\n eachMessage: async (\n event: RecordedMessage<EventType, EventMetaDataType>,\n context: HandlerContext,\n ) => projection.handle([event], context),\n });\n};\n","export * from './deepReadonly';\n\nexport * from './command';\nexport * from './event';\nexport * from './message';\nexport * from './messageHandling';\n\nexport * from './decider';\nexport * from './workflow';\n\nexport type Brand<K, T> = K & { readonly __brand: T };\nexport type Flavour<K, T> = K & { readonly __brand?: T };\n\nexport type DefaultRecord = Record<string, unknown>;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type AnyRecord = Record<string, any>;\n\nexport type NonNullable<T> = T extends null | undefined ? never : T;\n\nexport const emmettPrefix = 'emt';\n\nexport const globalTag = 'global';\nexport const defaultTag = `${emmettPrefix}:default`;\nexport const unknownTag = `${emmettPrefix}:unknown`;\n","import { EmmettError } from '../errors';\n\nexport type TaskQueue = TaskQueueItem[];\n\nexport type TaskQueueItem = {\n task: () => Promise<void>;\n options?: EnqueueTaskOptions;\n};\n\nexport type TaskProcessorOptions = {\n maxActiveTasks: number;\n maxQueueSize: number;\n maxTaskIdleTime?: number;\n};\n\nexport type Task<T> = (context: TaskContext) => Promise<T>;\n\nexport type TaskContext = {\n ack: () => void;\n};\n\nexport type EnqueueTaskOptions = { taskGroupId?: string };\n\nexport class TaskProcessor {\n private queue: TaskQueue = [];\n private isProcessing = false;\n private activeTasks = 0;\n private activeGroups: Set<string> = new Set();\n\n constructor(private options: TaskProcessorOptions) {}\n\n enqueue<T>(task: Task<T>, options?: EnqueueTaskOptions): Promise<T> {\n if (this.queue.length >= this.options.maxQueueSize) {\n return Promise.reject(\n new EmmettError(\n 'Too many pending connections. Please try again later.',\n ),\n );\n }\n\n return this.schedule(task, options);\n }\n\n waitForEndOfProcessing(): Promise<void> {\n return this.schedule(({ ack }) => Promise.resolve(ack()));\n }\n\n private schedule<T>(task: Task<T>, options?: EnqueueTaskOptions): Promise<T> {\n return promiseWithDeadline(\n (resolve, reject) => {\n const taskWithContext = () => {\n return new Promise<void>((resolveTask, failTask) => {\n const taskPromise = task({\n ack: resolveTask,\n });\n\n taskPromise.then(resolve).catch((err) => {\n // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors\n failTask(err);\n reject(err);\n });\n });\n };\n\n this.queue.push({ task: taskWithContext, options });\n if (!this.isProcessing) {\n this.ensureProcessing();\n }\n },\n { deadline: this.options.maxTaskIdleTime },\n );\n }\n\n private ensureProcessing(): void {\n if (this.isProcessing) return;\n this.isProcessing = true;\n this.processQueue();\n }\n\n private processQueue(): void {\n try {\n while (\n this.activeTasks < this.options.maxActiveTasks &&\n this.queue.length > 0\n ) {\n const item = this.takeFirstAvailableItem();\n\n if (item === null) return;\n\n const groupId = item.options?.taskGroupId;\n\n if (groupId) {\n // Mark the group as active\n this.activeGroups.add(groupId);\n }\n\n this.activeTasks++;\n void this.executeItem(item);\n }\n } catch (error) {\n console.error(error);\n throw error;\n } finally {\n this.isProcessing = false;\n if (\n this.hasItemsToProcess() &&\n this.activeTasks < this.options.maxActiveTasks\n ) {\n this.ensureProcessing();\n }\n }\n }\n\n private async executeItem({ task, options }: TaskQueueItem): Promise<void> {\n try {\n await task();\n } finally {\n this.activeTasks--;\n\n // Mark the group as inactive after task completion\n if (options && options.taskGroupId) {\n this.activeGroups.delete(options.taskGroupId);\n }\n\n this.ensureProcessing();\n }\n }\n\n private takeFirstAvailableItem = (): TaskQueueItem | null => {\n const taskIndex = this.queue.findIndex(\n (item) =>\n !item.options?.taskGroupId ||\n !this.activeGroups.has(item.options.taskGroupId),\n );\n\n if (taskIndex === -1) {\n // All remaining tasks are blocked by active groups\n return null;\n }\n\n // Remove the task from the queue\n const [item] = this.queue.splice(taskIndex, 1);\n\n return item ?? null;\n };\n\n private hasItemsToProcess = (): boolean =>\n this.queue.findIndex(\n (item) =>\n !item.options?.taskGroupId ||\n !this.activeGroups.has(item.options.taskGroupId),\n ) !== -1;\n}\n\nconst DEFAULT_PROMISE_DEADLINE = 2147483647;\n\nconst promiseWithDeadline = <T>(\n executor: (\n resolve: (value: T | PromiseLike<T>) => void,\n reject: (reason?: unknown) => void,\n ) => void,\n options: { deadline?: number },\n) => {\n return new Promise<T>((resolve, reject) => {\n let taskStarted = false;\n\n const maxWaitingTime = options.deadline || DEFAULT_PROMISE_DEADLINE;\n\n let timeoutId: NodeJS.Timeout | null = setTimeout(() => {\n if (!taskStarted) {\n reject(\n new Error('Task was not started within the maximum waiting time'),\n );\n }\n }, maxWaitingTime);\n\n executor((value) => {\n taskStarted = true;\n if (timeoutId) {\n clearTimeout(timeoutId);\n }\n timeoutId = null;\n resolve(value);\n }, reject);\n });\n};\n","import { TaskProcessor } from '../../taskProcessing';\n\nexport type LockOptions = { lockId: number };\n\nexport type AcquireLockOptions = { lockId: string };\nexport type ReleaseLockOptions = { lockId: string };\n\nexport type Lock = {\n acquire(options: AcquireLockOptions): Promise<void>;\n tryAcquire(options: AcquireLockOptions): Promise<boolean>;\n release(options: ReleaseLockOptions): Promise<boolean>;\n withAcquire: <Result = unknown>(\n handle: () => Promise<Result>,\n options: AcquireLockOptions,\n ) => Promise<Result>;\n};\n\nexport const InProcessLock = (): Lock => {\n const taskProcessor = new TaskProcessor({\n maxActiveTasks: Number.MAX_VALUE,\n maxQueueSize: Number.MAX_VALUE,\n });\n\n // Map to store ack functions of currently held locks: lockId -> ack()\n const locks = new Map<string, () => void>();\n\n return {\n async acquire({ lockId }: AcquireLockOptions): Promise<void> {\n // If the lock is already held, we just queue up another task in the same group.\n // TaskProcessor ensures tasks in the same group run one at a time.\n await new Promise<void>((resolve, reject) => {\n taskProcessor\n .enqueue(\n ({ ack }) => {\n // When this task starts, it means the previous lock (if any) was released\n // and now we have exclusive access.\n locks.set(lockId, ack);\n // We do NOT call ack() here. We hold onto the lock.\n resolve();\n return Promise.resolve();\n },\n { taskGroupId: lockId },\n )\n .catch(reject);\n });\n },\n\n async tryAcquire({ lockId }: AcquireLockOptions): Promise<boolean> {\n // If lock is already held, fail immediately\n if (locks.has(lockId)) {\n return false;\n }\n\n // TODO: Check pending queue\n await this.acquire({ lockId });\n\n return true;\n },\n\n release({ lockId }: ReleaseLockOptions): Promise<boolean> {\n const ack = locks.get(lockId);\n if (ack === undefined) {\n return Promise.resolve(true);\n }\n locks.delete(lockId);\n ack();\n return Promise.resolve(true);\n },\n\n async withAcquire<Result = unknown>(\n handle: () => Promise<Result>,\n { lockId }: AcquireLockOptions,\n ): Promise<Result> {\n return taskProcessor.enqueue(\n async ({ ack }) => {\n // When this task starts, it means the previous lock (if any) was released\n // and now we have exclusive access.\n locks.set(lockId, ack);\n\n // We do NOT call ack() here. We hold onto the lock.\n try {\n return await handle();\n } finally {\n locks.delete(lockId);\n ack();\n }\n },\n { taskGroupId: lockId },\n );\n },\n };\n};\n","const textEncoder = new TextEncoder();\n\nexport const hashText = async (text: string): Promise<bigint> => {\n const hashBuffer = await crypto.subtle.digest(\n 'SHA-256',\n textEncoder.encode(text),\n );\n // Create an array with a single element that is a 64-bit signed integer\n // We take the first 8 bytes (so 64 bits) of the SHA-256 hash\n const view = new BigInt64Array(hashBuffer, 0, 1);\n return view[0]!;\n};\n","import { InProcessLock } from '@event-driven-io/emmett';\nimport { EventStoreDBClient } from '@eventstore/db-client';\nimport {\n AbstractStartedContainer,\n GenericContainer,\n Wait,\n type StartedTestContainer,\n} from 'testcontainers';\nimport type { Environment } from 'testcontainers/build/types';\n\nexport const EVENTSTOREDB_PORT = 2113;\nexport const EVENTSTOREDB_IMAGE_NAME = 'eventstore/eventstore';\nexport const EVENTSTOREDB_IMAGE_TAG = '24.10.0-bookworm-slim';\nexport const EVENTSTOREDB_ARM64_IMAGE_TAG = '24.10.0-alpha-arm64v8';\n\nexport const EVENTSTOREDB_DEFAULT_IMAGE = `${EVENTSTOREDB_IMAGE_NAME}:${process.arch !== 'arm64' ? EVENTSTOREDB_IMAGE_TAG : EVENTSTOREDB_ARM64_IMAGE_TAG}`;\n\nexport type EventStoreDBContainerOptions = {\n disableProjections?: boolean;\n isSecure?: boolean;\n useFileStorage?: boolean;\n withReuse?: boolean;\n};\n\nexport const defaultEventStoreDBContainerOptions: EventStoreDBContainerOptions =\n {\n disableProjections: false,\n isSecure: false,\n useFileStorage: false,\n withReuse: false,\n };\n\nexport class EventStoreDBContainer extends GenericContainer {\n constructor(\n image = EVENTSTOREDB_DEFAULT_IMAGE,\n options: EventStoreDBContainerOptions = defaultEventStoreDBContainerOptions,\n ) {\n super(image);\n\n const environment: Environment = {\n ...(!options.disableProjections\n ? {\n EVENTSTORE_RUN_PROJECTIONS: 'ALL',\n }\n : {}),\n ...(!options.isSecure\n ? {\n EVENTSTORE_INSECURE: 'true',\n }\n : {}),\n ...(options.useFileStorage\n ? {\n EVENTSTORE_MEM_DB: 'false',\n EVENTSTORE_DB: '/data/integration-tests',\n }\n : {}),\n EVENTSTORE_CLUSTER_SIZE: '1',\n EVENTSTORE_START_STANDARD_PROJECTIONS: 'true',\n EVENTSTORE_NODE_PORT: `${EVENTSTOREDB_PORT}`,\n EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP: 'true',\n };\n\n this.withEnvironment(environment).withExposedPorts(EVENTSTOREDB_PORT);\n\n if (options.withReuse) this.withReuse();\n\n this.withWaitStrategy(\n Wait.forAll([Wait.forHealthCheck(), Wait.forListeningPorts()]),\n );\n }\n\n async start(): Promise<StartedEventStoreDBContainer> {\n return new StartedEventStoreDBContainer(await super.start());\n }\n}\n\nexport class StartedEventStoreDBContainer extends AbstractStartedContainer {\n constructor(container: StartedTestContainer) {\n super(container);\n }\n\n getConnectionString(): string {\n return `esdb://${this.getHost()}:${this.getMappedPort(2113)}?tls=false`;\n }\n\n getClient(): EventStoreDBClient {\n return EventStoreDBClient.connectionString(this.getConnectionString());\n }\n}\n\nlet container: EventStoreDBContainer | null = null;\nlet startedContainer: StartedEventStoreDBContainer | null = null;\nlet startedCount = 0;\nconst lock = InProcessLock();\n\nexport const getSharedEventStoreDBTestContainer = () =>\n lock.withAcquire(\n async () => {\n if (startedContainer) return startedContainer;\n\n if (!container)\n container = new EventStoreDBContainer(EVENTSTOREDB_DEFAULT_IMAGE);\n\n startedContainer = await container.start();\n startedCount++;\n\n container.withLogConsumer((stream) =>\n stream\n .on('data', (line) => console.log(line))\n .on('err', (line) => console.error(line))\n .on('end', () => console.log('Stream closed')),\n );\n\n return startedContainer;\n },\n { lockId: 'SharedEventStoreDBTestContainer' },\n );\n\nexport const getSharedTestEventStoreDBClient = async () => {\n return (await getSharedEventStoreDBTestContainer()).getClient();\n};\n\nexport const releaseSharedEventStoreDBTestContainer = () =>\n lock.withAcquire(\n async () => {\n const containerToStop = startedContainer;\n if (containerToStop && --startedCount === 0) {\n try {\n startedContainer = null;\n container = null;\n await containerToStop.stop();\n } catch {\n /* do nothing */\n }\n }\n },\n { lockId: 'SharedEventStoreDBTestContainer' },\n );\n","import { MongoDBContainer } from '@testcontainers/mongodb';\n\nexport const getMongoDBContainer = (\n options: { version: string } = { version: '6.0.1' },\n) => {\n return new MongoDBContainer(`mongo:${options.version}`);\n};\n\nexport const getMongoDBStartedContainer = async (\n options: { version: string } = { version: '6.0.1' },\n) => {\n const container = getMongoDBContainer(options);\n return container.start();\n};\n","import { PostgreSqlContainer } from '@testcontainers/postgresql';\n\nexport const getPostgreSQLContainer = (\n options: { version: string } = { version: '18.1' },\n) => {\n return new PostgreSqlContainer(`postgres:${options.version}`);\n};\n\nexport const getPostgreSQLStartedContainer = async (\n options: { version: string } = { version: '18.1' },\n) => {\n const container = getPostgreSQLContainer(options);\n return container.start();\n};\n"]}
|