@event-driven-io/emmett-expressjs 0.43.0-beta.2 → 0.43.0-beta.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-expressjs/dist/index.cjs","../src/index.ts","../src/application.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/processors/processors.ts","../../emmett/src/eventStore/projections/inMemory/inMemoryProjectionSpec.ts","../../emmett/src/workflows/handleWorkflow.ts","../../emmett/src/typing/index.ts","../../emmett/src/serialization/json/JSONParser.ts","../../emmett/src/utils/strings/hashText.ts","../../emmett/src/testing/assertions.ts","../../emmett/src/testing/wrapEventStore.ts","../src/middlewares/problemDetailsMiddleware.ts","../src/etag.ts","../src/handler.ts","../src/responses.ts","../src/testing/apiE2ESpecification.ts","../src/testing/apiSpecification.ts"],"names":["message","ETagErrors","supertest"],"mappings":"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","file":"/home/runner/work/emmett/emmett/src/packages/emmett-expressjs/dist/index.cjs","sourcesContent":[null,"import 'express-async-errors';\n\nexport * from './application';\nexport * from './etag';\nexport * from './handler';\nexport * from './responses';\nexport * from './testing';\n","import express, { Router, type Application } from 'express';\nimport 'express-async-errors';\nimport http from 'http';\nimport { problemDetailsMiddleware } from './middlewares/problemDetailsMiddleware';\nimport type { ErrorToProblemDetailsMapping } from './responses';\n\n// #region web-api-setup\nexport type WebApiSetup = (router: Router) => void;\n// #endregion web-api-setup\n\nexport type ApplicationOptions = {\n apis: WebApiSetup[];\n mapError?: ErrorToProblemDetailsMapping;\n enableDefaultExpressEtag?: boolean;\n disableJsonMiddleware?: boolean;\n disableUrlEncodingMiddleware?: boolean;\n disableProblemDetailsMiddleware?: boolean;\n};\n\nexport const getApplication = (options: ApplicationOptions) => {\n const app: Application = express();\n\n const {\n apis,\n mapError,\n enableDefaultExpressEtag,\n disableJsonMiddleware,\n disableUrlEncodingMiddleware,\n disableProblemDetailsMiddleware,\n } = options;\n\n const router = Router();\n\n // disabling default etag behaviour\n // to use etags in if-match and if-not-match headers\n app.set('etag', enableDefaultExpressEtag ?? false);\n\n // add json middleware\n if (!disableJsonMiddleware) app.use(express.json());\n\n // enable url encoded urls and bodies\n if (!disableUrlEncodingMiddleware)\n app.use(\n express.urlencoded({\n extended: true,\n }),\n );\n\n for (const api of apis) {\n api(router);\n }\n app.use(router);\n\n // add problem details middleware\n if (!disableProblemDetailsMiddleware)\n app.use(problemDetailsMiddleware(mapError));\n\n return app;\n};\n\nexport type StartApiOptions = {\n port?: number;\n};\n\nexport const startAPI = (\n app: Application,\n options: StartApiOptions = { port: 3000 },\n) => {\n const { port } = options;\n const server = http.createServer(app);\n\n server.on('listening', () => {\n console.info('server up listening');\n });\n\n return server.listen(port);\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 { bigIntProcessorCheckpoint } from '../processors';\nimport type { ProjectionRegistration } from '../projections';\nimport type {\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<EventType, EventPayloadType>,\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<EventType, EventPayloadType>,\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 globalPosition = BigInt(getAllEventsCount() + index + 1);\n const metadata: ReadEventMetadataWithGlobalPosition = {\n streamName,\n messageId: uuid(),\n streamPosition: BigInt(currentEvents.length + index + 1),\n globalPosition,\n checkpoint: bigIntProcessorCheckpoint(globalPosition),\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 { 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 Brand,\n type CanHandle,\n type DefaultRecord,\n type Event,\n type Message,\n type MessageHandlerResult,\n type RecordedMessage,\n type SingleMessageHandlerWithContext,\n type SingleRecordedMessageHandlerWithContext,\n} from '../typing';\nimport { bigInt } from '../utils';\nimport { onShutdown } from '../utils/shutdown';\n\nexport type CurrentMessageProcessorPosition =\n | { lastCheckpoint: ProcessorCheckpoint }\n | 'BEGINNING'\n | 'END';\n\nexport type GetCheckpoint<\n MessageType extends AnyMessage = AnyMessage,\n MessageMetadataType extends AnyReadEventMetadata = AnyReadEventMetadata,\n> = (\n message: RecordedMessage<MessageType, MessageMetadataType>,\n) => ProcessorCheckpoint | null;\n\nexport const getCheckpoint = <\n MessageType extends AnyMessage = AnyMessage,\n MessageMetadataType extends AnyReadEventMetadata = AnyReadEventMetadata,\n>(\n message: RecordedMessage<MessageType, MessageMetadataType>,\n): ProcessorCheckpoint | null => {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access\n return message.metadata.checkpoint;\n};\n\nexport const wasMessageHandled = <\n MessageType extends AnyMessage = AnyMessage,\n MessageMetadataType extends AnyReadEventMetadata = AnyReadEventMetadata,\n>(\n message: RecordedMessage<MessageType, MessageMetadataType>,\n checkpoint: ProcessorCheckpoint | null,\n): boolean => {\n //TODO Make it smarter\n const messageCheckpoint = getCheckpoint(message);\n\n return (\n messageCheckpoint !== null &&\n messageCheckpoint !== undefined &&\n checkpoint !== null &&\n checkpoint !== undefined &&\n messageCheckpoint <= checkpoint\n );\n};\n\nexport type MessageProcessorStartFrom =\n | CurrentMessageProcessorPosition\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> = {\n id: string;\n instanceId: string;\n type: string;\n canHandle?: string[];\n init: (options: Partial<HandlerContext>) => Promise<void>;\n start: (\n options: Partial<HandlerContext>,\n ) => Promise<CurrentMessageProcessorPosition | 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> = {\n read: ReadProcessorCheckpoint<HandlerContext>;\n store: StoreProcessorCheckpoint<\n MessageType,\n MessageMetadataType,\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> = {\n type?: string;\n processorId: string;\n processorInstanceId?: string;\n version?: number;\n partition?: string;\n startFrom?: MessageProcessorStartFrom;\n stopAfter?: (\n message: RecordedMessage<MessageType, MessageMetadataType>,\n ) => boolean;\n processingScope?: MessageProcessingScope<HandlerContext>;\n checkpoints?: Checkpointer<MessageType, MessageMetadataType, HandlerContext>;\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 MessagePayloadType extends AnyMessage = MessageType,\n> = BaseMessageProcessorOptions<\n MessageType,\n MessageMetadataType,\n HandlerContext\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 EventPayloadType extends Event = EventType,\n> = Omit<\n BaseMessageProcessorOptions<EventType, MessageMetadataType, HandlerContext>,\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 ProcessorCheckpoint = Brand<string, 'ProcessorCheckpoint'>;\n\nexport const bigIntProcessorCheckpoint = (value: bigint): ProcessorCheckpoint =>\n bigInt.toNormalizedString(value) as ProcessorCheckpoint;\n\nexport const parseBigIntProcessorCheckpoint = (\n value: ProcessorCheckpoint,\n): bigint => BigInt(value);\n\nexport type ReadProcessorCheckpointResult = {\n lastCheckpoint: ProcessorCheckpoint | null;\n};\n\nexport type ReadProcessorCheckpoint<\n HandlerContext extends DefaultRecord = DefaultRecord,\n> = (\n options: { processorId: string; partition?: string },\n context: HandlerContext,\n) => Promise<ReadProcessorCheckpointResult>;\n\nexport type StoreProcessorCheckpointResult =\n | {\n success: true;\n newCheckpoint: ProcessorCheckpoint | null;\n }\n | { success: false; reason: 'IGNORED' | 'MISMATCH' | 'CURRENT_AHEAD' };\n\nexport type StoreProcessorCheckpoint<\n MessageType extends Message = AnyMessage,\n MessageMetadataType extends AnyReadEventMetadata = AnyReadEventMetadata,\n HandlerContext extends DefaultRecord | undefined = undefined,\n> = (\n options: {\n message: RecordedMessage<MessageType, MessageMetadataType>;\n processorId: string;\n version: number | undefined;\n lastCheckpoint: ProcessorCheckpoint | null;\n partition?: string;\n },\n context: HandlerContext,\n) => Promise<StoreProcessorCheckpointResult>;\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 MessagePayloadType extends Message = MessageType,\n>(\n options: ReactorOptions<\n MessageType,\n MessageMetadataType,\n HandlerContext,\n MessagePayloadType\n >,\n): MessageProcessor<MessageType, MessageMetadataType, HandlerContext> => {\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: ProcessorCheckpoint | 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 canHandle,\n init,\n start: async (\n startOptions: Partial<HandlerContext>,\n ): Promise<CurrentMessageProcessorPosition | 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 =\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 EventPayloadType extends Event = EventType,\n>(\n options: ProjectorOptions<\n EventType,\n EventMetaDataType,\n HandlerContext,\n EventPayloadType\n >,\n): MessageProcessor<EventType, EventMetaDataType, HandlerContext> => {\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 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","import { v4 as uuid } from 'uuid';\nimport {\n handleInMemoryProjections,\n type InMemoryProjectionDefinition,\n} from '.';\nimport { bigIntProcessorCheckpoint } 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 {\n CombinedReadEventMetadata,\n Event,\n ReadEvent,\n} 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 checkpoint: bigIntProcessorCheckpoint(++globalPosition),\n 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 {\n canCreateEventStoreSession,\n isExpectedVersionConflictError,\n NO_CONCURRENCY_CHECK,\n nulloSessionFactory,\n STREAM_DOES_NOT_EXIST,\n type AppendStreamResultOfEventStore,\n type AppendToStreamOptions,\n type EventStore,\n type EventStoreSession,\n type ExpectedStreamVersion,\n type ReadStreamOptions,\n} from '../eventStore';\nimport type {\n AnyCommand,\n AnyEvent,\n AnyReadEventMetadata,\n Event,\n RecordedMessage,\n} from '../typing';\nimport { asyncRetry, NoRetries, type AsyncRetryOptions } from '../utils';\nimport type {\n WorkflowEvent,\n WorkflowInputMessageMetadata,\n WorkflowMessageAction,\n} from './workflow';\nimport type { WorkflowOptions } from './workflowProcessor';\n\nexport const WorkflowHandlerStreamVersionConflictRetryOptions: AsyncRetryOptions =\n {\n retries: 3,\n minTimeout: 100,\n factor: 1.5,\n shouldRetryError: isExpectedVersionConflictError,\n };\n\nexport type WorkflowHandlerRetryOptions =\n | AsyncRetryOptions\n | { onVersionConflict: true | number | AsyncRetryOptions };\n\nconst fromWorkflowHandlerRetryOptions = (\n retryOptions: WorkflowHandlerRetryOptions | undefined,\n): AsyncRetryOptions => {\n if (retryOptions === undefined) return NoRetries;\n\n if ('onVersionConflict' in retryOptions) {\n if (typeof retryOptions.onVersionConflict === 'boolean')\n return WorkflowHandlerStreamVersionConflictRetryOptions;\n else if (typeof retryOptions.onVersionConflict === 'number')\n return {\n ...WorkflowHandlerStreamVersionConflictRetryOptions,\n retries: retryOptions.onVersionConflict,\n };\n else return retryOptions.onVersionConflict;\n }\n\n return retryOptions;\n};\n\n// #region workflow-handler\nexport type WorkflowHandlerResult<\n Output extends AnyEvent | AnyCommand,\n Store extends EventStore,\n> = AppendStreamResultOfEventStore<Store> & {\n newMessages: Output[];\n};\n\nexport type WorkflowHandleOptions<Store extends EventStore> = Parameters<\n Store['appendToStream']\n>[2] & {\n expectedStreamVersion?: ExpectedStreamVersion;\n retry?: WorkflowHandlerRetryOptions;\n};\n\ntype WorkflowInternalState<State> = {\n userState: State;\n processedInputIds: Set<string>;\n};\n\nconst emptyHandlerResult = <\n Output extends AnyEvent | AnyCommand,\n Store extends EventStore,\n>(\n nextExpectedStreamVersion: bigint = 0n,\n): WorkflowHandlerResult<Output, Store> =>\n ({\n newMessages: [] as Output[],\n createdNewStream: false,\n nextExpectedStreamVersion,\n }) as unknown as WorkflowHandlerResult<Output, Store>;\n\nconst createInputMetadata = (\n originalMessageId: string,\n action: Extract<WorkflowMessageAction, 'InitiatedBy' | 'Received'>,\n): WorkflowInputMessageMetadata => ({\n originalMessageId,\n input: true,\n action,\n});\n\nconst tagOutputMessage = <Output extends AnyEvent | AnyCommand>(\n msg: Output,\n action: Extract<WorkflowMessageAction, 'Sent' | 'Published' | 'Scheduled'>,\n): Output => {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n const existingMetadata =\n 'metadata' in msg && msg.metadata ? msg.metadata : {};\n return {\n ...msg,\n metadata: {\n ...existingMetadata,\n action,\n },\n } as Output;\n};\n\nconst createWrappedInitialState = <State>(initialState: () => State) => {\n return (): WorkflowInternalState<State> => ({\n userState: initialState(),\n processedInputIds: new Set(),\n });\n};\n\nconst createWrappedEvolve = <\n Input extends AnyEvent | AnyCommand,\n Output extends AnyEvent | AnyCommand,\n State,\n>(\n evolve: (state: State, event: WorkflowEvent<Input | Output>) => State,\n workflowName: string,\n separateInputInboxFromProcessing: boolean,\n) => {\n return (\n state: WorkflowInternalState<State>,\n event: WorkflowEvent<Input | Output>,\n ): WorkflowInternalState<State> => {\n const metadata = (event as Record<string, unknown>).metadata as\n | Record<string, unknown>\n | undefined;\n\n // Track processed inputs for idempotency\n let processedInputIds = state.processedInputIds;\n if (\n metadata?.input === true &&\n typeof metadata?.originalMessageId === 'string'\n ) {\n processedInputIds = new Set(state.processedInputIds);\n processedInputIds.add(metadata.originalMessageId);\n }\n\n // In separated inbox mode, don't apply inputs to state - they're just sitting in inbox\n // Only outputs (from processing) should update state\n if (separateInputInboxFromProcessing && metadata?.input === true) {\n return {\n userState: state.userState,\n processedInputIds,\n };\n }\n\n // Strip workflow prefix from input event types\n const eventType = event.type as string;\n const eventForEvolve = eventType.startsWith(`${workflowName}:`)\n ? ({\n ...event,\n type: eventType.replace(`${workflowName}:`, ''),\n } as WorkflowEvent<Input | Output>)\n : event;\n\n return {\n userState: evolve(state.userState, eventForEvolve),\n processedInputIds,\n };\n };\n};\n\nexport const workflowStreamName = ({\n workflowName,\n workflowId,\n}: {\n workflowName: string;\n workflowId: string;\n}) => `emt:workflow:${workflowName}:${workflowId}`;\n\nexport const WorkflowHandler =\n <\n Input extends AnyEvent | AnyCommand,\n State,\n Output extends AnyEvent | AnyCommand,\n MessageMetadataType extends AnyReadEventMetadata = AnyReadEventMetadata,\n StoredMessage extends AnyEvent | AnyCommand = Output,\n >(\n options: WorkflowOptions<\n Input,\n State,\n Output,\n MessageMetadataType,\n StoredMessage\n > & {\n retry?: WorkflowHandlerRetryOptions;\n },\n ) =>\n async <Store extends EventStore>(\n store: Store,\n message: Input | RecordedMessage<Input, MessageMetadataType>,\n handleOptions?: WorkflowHandleOptions<Store>,\n ): Promise<WorkflowHandlerResult<Output, Store>> =>\n asyncRetry(\n async () => {\n const result = await withSession<\n Store,\n WorkflowHandlerResult<Output, Store>\n >(store, async ({ eventStore }) => {\n const {\n workflow: { evolve, initialState, decide, name: workflowName },\n getWorkflowId,\n } = options;\n\n const inputMessageId =\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n ('metadata' in message && message.metadata?.messageId\n ? // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n (message.metadata.messageId as string | undefined)\n : undefined) ?? uuid();\n\n const messageWithMetadata: RecordedMessage<\n Input,\n MessageMetadataType\n > = {\n ...message,\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n metadata: {\n messageId: inputMessageId,\n ...(message as RecordedMessage<Input, MessageMetadataType>)\n .metadata,\n },\n } as RecordedMessage<Input, MessageMetadataType>;\n\n const workflowId = getWorkflowId(messageWithMetadata);\n\n if (!workflowId) {\n return emptyHandlerResult<Output, Store>();\n }\n\n const streamName = options.mapWorkflowId\n ? options.mapWorkflowId(workflowId)\n : workflowStreamName({ workflowName, workflowId });\n\n const messageType = messageWithMetadata.type as string;\n const hasWorkflowPrefix = messageType.startsWith(`${workflowName}:`);\n\n // Separated inbox mode: store-only path (no prefix = external input)\n if (options.separateInputInboxFromProcessing && !hasWorkflowPrefix) {\n const inputMetadata = createInputMetadata(\n inputMessageId,\n 'InitiatedBy',\n );\n\n const inputToStore = {\n type: `${workflowName}:${messageWithMetadata.type}`,\n data: messageWithMetadata.data,\n kind: messageWithMetadata.kind,\n metadata: inputMetadata,\n } as StoredMessage;\n\n const appendResult = await eventStore.appendToStream(\n streamName,\n [inputToStore] as unknown as Event[],\n {\n ...(handleOptions as AppendToStreamOptions<\n Event,\n StoredMessage & Event\n >),\n expectedStreamVersion:\n handleOptions?.expectedStreamVersion ?? NO_CONCURRENCY_CHECK,\n },\n );\n\n return {\n ...appendResult,\n newMessages: [] as Output[],\n } as unknown as WorkflowHandlerResult<Output, Store>;\n }\n\n // Wrap the evolve and initialState for idempotency tracking\n const wrappedInitialState = createWrappedInitialState(initialState);\n const wrappedEvolve = createWrappedEvolve(\n evolve,\n workflowName,\n options.separateInputInboxFromProcessing ?? false,\n ) as (\n state: WorkflowInternalState<State>,\n event: WorkflowEvent<Input | Output>,\n ) => WorkflowInternalState<State>;\n\n // 1. Aggregate the stream\n const aggregationResult = await eventStore.aggregateStream<\n WorkflowInternalState<State>,\n WorkflowEvent<Input | Output>,\n StoredMessage & Event\n >(streamName, {\n evolve: wrappedEvolve,\n initialState: wrappedInitialState,\n read: {\n ...(handleOptions as ReadStreamOptions<\n WorkflowEvent<Input | Output>,\n StoredMessage & Event\n >),\n // expected stream version is passed to fail fast\n // if stream is in the wrong state\n\n expectedStreamVersion:\n handleOptions?.expectedStreamVersion ?? NO_CONCURRENCY_CHECK,\n },\n });\n\n // 2. Use the aggregate state\n\n const { currentStreamVersion } = aggregationResult;\n\n const { userState: state, processedInputIds } =\n aggregationResult.state;\n\n // Idempotency: skip if this input was already processed\n\n if (processedInputIds.has(inputMessageId)) {\n return emptyHandlerResult<Output, Store>(currentStreamVersion);\n }\n\n // 3. Run business logic\n // Strip workflow prefix from message type if present (for separated inbox processing)\n const messageForDecide = hasWorkflowPrefix\n ? ({\n ...messageWithMetadata,\n type: messageType.replace(`${workflowName}:`, ''),\n } as Input)\n : (messageWithMetadata as Input);\n\n const result = decide(messageForDecide, state);\n\n const inputMetadata = createInputMetadata(\n inputMessageId,\n aggregationResult.streamExists ? 'Received' : 'InitiatedBy',\n );\n\n const inputToStore = {\n type: `${workflowName}:${messageWithMetadata.type}`,\n data: messageWithMetadata.data,\n kind: messageWithMetadata.kind,\n metadata: inputMetadata,\n } as StoredMessage;\n\n const outputMessages = (\n Array.isArray(result) ? result : [result]\n ).filter((msg): msg is Output => msg !== undefined && msg !== null);\n\n const outputCommandTypes = options.outputs?.commands ?? [];\n const taggedOutputMessages = outputMessages.map((msg) => {\n const action: WorkflowMessageAction = outputCommandTypes.includes(\n msg.type as string,\n )\n ? 'Sent'\n : 'Published';\n return tagOutputMessage(msg, action);\n });\n\n const messagesToAppend =\n options.separateInputInboxFromProcessing && hasWorkflowPrefix\n ? [...taggedOutputMessages] // input already in stream\n : [inputToStore, ...taggedOutputMessages]; // normal: store input + outputs\n\n // If there are no messages to append, return early with current state\n if (messagesToAppend.length === 0) {\n return emptyHandlerResult<Output, Store>(currentStreamVersion);\n }\n\n // Either use:\n // - provided expected stream version,\n // - current stream version got from stream aggregation,\n // - or expect stream not to exists otherwise.\n\n const expectedStreamVersion: ExpectedStreamVersion =\n handleOptions?.expectedStreamVersion ??\n (aggregationResult.streamExists\n ? currentStreamVersion\n : STREAM_DOES_NOT_EXIST);\n\n // 4. Append result to the stream\n const appendResult = await eventStore.appendToStream(\n streamName,\n // TODO: Fix this cast\n messagesToAppend as unknown as Event[],\n {\n ...(handleOptions as AppendToStreamOptions<\n Event,\n StoredMessage & Event\n >),\n expectedStreamVersion,\n },\n );\n\n // 5. Return result with output messages only\n return {\n ...appendResult,\n newMessages: outputMessages,\n } as unknown as WorkflowHandlerResult<Output, Store>;\n });\n\n return result;\n },\n fromWorkflowHandlerRetryOptions(\n handleOptions && 'retry' in handleOptions\n ? handleOptions.retry\n : options.retry,\n ),\n );\n// #endregion stream-handler\n\nconst withSession = <EventStoreType extends EventStore, T = unknown>(\n eventStore: EventStoreType,\n callback: (session: EventStoreSession<EventStoreType>) => Promise<T>,\n) => {\n const sessionFactory = canCreateEventStoreSession<EventStoreType>(eventStore)\n ? eventStore\n : nulloSessionFactory<EventStoreType>(eventStore);\n\n return sessionFactory.withSession(callback);\n};\n","export * from './deepReadonly';\n\nexport * from './command';\nexport * from './event';\nexport * from './message';\nexport * from './messageHandling';\n\nexport * from './decider';\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","export class ParseError extends Error {\n constructor(text: string) {\n super(`Cannot parse! ${text}`);\n }\n}\n\nexport type Mapper<From, To = From> =\n | ((value: unknown) => To)\n | ((value: Partial<From>) => To)\n | ((value: From) => To)\n | ((value: Partial<To>) => To)\n | ((value: To) => To)\n | ((value: Partial<To | From>) => To)\n | ((value: To | From) => To);\n\nexport type MapperArgs<From, To = From> = Partial<From> &\n From &\n Partial<To> &\n To;\n\nexport type ParseOptions<From, To = From> = {\n reviver?: (key: string, value: unknown) => unknown;\n map?: Mapper<From, To>;\n typeCheck?: <To>(value: unknown) => value is To;\n};\n\nexport type StringifyOptions<From, To = From> = {\n map?: Mapper<From, To>;\n};\n\nexport const JSONParser = {\n stringify: <From, To = From>(\n value: From,\n options?: StringifyOptions<From, To>,\n ) => {\n return JSON.stringify(\n options?.map ? options.map(value as MapperArgs<From, To>) : value,\n //TODO: Consider adding support to DateTime and adding specific format to mark that's a bigint\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n (_, v) => (typeof v === 'bigint' ? v.toString() : v),\n );\n },\n parse: <From, To = From>(\n text: string,\n options?: ParseOptions<From, To>,\n ): To | undefined => {\n const parsed: unknown = JSON.parse(text, options?.reviver);\n\n if (options?.typeCheck && !options?.typeCheck<To>(parsed))\n throw new ParseError(text);\n\n return options?.map\n ? options.map(parsed as MapperArgs<From, To>)\n : (parsed as To | undefined);\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 { JSONParser } from '../serialization';\nimport type { DefaultRecord } from '../typing';\nimport { deepEquals } from '../utils';\n\nexport class AssertionError extends Error {\n constructor(message: string) {\n super(message);\n }\n}\n\nexport const isSubset = (superObj: unknown, subObj: unknown): boolean => {\n const sup = superObj as DefaultRecord;\n const sub = subObj as DefaultRecord;\n\n assertOk(sup);\n assertOk(sub);\n\n return Object.keys(sub).every((ele: string) => {\n if (sub[ele] !== null && typeof sub[ele] == 'object') {\n return isSubset(sup[ele], sub[ele]);\n }\n return sub[ele] === sup[ele];\n });\n};\n\nexport const assertFails = (message?: string) => {\n throw new AssertionError(message ?? 'That should not ever happened, right?');\n};\n\nexport const assertThrowsAsync = async <TError extends Error>(\n fun: () => Promise<void>,\n errorCheck?: (error: Error) => boolean,\n): Promise<TError> => {\n try {\n await fun();\n } catch (error) {\n const typedError = error as TError;\n if (typedError instanceof AssertionError || !errorCheck) {\n assertFalse(\n typedError instanceof AssertionError,\n \"Function didn't throw expected error\",\n );\n return typedError;\n }\n\n assertTrue(\n errorCheck(typedError),\n `Error doesn't match the expected condition: ${JSONParser.stringify(error)}`,\n );\n\n return typedError;\n }\n throw new AssertionError(\"Function didn't throw expected error\");\n};\n\nexport const assertThrows = <TError extends Error>(\n fun: () => void,\n errorCheck?: (error: Error) => boolean,\n): TError => {\n try {\n fun();\n } catch (error) {\n const typedError = error as TError;\n\n if (errorCheck) {\n assertTrue(\n errorCheck(typedError),\n `Error doesn't match the expected condition: ${JSONParser.stringify(error)}`,\n );\n } else if (typedError instanceof AssertionError) {\n assertFalse(\n typedError instanceof AssertionError,\n \"Function didn't throw expected error\",\n );\n }\n\n return typedError;\n }\n throw new AssertionError(\"Function didn't throw expected error\");\n};\n\nexport const assertDoesNotThrow = <TError extends Error>(\n fun: () => void,\n errorCheck?: (error: Error) => boolean,\n): TError | null => {\n try {\n fun();\n return null;\n } catch (error) {\n const typedError = error as TError;\n\n if (errorCheck) {\n assertFalse(\n errorCheck(typedError),\n `Error matching the expected condition was thrown!: ${JSONParser.stringify(error)}`,\n );\n } else {\n assertFails(`Function threw an error: ${JSONParser.stringify(error)}`);\n }\n\n return typedError;\n }\n};\n\nexport const assertRejects = async <T, TError extends Error = Error>(\n promise: Promise<T>,\n errorCheck?: ((error: TError) => boolean) | TError,\n) => {\n try {\n await promise;\n throw new AssertionError(\"Function didn't throw expected error\");\n } catch (error) {\n if (!errorCheck) return;\n\n if (errorCheck instanceof Error) assertDeepEqual(error, errorCheck);\n else assertTrue(errorCheck(error as TError));\n }\n};\n\nexport const assertMatches = (\n actual: unknown,\n expected: unknown,\n message?: string,\n) => {\n if (!isSubset(actual, expected))\n throw new AssertionError(\n message ??\n `subObj:\\n${JSONParser.stringify(expected)}\\nis not subset of\\n${JSONParser.stringify(actual)}`,\n );\n};\n\nexport const assertDeepEqual = <T = unknown>(\n actual: T,\n expected: T,\n message?: string,\n) => {\n if (!deepEquals(actual, expected))\n throw new AssertionError(\n message ??\n `subObj:\\n${JSONParser.stringify(expected)}\\nis not equal to\\n${JSONParser.stringify(actual)}`,\n );\n};\n\nexport const assertNotDeepEqual = <T = unknown>(\n actual: T,\n expected: T,\n message?: string,\n) => {\n if (deepEquals(actual, expected))\n throw new AssertionError(\n message ??\n `subObj:\\n${JSONParser.stringify(expected)}\\nis equals to\\n${JSONParser.stringify(actual)}`,\n );\n};\n\nexport const assertThat = <T>(item: T) => {\n return {\n isEqualTo: (other: T) => assertTrue(deepEquals(item, other)),\n };\n};\n\nexport const assertDefined = (\n value: unknown,\n message?: string | Error,\n): asserts value => {\n assertOk(value, message instanceof Error ? message.message : message);\n};\n\nexport function assertFalse(\n condition: boolean,\n message?: string,\n): asserts condition is false {\n if (condition !== false)\n throw new AssertionError(message ?? `Condition is true`);\n}\n\nexport function assertTrue(\n condition: boolean,\n message?: string,\n): asserts condition is true {\n if (condition !== true)\n throw new AssertionError(message ?? `Condition is false`);\n}\n\n// TODO: replace with assertDefined\nexport function assertOk<T>(\n obj: T | null | undefined,\n message?: string,\n): asserts obj is T {\n if (!obj) throw new AssertionError(message ?? `Condition is not truthy`);\n}\n\nexport function assertEqual<T>(\n expected: T | null | undefined,\n actual: T | null | undefined,\n message?: string,\n): void {\n if (expected !== actual)\n throw new AssertionError(\n `${message ?? 'Objects are not equal'}:\\nExpected: ${JSONParser.stringify(expected)}\\nActual: ${JSONParser.stringify(actual)}`,\n );\n}\n\nexport function assertNotEqual<T>(\n obj: T | null | undefined,\n other: T | null | undefined,\n message?: string,\n): void {\n if (obj === other)\n throw new AssertionError(\n message ?? `Objects are equal: ${JSONParser.stringify(obj)}`,\n );\n}\n\nexport function assertIsNotNull<T extends object | bigint>(\n result: T | null,\n): asserts result is T {\n assertNotEqual(result, null);\n assertOk(result);\n}\n\nexport function assertIsNull<T extends object>(\n result: T | null,\n): asserts result is null {\n assertEqual(result, null);\n}\n\ntype Call = {\n arguments: unknown[];\n result: unknown;\n target: unknown;\n this: unknown;\n};\n\nexport type ArgumentMatcher = (arg: unknown) => boolean;\n\nexport const argValue =\n <T>(value: T): ArgumentMatcher =>\n (arg) =>\n deepEquals(arg, value);\n\nexport const argMatches =\n <T>(matches: (arg: T) => boolean): ArgumentMatcher =>\n (arg) =>\n matches(arg as T);\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\nexport type MockedFunction = Function & { mock?: { calls: Call[] } };\n\nexport function verifyThat(fn: MockedFunction) {\n return {\n calledTimes: (times: number) => {\n assertEqual(fn.mock?.calls?.length, times);\n },\n notCalled: () => {\n assertEqual(fn?.mock?.calls?.length, 0);\n },\n called: () => {\n assertTrue(\n fn.mock?.calls.length !== undefined && fn.mock.calls.length > 0,\n );\n },\n calledWith: (...args: unknown[]) => {\n assertTrue(\n fn.mock?.calls.length !== undefined &&\n fn.mock.calls.length >= 1 &&\n fn.mock.calls.some((call) => deepEquals(call.arguments, args)),\n );\n },\n calledOnceWith: (...args: unknown[]) => {\n assertTrue(\n fn.mock?.calls.length !== undefined &&\n fn.mock.calls.length === 1 &&\n fn.mock.calls.some((call) => deepEquals(call.arguments, args)),\n );\n },\n calledWithArgumentMatching: (...matches: ArgumentMatcher[]) => {\n assertTrue(\n fn.mock?.calls.length !== undefined && fn.mock.calls.length >= 1,\n );\n assertTrue(\n fn.mock?.calls.length !== undefined &&\n fn.mock.calls.length >= 1 &&\n fn.mock.calls.some(\n (call) =>\n call.arguments &&\n call.arguments.length >= matches.length &&\n matches.every((match, index) => match(call.arguments[index])),\n ),\n );\n },\n notCalledWithArgumentMatching: (...matches: ArgumentMatcher[]) => {\n assertFalse(\n fn.mock?.calls.length !== undefined &&\n fn.mock.calls.length >= 1 &&\n fn.mock.calls[0]!.arguments &&\n fn.mock.calls[0]!.arguments.length >= matches.length &&\n matches.every((match, index) =>\n match(fn.mock!.calls[0]!.arguments[index]),\n ),\n );\n },\n };\n}\n\nexport const assertThatArray = <T>(array: T[]) => {\n return {\n isEmpty: () =>\n assertEqual(\n array.length,\n 0,\n `Array is not empty ${JSONParser.stringify(array)}`,\n ),\n isNotEmpty: () => assertNotEqual(array.length, 0, `Array is empty`),\n hasSize: (length: number) => assertEqual(array.length, length),\n containsElements: (other: T[]) => {\n assertTrue(other.every((ts) => array.some((o) => deepEquals(ts, o))));\n },\n containsElementsMatching: (other: T[]) => {\n assertTrue(other.every((ts) => array.some((o) => isSubset(o, ts))));\n },\n containsOnlyElementsMatching: (other: T[]) => {\n assertEqual(array.length, other.length, `Arrays lengths don't match`);\n assertTrue(other.every((ts) => array.some((o) => isSubset(o, ts))));\n },\n containsExactlyInAnyOrder: (other: T[]) => {\n assertEqual(array.length, other.length);\n assertTrue(array.every((ts) => other.some((o) => deepEquals(ts, o))));\n },\n containsExactlyInAnyOrderElementsOf: (other: T[]) => {\n assertEqual(array.length, other.length);\n assertTrue(array.every((ts) => other.some((o) => deepEquals(ts, o))));\n },\n containsExactlyElementsOf: (other: T[]) => {\n assertEqual(array.length, other.length);\n for (let i = 0; i < array.length; i++) {\n assertTrue(deepEquals(array[i], other[i]));\n }\n },\n containsExactly: (elem: T) => {\n assertEqual(array.length, 1);\n assertTrue(deepEquals(array[0], elem));\n },\n contains: (elem: T) => {\n assertTrue(array.some((a) => deepEquals(a, elem)));\n },\n containsOnlyOnceElementsOf: (other: T[]) => {\n assertTrue(\n other\n .map((o) => array.filter((a) => deepEquals(a, o)).length)\n .filter((a) => a === 1).length === other.length,\n );\n },\n containsAnyOf: (other: T[]) => {\n assertTrue(array.some((a) => other.some((o) => deepEquals(a, o))));\n },\n allMatch: (matches: (item: T) => boolean) => {\n assertTrue(array.every(matches));\n },\n anyMatches: (matches: (item: T) => boolean) => {\n assertTrue(array.some(matches));\n },\n allMatchAsync: async (\n matches: (item: T) => Promise<boolean>,\n ): Promise<void> => {\n for (const item of array) {\n assertTrue(await matches(item));\n }\n },\n };\n};\n","import type {\n AggregateStreamOptions,\n AggregateStreamResult,\n AppendToStreamOptions,\n AppendToStreamResult,\n EventStore,\n EventStoreReadEventMetadata,\n ReadStreamOptions,\n ReadStreamResult,\n} from '../eventStore';\nimport type { Event, EventMetaDataOf } from '../typing';\n\nexport type TestEventStream<EventType extends Event = Event> = [\n string,\n EventType[],\n];\n\nexport type EventStoreWrapper<Store extends EventStore> = Store & {\n appendedEvents: Map<string, TestEventStream>;\n setup<EventType extends Event>(\n streamName: string,\n events: EventType[],\n ): Promise<AppendToStreamResult>;\n};\n\nexport const WrapEventStore = <Store extends EventStore>(\n eventStore: Store,\n): EventStoreWrapper<Store> => {\n const appendedEvents = new Map<string, TestEventStream>();\n\n const wrapped = {\n ...eventStore,\n aggregateStream<State, EventType extends Event>(\n streamName: string,\n options: AggregateStreamOptions<State, EventType>,\n ): Promise<AggregateStreamResult<State>> {\n return eventStore.aggregateStream(streamName, options);\n },\n\n async readStream<EventType extends Event>(\n streamName: string,\n options?: ReadStreamOptions<EventType>,\n ): Promise<\n ReadStreamResult<\n EventType,\n EventStoreReadEventMetadata<Store> & EventMetaDataOf<EventType>\n >\n > {\n return (await eventStore.readStream(\n streamName,\n options,\n )) as ReadStreamResult<\n EventType,\n EventStoreReadEventMetadata<Store> & EventMetaDataOf<EventType>\n >;\n },\n\n appendToStream: async <EventType extends Event>(\n streamName: string,\n events: EventType[],\n options?: AppendToStreamOptions<EventType>,\n ): Promise<AppendToStreamResult> => {\n const result = await eventStore.appendToStream(\n streamName,\n events,\n options,\n );\n\n const currentStream = appendedEvents.get(streamName) ?? [streamName, []];\n\n appendedEvents.set(streamName, [\n streamName,\n [...currentStream[1], ...events],\n ]);\n\n return result;\n },\n\n appendedEvents,\n\n setup: async <EventType extends Event>(\n streamName: string,\n events: EventType[],\n ): Promise<AppendToStreamResult> => {\n return eventStore.appendToStream(streamName, events);\n },\n\n // streamEvents: (): ReadableStream<\n // // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents\n // ReadEvent<Event, ReadEventMetadataType> | GlobalSubscriptionEvent\n // > => {\n // return eventStore.streamEvents();\n // },\n };\n\n return wrapped as EventStoreWrapper<Store>;\n};\n","import { isNumber } from '@event-driven-io/emmett';\nimport type { NextFunction, Request, Response } from 'express';\nimport { ProblemDocument } from 'http-problem-details';\nimport { sendProblem, type ErrorToProblemDetailsMapping } from '..';\n\nexport const problemDetailsMiddleware =\n (mapError?: ErrorToProblemDetailsMapping) =>\n (\n error: Error,\n request: Request,\n response: Response,\n _next: NextFunction,\n ): void => {\n let problemDetails: ProblemDocument | undefined;\n\n if (mapError) problemDetails = mapError(error, request);\n\n problemDetails =\n problemDetails ?? defaultErrorToProblemDetailsMapping(error);\n\n sendProblem(response, problemDetails.status, { problem: problemDetails });\n };\n\nexport const defaultErrorToProblemDetailsMapping = (\n error: Error,\n): ProblemDocument => {\n let statusCode = 500;\n\n if (\n 'errorCode' in error &&\n isNumber(error.errorCode) &&\n error.errorCode >= 100 &&\n error.errorCode < 600\n ) {\n statusCode = error.errorCode;\n }\n\n return new ProblemDocument({\n detail: error.message,\n status: statusCode,\n });\n};\n","import { type Brand, EmmettError } from '@event-driven-io/emmett';\nimport type { Request, Response } from 'express';\n\n//////////////////////////////////////\n/// ETAG\n//////////////////////////////////////\n\nexport const HeaderNames = {\n IF_MATCH: 'if-match',\n IF_NOT_MATCH: 'if-not-match',\n ETag: 'etag',\n};\n\nexport type WeakETag = Brand<`W/${string}`, 'ETag'>;\nexport type ETag = Brand<string, 'ETag'>;\n\nexport const WeakETagRegex = /W\\/\"(-?\\d+.*)\"/;\n\nexport const enum ETagErrors {\n WRONG_WEAK_ETAG_FORMAT = 'WRONG_WEAK_ETAG_FORMAT',\n MISSING_IF_MATCH_HEADER = 'MISSING_IF_MATCH_HEADER',\n MISSING_IF_NOT_MATCH_HEADER = 'MISSING_IF_NOT_MATCH_HEADER',\n}\n\nexport const isWeakETag = (etag: ETag): etag is WeakETag => {\n return WeakETagRegex.test(etag as string);\n};\n\nexport const getWeakETagValue = (etag: ETag): string => {\n const result = WeakETagRegex.exec(etag as string);\n if (result === null || result.length === 0) {\n throw new EmmettError({\n errorCode: EmmettError.Codes.ConcurrencyError,\n message: ETagErrors.WRONG_WEAK_ETAG_FORMAT,\n });\n }\n return result[1]!;\n};\n\nexport const toWeakETag = (value: number | bigint | string): WeakETag => {\n return `W/\"${value}\"` as WeakETag;\n};\n\nexport const getETagFromIfMatch = (request: Request): ETag => {\n const etag = request.headers[HeaderNames.IF_MATCH];\n\n if (etag === undefined) {\n throw new EmmettError({\n errorCode: EmmettError.Codes.ConcurrencyError,\n message: ETagErrors.MISSING_IF_MATCH_HEADER,\n });\n }\n\n return etag as ETag;\n};\n\nexport const getETagFromIfNotMatch = (request: Request): ETag => {\n const etag = request.headers[HeaderNames.IF_NOT_MATCH];\n\n if (etag === undefined) {\n throw new EmmettError({\n errorCode: EmmettError.Codes.ConcurrencyError,\n message: ETagErrors.MISSING_IF_NOT_MATCH_HEADER,\n });\n }\n\n return (Array.isArray(etag) ? etag[0] : etag) as ETag;\n};\n\nexport const setETag = (response: Response, etag: ETag): void => {\n response.setHeader(HeaderNames.ETag, etag as string);\n};\n\nexport const getETagValueFromIfMatch = (request: Request): string => {\n const eTagValue: ETag = getETagFromIfMatch(request);\n\n return isWeakETag(eTagValue)\n ? getWeakETagValue(eTagValue)\n : (eTagValue as string);\n};\n","import type { NextFunction, Request, Response } from 'express';\nimport {\n send,\n sendAccepted,\n sendCreated,\n sendProblem,\n type AcceptedHttpResponseOptions,\n type CreatedHttpResponseOptions,\n type HttpProblemResponseOptions,\n type HttpResponseOptions,\n type NoContentHttpResponseOptions,\n} from '.';\n\n// #region httpresponse-on\nexport type HttpResponse = (response: Response) => void;\n\nexport type HttpHandler<RequestType extends Request> = (\n request: RequestType,\n) => Promise<HttpResponse> | HttpResponse;\n\nexport const on =\n <RequestType extends Request>(handle: HttpHandler<RequestType>) =>\n async (\n request: RequestType,\n response: Response,\n _next: NextFunction,\n ): Promise<void> => {\n const setResponse = await Promise.resolve(handle(request));\n\n return setResponse(response);\n };\n// #endregion httpresponse-on\n\nexport const OK =\n (options?: HttpResponseOptions): HttpResponse =>\n (response: Response) => {\n send(response, 200, options);\n };\n\nexport const Created =\n (options: CreatedHttpResponseOptions): HttpResponse =>\n (response: Response) => {\n sendCreated(response, options);\n };\n\nexport const Accepted =\n (options: AcceptedHttpResponseOptions): HttpResponse =>\n (response: Response) => {\n sendAccepted(response, options);\n };\n\nexport const NoContent = (\n options?: NoContentHttpResponseOptions,\n): HttpResponse => HttpResponse(204, options);\n\nexport const HttpResponse =\n (statusCode: number, options?: HttpResponseOptions): HttpResponse =>\n (response: Response) => {\n send(response, statusCode, options);\n };\n\n/////////////////////\n// ERRORS\n/////////////////////\n\nexport const BadRequest = (\n options?: HttpProblemResponseOptions,\n): HttpResponse => HttpProblem(400, options);\n\nexport const Forbidden = (options?: HttpProblemResponseOptions): HttpResponse =>\n HttpProblem(403, options);\n\nexport const NotFound = (options?: HttpProblemResponseOptions): HttpResponse =>\n HttpProblem(404, options);\n\nexport const Conflict = (options?: HttpProblemResponseOptions): HttpResponse =>\n HttpProblem(409, options);\n\nexport const PreconditionFailed = (\n options: HttpProblemResponseOptions,\n): HttpResponse => HttpProblem(412, options);\n\nexport const HttpProblem =\n (statusCode: number, options?: HttpProblemResponseOptions): HttpResponse =>\n (response: Response) => {\n sendProblem(response, statusCode, options);\n };\n","import type { Request, Response } from 'express';\nimport { ProblemDocument } from 'http-problem-details';\nimport { setETag, type ETag } from './etag';\n\nexport type ErrorToProblemDetailsMapping = (\n error: Error,\n request: Request,\n) => ProblemDocument | undefined;\n\nexport type HttpResponseOptions = {\n body?: unknown;\n location?: string;\n eTag?: ETag;\n};\nexport const DefaultHttpResponseOptions: HttpResponseOptions = {};\n\nexport type HttpProblemResponseOptions = {\n location?: string;\n eTag?: ETag;\n} & Omit<HttpResponseOptions, 'body'> &\n (\n | {\n problem: ProblemDocument;\n }\n | { problemDetails: string }\n );\nexport const DefaultHttpProblemResponseOptions: HttpProblemResponseOptions = {\n problemDetails: 'Error occured!',\n};\n\nexport type CreatedHttpResponseOptions = (\n | {\n createdId: string;\n }\n | {\n createdId?: string;\n url: string;\n }\n) &\n HttpResponseOptions;\n\nexport const sendCreated = (\n response: Response,\n { eTag, ...options }: CreatedHttpResponseOptions,\n): void =>\n send(response, 201, {\n location:\n 'url' in options\n ? options.url\n : `${response.req.url}/${options.createdId}`,\n body: 'createdId' in options ? { id: options.createdId } : undefined,\n eTag,\n });\n\nexport type AcceptedHttpResponseOptions = {\n location: string;\n} & HttpResponseOptions;\n\nexport const sendAccepted = (\n response: Response,\n options: AcceptedHttpResponseOptions,\n): void => send(response, 202, options);\n\nexport type NoContentHttpResponseOptions = Omit<HttpResponseOptions, 'body'>;\n\nexport const send = (\n response: Response,\n statusCode: number,\n options?: HttpResponseOptions,\n): void => {\n const { location, body, eTag } = options ?? DefaultHttpResponseOptions;\n // HEADERS\n if (eTag) setETag(response, eTag);\n if (location) response.setHeader('Location', location);\n\n if (body) {\n response.statusCode = statusCode;\n response.send(body);\n } else {\n response.sendStatus(statusCode);\n }\n};\n\nexport const sendProblem = (\n response: Response,\n statusCode: number,\n options?: HttpProblemResponseOptions,\n): void => {\n options = options ?? DefaultHttpProblemResponseOptions;\n\n const { location, eTag } = options;\n\n const problemDetails =\n 'problem' in options\n ? options.problem\n : new ProblemDocument({\n detail: options.problemDetails,\n status: statusCode,\n });\n\n // HEADERS\n if (eTag) setETag(response, eTag);\n if (location) response.setHeader('Location', location);\n\n response.setHeader('Content-Type', 'application/problem+json');\n\n response.statusCode = statusCode;\n response.json(problemDetails);\n};\n","import supertest, { type Response } from 'supertest';\n\nimport type { EventStore } from '@event-driven-io/emmett';\nimport assert from 'assert';\nimport type { Application } from 'express';\nimport type { TestRequest } from './apiSpecification';\n\nexport type E2EResponseAssert = (response: Response) => boolean | void;\n\nexport type ApiE2ESpecificationAssert = [E2EResponseAssert];\n\nexport type ApiE2ESpecification = (...givenRequests: TestRequest[]) => {\n when: (setupRequest: TestRequest) => {\n then: (verify: ApiE2ESpecificationAssert) => Promise<void>;\n };\n};\n\nexport const ApiE2ESpecification = {\n for: <Store extends EventStore = EventStore>(\n getEventStore: () => Store,\n getApplication: (eventStore: Store) => Application,\n ): ApiE2ESpecification => {\n {\n return (...givenRequests: TestRequest[]) => {\n const eventStore = getEventStore();\n const application = getApplication(eventStore);\n\n return {\n when: (setupRequest: TestRequest) => {\n const handle = async () => {\n for (const requestFn of givenRequests) {\n await requestFn(supertest(application));\n }\n\n return setupRequest(supertest(application));\n };\n\n return {\n then: async (\n verify: ApiE2ESpecificationAssert,\n ): Promise<void> => {\n const response = await handle();\n\n verify.forEach((assertion) => {\n const succeeded = assertion(response);\n\n if (succeeded === false) assert.fail();\n });\n },\n };\n },\n };\n };\n }\n },\n};\n","import {\n WrapEventStore,\n assertEqual,\n assertFails,\n assertMatches,\n type Event,\n type EventStore,\n type TestEventStream,\n} from '@event-driven-io/emmett';\nimport type { Application } from 'express';\nimport type { ProblemDocument } from 'http-problem-details';\nimport type { Response, Test } from 'supertest';\nimport supertest from 'supertest';\nimport type TestAgent from 'supertest/lib/agent';\n\n////////////////////////////////\n/////////// Setup\n////////////////////////////////\n\nexport type TestRequest = (request: TestAgent<supertest.Test>) => Test;\n\nexport const existingStream = <EventType extends Event = Event>(\n streamId: string,\n events: EventType[],\n): TestEventStream<EventType> => {\n return [streamId, events];\n};\n\n////////////////////////////////\n/////////// Asserts\n////////////////////////////////\n\nexport type ResponseAssert = (response: Response) => boolean | void;\n\nexport type ApiSpecificationAssert<EventType extends Event = Event> =\n | TestEventStream<EventType>[]\n | ResponseAssert\n | [ResponseAssert, ...TestEventStream<EventType>[]];\n\nexport const expect = <EventType extends Event = Event>(\n streamId: string,\n events: EventType[],\n): TestEventStream<EventType> => {\n return [streamId, events];\n};\n\nexport const expectNewEvents = <EventType extends Event = Event>(\n streamId: string,\n events: EventType[],\n): TestEventStream<EventType> => {\n return [streamId, events];\n};\n\nexport const expectResponse =\n <Body = unknown>(\n statusCode: number,\n options?: { body?: Body; headers?: { [index: string]: string } },\n ) =>\n (response: Response): void => {\n const { body, headers } = options ?? {};\n assertEqual(statusCode, response.statusCode, \"Response code doesn't match\");\n if (body) assertMatches(response.body, body);\n if (headers) assertMatches(response.headers, headers);\n };\n\nexport const expectError = (\n errorCode: number,\n problemDetails?: Partial<ProblemDocument>,\n) =>\n expectResponse(\n errorCode,\n problemDetails ? { body: problemDetails } : undefined,\n );\n\n////////////////////////////////\n/////////// Api Specification\n////////////////////////////////\n\nexport type ApiSpecification<EventType extends Event = Event> = (\n ...givenStreams: TestEventStream<EventType>[]\n) => {\n when: (setupRequest: TestRequest) => {\n then: (verify: ApiSpecificationAssert<EventType>) => Promise<void>;\n };\n};\n\nexport const ApiSpecification = {\n for: <EventType extends Event = Event, Store extends EventStore = EventStore>(\n getEventStore: () => Store,\n getApplication: (eventStore: Store) => Application,\n ): ApiSpecification<EventType> => {\n {\n return (...givenStreams: TestEventStream<EventType>[]) => {\n const eventStore = WrapEventStore(getEventStore());\n const application = getApplication(eventStore);\n\n return {\n when: (setupRequest: TestRequest) => {\n const handle = async () => {\n for (const [streamName, events] of givenStreams) {\n await eventStore.setup(streamName, events);\n }\n\n return setupRequest(supertest(application));\n };\n\n return {\n then: async (\n verify: ApiSpecificationAssert<EventType>,\n ): Promise<void> => {\n const response = await handle();\n\n if (typeof verify === 'function') {\n const succeeded = verify(response);\n\n if (succeeded === false) assertFails();\n } else if (Array.isArray(verify)) {\n const [first, ...rest] = verify;\n\n if (typeof first === 'function') {\n const succeeded = first(response);\n\n if (succeeded === false) assertFails();\n }\n\n const events = typeof first === 'function' ? rest : verify;\n\n assertMatches(\n Array.from(eventStore.appendedEvents.values()),\n events,\n );\n }\n },\n };\n },\n };\n };\n }\n },\n};\n"]}
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/emmett/emmett/src/packages/emmett-expressjs/dist/index.cjs","../src/index.ts","../src/application.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/processors/processors.ts","../../emmett/src/eventStore/projections/inMemory/inMemoryProjectionSpec.ts","../../emmett/src/workflows/handleWorkflow.ts","../../emmett/src/typing/index.ts","../../emmett/src/serialization/json/JSONParser.ts","../../emmett/src/utils/strings/hashText.ts","../../emmett/src/testing/assertions.ts","../../emmett/src/testing/wrapEventStore.ts","../src/middlewares/problemDetailsMiddleware.ts","../src/etag.ts","../src/handler.ts","../src/responses.ts","../src/testing/apiE2ESpecification.ts","../src/testing/apiSpecification.ts"],"names":["message","ETagErrors","supertest"],"mappings":"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","file":"/home/runner/work/emmett/emmett/src/packages/emmett-expressjs/dist/index.cjs","sourcesContent":[null,"import 'express-async-errors';\n\nexport * from './application';\nexport * from './etag';\nexport * from './handler';\nexport * from './responses';\nexport * from './testing';\n","import express, { Router, type Application } from 'express';\nimport 'express-async-errors';\nimport http from 'http';\nimport { problemDetailsMiddleware } from './middlewares/problemDetailsMiddleware';\nimport type { ErrorToProblemDetailsMapping } from './responses';\n\n// #region web-api-setup\nexport type WebApiSetup = (router: Router) => void;\n// #endregion web-api-setup\n\nexport type ApplicationOptions = {\n apis: WebApiSetup[];\n mapError?: ErrorToProblemDetailsMapping;\n enableDefaultExpressEtag?: boolean;\n disableJsonMiddleware?: boolean;\n disableUrlEncodingMiddleware?: boolean;\n disableProblemDetailsMiddleware?: boolean;\n};\n\nexport const getApplication = (options: ApplicationOptions) => {\n const app: Application = express();\n\n const {\n apis,\n mapError,\n enableDefaultExpressEtag,\n disableJsonMiddleware,\n disableUrlEncodingMiddleware,\n disableProblemDetailsMiddleware,\n } = options;\n\n const router = Router();\n\n // disabling default etag behaviour\n // to use etags in if-match and if-not-match headers\n app.set('etag', enableDefaultExpressEtag ?? false);\n\n // add json middleware\n if (!disableJsonMiddleware) app.use(express.json());\n\n // enable url encoded urls and bodies\n if (!disableUrlEncodingMiddleware)\n app.use(\n express.urlencoded({\n extended: true,\n }),\n );\n\n for (const api of apis) {\n api(router);\n }\n app.use(router);\n\n // add problem details middleware\n if (!disableProblemDetailsMiddleware)\n app.use(problemDetailsMiddleware(mapError));\n\n return app;\n};\n\nexport type StartApiOptions = {\n port?: number;\n};\n\nexport const startAPI = (\n app: Application,\n options: StartApiOptions = { port: 3000 },\n) => {\n const { port } = options;\n const server = http.createServer(app);\n\n server.on('listening', () => {\n console.info('server up listening');\n });\n\n return server.listen(port);\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 { bigIntProcessorCheckpoint } from '../processors';\nimport type { ProjectionRegistration } from '../projections';\nimport type {\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<EventType, EventPayloadType>,\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<EventType, EventPayloadType>,\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 globalPosition = BigInt(getAllEventsCount() + index + 1);\n const metadata: ReadEventMetadataWithGlobalPosition = {\n streamName,\n messageId: uuid(),\n streamPosition: BigInt(currentEvents.length + index + 1),\n globalPosition,\n checkpoint: bigIntProcessorCheckpoint(globalPosition),\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 { 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 Brand,\n type CanHandle,\n type DefaultRecord,\n type Event,\n type Message,\n type MessageHandlerResult,\n type RecordedMessage,\n type SingleMessageHandlerWithContext,\n type SingleRecordedMessageHandlerWithContext,\n} from '../typing';\nimport { bigInt } from '../utils';\nimport { onShutdown } from '../utils/shutdown';\n\nexport type CurrentMessageProcessorPosition =\n | { lastCheckpoint: ProcessorCheckpoint }\n | 'BEGINNING'\n | 'END';\n\nexport type GetCheckpoint<\n MessageType extends AnyMessage = AnyMessage,\n MessageMetadataType extends AnyReadEventMetadata = AnyReadEventMetadata,\n> = (\n message: RecordedMessage<MessageType, MessageMetadataType>,\n) => ProcessorCheckpoint | null;\n\nexport const getCheckpoint = <\n MessageType extends AnyMessage = AnyMessage,\n MessageMetadataType extends AnyReadEventMetadata = AnyReadEventMetadata,\n>(\n message: RecordedMessage<MessageType, MessageMetadataType>,\n): ProcessorCheckpoint | null => {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access\n return message.metadata.checkpoint;\n};\n\nexport const wasMessageHandled = <\n MessageType extends AnyMessage = AnyMessage,\n MessageMetadataType extends AnyReadEventMetadata = AnyReadEventMetadata,\n>(\n message: RecordedMessage<MessageType, MessageMetadataType>,\n checkpoint: ProcessorCheckpoint | null,\n): boolean => {\n //TODO Make it smarter\n const messageCheckpoint = getCheckpoint(message);\n\n return (\n messageCheckpoint !== null &&\n messageCheckpoint !== undefined &&\n checkpoint !== null &&\n checkpoint !== undefined &&\n messageCheckpoint <= checkpoint\n );\n};\n\nexport type MessageProcessorStartFrom =\n | CurrentMessageProcessorPosition\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> = {\n id: string;\n instanceId: string;\n type: string;\n canHandle?: string[];\n init: (options: Partial<HandlerContext>) => Promise<void>;\n start: (\n options: Partial<HandlerContext>,\n ) => Promise<CurrentMessageProcessorPosition | 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> = {\n read: ReadProcessorCheckpoint<HandlerContext>;\n store: StoreProcessorCheckpoint<\n MessageType,\n MessageMetadataType,\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> = {\n type?: string;\n processorId: string;\n processorInstanceId?: string;\n version?: number;\n partition?: string;\n startFrom?: MessageProcessorStartFrom;\n stopAfter?: (\n message: RecordedMessage<MessageType, MessageMetadataType>,\n ) => boolean;\n processingScope?: MessageProcessingScope<HandlerContext>;\n checkpoints?: Checkpointer<MessageType, MessageMetadataType, HandlerContext>;\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 MessagePayloadType extends AnyMessage = MessageType,\n> = BaseMessageProcessorOptions<\n MessageType,\n MessageMetadataType,\n HandlerContext\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 EventPayloadType extends Event = EventType,\n> = Omit<\n BaseMessageProcessorOptions<EventType, MessageMetadataType, HandlerContext>,\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 ProcessorCheckpoint = Brand<string, 'ProcessorCheckpoint'>;\n\nexport const bigIntProcessorCheckpoint = (value: bigint): ProcessorCheckpoint =>\n bigInt.toNormalizedString(value) as ProcessorCheckpoint;\n\nexport const parseBigIntProcessorCheckpoint = (\n value: ProcessorCheckpoint,\n): bigint => BigInt(value);\n\nexport type ReadProcessorCheckpointResult = {\n lastCheckpoint: ProcessorCheckpoint | null;\n};\n\nexport type ReadProcessorCheckpoint<\n HandlerContext extends DefaultRecord = DefaultRecord,\n> = (\n options: { processorId: string; partition?: string },\n context: HandlerContext,\n) => Promise<ReadProcessorCheckpointResult>;\n\nexport type StoreProcessorCheckpointResult =\n | {\n success: true;\n newCheckpoint: ProcessorCheckpoint | null;\n }\n | { success: false; reason: 'IGNORED' | 'MISMATCH' | 'CURRENT_AHEAD' };\n\nexport type StoreProcessorCheckpoint<\n MessageType extends Message = AnyMessage,\n MessageMetadataType extends AnyReadEventMetadata = AnyReadEventMetadata,\n HandlerContext extends DefaultRecord | undefined = undefined,\n> = (\n options: {\n message: RecordedMessage<MessageType, MessageMetadataType>;\n processorId: string;\n version: number | undefined;\n lastCheckpoint: ProcessorCheckpoint | null;\n partition?: string;\n },\n context: HandlerContext,\n) => Promise<StoreProcessorCheckpointResult>;\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 MessagePayloadType extends Message = MessageType,\n>(\n options: ReactorOptions<\n MessageType,\n MessageMetadataType,\n HandlerContext,\n MessagePayloadType\n >,\n): MessageProcessor<MessageType, MessageMetadataType, HandlerContext> => {\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: ProcessorCheckpoint | 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 canHandle,\n init,\n start: async (\n startOptions: Partial<HandlerContext>,\n ): Promise<CurrentMessageProcessorPosition | undefined> => {\n if (isActive) return;\n\n await init(startOptions);\n\n isActive = true;\n\n closeSignal = onShutdown(() => close(startOptions));\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 =\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 EventPayloadType extends Event = EventType,\n>(\n options: ProjectorOptions<\n EventType,\n EventMetaDataType,\n HandlerContext,\n EventPayloadType\n >,\n): MessageProcessor<EventType, EventMetaDataType, HandlerContext> => {\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 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","import { v4 as uuid } from 'uuid';\nimport {\n handleInMemoryProjections,\n type InMemoryProjectionDefinition,\n} from '.';\nimport { bigIntProcessorCheckpoint } 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 {\n CombinedReadEventMetadata,\n Event,\n ReadEvent,\n} 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 checkpoint: bigIntProcessorCheckpoint(++globalPosition),\n 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 {\n canCreateEventStoreSession,\n isExpectedVersionConflictError,\n NO_CONCURRENCY_CHECK,\n nulloSessionFactory,\n STREAM_DOES_NOT_EXIST,\n type AppendStreamResultOfEventStore,\n type AppendToStreamOptions,\n type EventStore,\n type EventStoreSession,\n type ExpectedStreamVersion,\n type ReadStreamOptions,\n} from '../eventStore';\nimport type {\n AnyCommand,\n AnyEvent,\n AnyReadEventMetadata,\n Event,\n RecordedMessage,\n} from '../typing';\nimport { asyncRetry, NoRetries, type AsyncRetryOptions } from '../utils';\nimport type {\n WorkflowEvent,\n WorkflowInputMessageMetadata,\n WorkflowMessageAction,\n} from './workflow';\nimport type { WorkflowOptions } from './workflowProcessor';\n\nexport const WorkflowHandlerStreamVersionConflictRetryOptions: AsyncRetryOptions =\n {\n retries: 3,\n minTimeout: 100,\n factor: 1.5,\n shouldRetryError: isExpectedVersionConflictError,\n };\n\nexport type WorkflowHandlerRetryOptions =\n | AsyncRetryOptions\n | { onVersionConflict: true | number | AsyncRetryOptions };\n\nconst fromWorkflowHandlerRetryOptions = (\n retryOptions: WorkflowHandlerRetryOptions | undefined,\n): AsyncRetryOptions => {\n if (retryOptions === undefined) return NoRetries;\n\n if ('onVersionConflict' in retryOptions) {\n if (typeof retryOptions.onVersionConflict === 'boolean')\n return WorkflowHandlerStreamVersionConflictRetryOptions;\n else if (typeof retryOptions.onVersionConflict === 'number')\n return {\n ...WorkflowHandlerStreamVersionConflictRetryOptions,\n retries: retryOptions.onVersionConflict,\n };\n else return retryOptions.onVersionConflict;\n }\n\n return retryOptions;\n};\n\n// #region workflow-handler\nexport type WorkflowHandlerResult<\n Output extends AnyEvent | AnyCommand,\n Store extends EventStore,\n> = AppendStreamResultOfEventStore<Store> & {\n newMessages: Output[];\n};\n\nexport type WorkflowHandleOptions<Store extends EventStore> = Parameters<\n Store['appendToStream']\n>[2] & {\n expectedStreamVersion?: ExpectedStreamVersion;\n retry?: WorkflowHandlerRetryOptions;\n};\n\ntype WorkflowInternalState<State> = {\n userState: State;\n processedInputIds: Set<string>;\n};\n\nconst emptyHandlerResult = <\n Output extends AnyEvent | AnyCommand,\n Store extends EventStore,\n>(\n nextExpectedStreamVersion: bigint = 0n,\n): WorkflowHandlerResult<Output, Store> =>\n ({\n newMessages: [] as Output[],\n createdNewStream: false,\n nextExpectedStreamVersion,\n }) as unknown as WorkflowHandlerResult<Output, Store>;\n\nconst createInputMetadata = (\n originalMessageId: string,\n action: Extract<WorkflowMessageAction, 'InitiatedBy' | 'Received'>,\n): WorkflowInputMessageMetadata => ({\n originalMessageId,\n input: true,\n action,\n});\n\nconst tagOutputMessage = <Output extends AnyEvent | AnyCommand>(\n msg: Output,\n action: Extract<WorkflowMessageAction, 'Sent' | 'Published' | 'Scheduled'>,\n): Output => {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n const existingMetadata =\n 'metadata' in msg && msg.metadata ? msg.metadata : {};\n return {\n ...msg,\n metadata: {\n ...existingMetadata,\n action,\n },\n } as Output;\n};\n\nconst createWrappedInitialState = <State>(initialState: () => State) => {\n return (): WorkflowInternalState<State> => ({\n userState: initialState(),\n processedInputIds: new Set(),\n });\n};\n\nconst createWrappedEvolve = <\n Input extends AnyEvent | AnyCommand,\n Output extends AnyEvent | AnyCommand,\n State,\n>(\n evolve: (state: State, event: WorkflowEvent<Input | Output>) => State,\n workflowName: string,\n separateInputInboxFromProcessing: boolean,\n) => {\n return (\n state: WorkflowInternalState<State>,\n event: WorkflowEvent<Input | Output>,\n ): WorkflowInternalState<State> => {\n const metadata = (event as Record<string, unknown>).metadata as\n | Record<string, unknown>\n | undefined;\n\n // Track processed inputs for idempotency\n let processedInputIds = state.processedInputIds;\n if (\n metadata?.input === true &&\n typeof metadata?.originalMessageId === 'string'\n ) {\n processedInputIds = new Set(state.processedInputIds);\n processedInputIds.add(metadata.originalMessageId);\n }\n\n // In separated inbox mode, don't apply inputs to state - they're just sitting in inbox\n // Only outputs (from processing) should update state\n if (separateInputInboxFromProcessing && metadata?.input === true) {\n return {\n userState: state.userState,\n processedInputIds,\n };\n }\n\n // Strip workflow prefix from input event types\n const eventType = event.type as string;\n const eventForEvolve = eventType.startsWith(`${workflowName}:`)\n ? ({\n ...event,\n type: eventType.replace(`${workflowName}:`, ''),\n } as WorkflowEvent<Input | Output>)\n : event;\n\n return {\n userState: evolve(state.userState, eventForEvolve),\n processedInputIds,\n };\n };\n};\n\nexport const workflowStreamName = ({\n workflowName,\n workflowId,\n}: {\n workflowName: string;\n workflowId: string;\n}) => `emt:workflow:${workflowName}:${workflowId}`;\n\nexport const WorkflowHandler =\n <\n Input extends AnyEvent | AnyCommand,\n State,\n Output extends AnyEvent | AnyCommand,\n MessageMetadataType extends AnyReadEventMetadata = AnyReadEventMetadata,\n StoredMessage extends AnyEvent | AnyCommand = Output,\n >(\n options: WorkflowOptions<\n Input,\n State,\n Output,\n MessageMetadataType,\n StoredMessage\n > & {\n retry?: WorkflowHandlerRetryOptions;\n },\n ) =>\n async <Store extends EventStore>(\n store: Store,\n message: Input | RecordedMessage<Input, MessageMetadataType>,\n handleOptions?: WorkflowHandleOptions<Store>,\n ): Promise<WorkflowHandlerResult<Output, Store>> =>\n asyncRetry(\n async () => {\n const result = await withSession<\n Store,\n WorkflowHandlerResult<Output, Store>\n >(store, async ({ eventStore }) => {\n const {\n workflow: { evolve, initialState, decide, name: workflowName },\n getWorkflowId,\n } = options;\n\n const inputMessageId =\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n ('metadata' in message && message.metadata?.messageId\n ? // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n (message.metadata.messageId as string | undefined)\n : undefined) ?? uuid();\n\n const messageWithMetadata: RecordedMessage<\n Input,\n MessageMetadataType\n > = {\n ...message,\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n metadata: {\n messageId: inputMessageId,\n ...(message as RecordedMessage<Input, MessageMetadataType>)\n .metadata,\n },\n } as RecordedMessage<Input, MessageMetadataType>;\n\n const workflowId = getWorkflowId(messageWithMetadata);\n\n if (!workflowId) {\n return emptyHandlerResult<Output, Store>();\n }\n\n const streamName = options.mapWorkflowId\n ? options.mapWorkflowId(workflowId)\n : workflowStreamName({ workflowName, workflowId });\n\n const messageType = messageWithMetadata.type as string;\n const hasWorkflowPrefix = messageType.startsWith(`${workflowName}:`);\n\n // Separated inbox mode: store-only path (no prefix = external input)\n if (options.separateInputInboxFromProcessing && !hasWorkflowPrefix) {\n const inputMetadata = createInputMetadata(\n inputMessageId,\n 'InitiatedBy',\n );\n\n const inputToStore = {\n type: `${workflowName}:${messageWithMetadata.type}`,\n data: messageWithMetadata.data,\n kind: messageWithMetadata.kind,\n metadata: inputMetadata,\n } as StoredMessage;\n\n const appendResult = await eventStore.appendToStream(\n streamName,\n [inputToStore] as unknown as Event[],\n {\n ...(handleOptions as AppendToStreamOptions<\n Event,\n StoredMessage & Event\n >),\n expectedStreamVersion:\n handleOptions?.expectedStreamVersion ?? NO_CONCURRENCY_CHECK,\n },\n );\n\n return {\n ...appendResult,\n newMessages: [] as Output[],\n } as unknown as WorkflowHandlerResult<Output, Store>;\n }\n\n // Wrap the evolve and initialState for idempotency tracking\n const wrappedInitialState = createWrappedInitialState(initialState);\n const wrappedEvolve = createWrappedEvolve(\n evolve,\n workflowName,\n options.separateInputInboxFromProcessing ?? false,\n ) as (\n state: WorkflowInternalState<State>,\n event: WorkflowEvent<Input | Output>,\n ) => WorkflowInternalState<State>;\n\n // 1. Aggregate the stream\n const aggregationResult = await eventStore.aggregateStream<\n WorkflowInternalState<State>,\n WorkflowEvent<Input | Output>,\n StoredMessage & Event\n >(streamName, {\n evolve: wrappedEvolve,\n initialState: wrappedInitialState,\n read: {\n ...(handleOptions as ReadStreamOptions<\n WorkflowEvent<Input | Output>,\n StoredMessage & Event\n >),\n // expected stream version is passed to fail fast\n // if stream is in the wrong state\n\n expectedStreamVersion:\n handleOptions?.expectedStreamVersion ?? NO_CONCURRENCY_CHECK,\n },\n });\n\n // 2. Use the aggregate state\n\n const { currentStreamVersion } = aggregationResult;\n\n const { userState: state, processedInputIds } =\n aggregationResult.state;\n\n // Idempotency: skip if this input was already processed\n\n if (processedInputIds.has(inputMessageId)) {\n return emptyHandlerResult<Output, Store>(currentStreamVersion);\n }\n\n // 3. Run business logic\n // Strip workflow prefix from message type if present (for separated inbox processing)\n const messageForDecide = hasWorkflowPrefix\n ? ({\n ...messageWithMetadata,\n type: messageType.replace(`${workflowName}:`, ''),\n } as Input)\n : (messageWithMetadata as Input);\n\n const result = decide(messageForDecide, state);\n\n const inputMetadata = createInputMetadata(\n inputMessageId,\n aggregationResult.streamExists ? 'Received' : 'InitiatedBy',\n );\n\n const inputToStore = {\n type: `${workflowName}:${messageWithMetadata.type}`,\n data: messageWithMetadata.data,\n kind: messageWithMetadata.kind,\n metadata: inputMetadata,\n } as StoredMessage;\n\n const outputMessages = (\n Array.isArray(result) ? result : [result]\n ).filter((msg): msg is Output => msg !== undefined && msg !== null);\n\n const outputCommandTypes = options.outputs?.commands ?? [];\n const taggedOutputMessages = outputMessages.map((msg) => {\n const action: WorkflowMessageAction = outputCommandTypes.includes(\n msg.type as string,\n )\n ? 'Sent'\n : 'Published';\n return tagOutputMessage(msg, action);\n });\n\n const messagesToAppend =\n options.separateInputInboxFromProcessing && hasWorkflowPrefix\n ? [...taggedOutputMessages] // input already in stream\n : [inputToStore, ...taggedOutputMessages]; // normal: store input + outputs\n\n // If there are no messages to append, return early with current state\n if (messagesToAppend.length === 0) {\n return emptyHandlerResult<Output, Store>(currentStreamVersion);\n }\n\n // Either use:\n // - provided expected stream version,\n // - current stream version got from stream aggregation,\n // - or expect stream not to exists otherwise.\n\n const expectedStreamVersion: ExpectedStreamVersion =\n handleOptions?.expectedStreamVersion ??\n (aggregationResult.streamExists\n ? currentStreamVersion\n : STREAM_DOES_NOT_EXIST);\n\n // 4. Append result to the stream\n const appendResult = await eventStore.appendToStream(\n streamName,\n // TODO: Fix this cast\n messagesToAppend as unknown as Event[],\n {\n ...(handleOptions as AppendToStreamOptions<\n Event,\n StoredMessage & Event\n >),\n expectedStreamVersion,\n },\n );\n\n // 5. Return result with output messages only\n return {\n ...appendResult,\n newMessages: outputMessages,\n } as unknown as WorkflowHandlerResult<Output, Store>;\n });\n\n return result;\n },\n fromWorkflowHandlerRetryOptions(\n handleOptions && 'retry' in handleOptions\n ? handleOptions.retry\n : options.retry,\n ),\n );\n// #endregion stream-handler\n\nconst withSession = <EventStoreType extends EventStore, T = unknown>(\n eventStore: EventStoreType,\n callback: (session: EventStoreSession<EventStoreType>) => Promise<T>,\n) => {\n const sessionFactory = canCreateEventStoreSession<EventStoreType>(eventStore)\n ? eventStore\n : nulloSessionFactory<EventStoreType>(eventStore);\n\n return sessionFactory.withSession(callback);\n};\n","export * from './deepReadonly';\n\nexport * from './command';\nexport * from './event';\nexport * from './message';\nexport * from './messageHandling';\n\nexport * from './decider';\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","export class ParseError extends Error {\n constructor(text: string) {\n super(`Cannot parse! ${text}`);\n }\n}\n\nexport type Mapper<From, To = From> =\n | ((value: unknown) => To)\n | ((value: Partial<From>) => To)\n | ((value: From) => To)\n | ((value: Partial<To>) => To)\n | ((value: To) => To)\n | ((value: Partial<To | From>) => To)\n | ((value: To | From) => To);\n\nexport type MapperArgs<From, To = From> = Partial<From> &\n From &\n Partial<To> &\n To;\n\nexport type ParseOptions<From, To = From> = {\n reviver?: (key: string, value: unknown) => unknown;\n map?: Mapper<From, To>;\n typeCheck?: <To>(value: unknown) => value is To;\n};\n\nexport type StringifyOptions<From, To = From> = {\n map?: Mapper<From, To>;\n};\n\nexport const JSONParser = {\n stringify: <From, To = From>(\n value: From,\n options?: StringifyOptions<From, To>,\n ) => {\n return JSON.stringify(\n options?.map ? options.map(value as MapperArgs<From, To>) : value,\n //TODO: Consider adding support to DateTime and adding specific format to mark that's a bigint\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n (_, v) => (typeof v === 'bigint' ? v.toString() : v),\n );\n },\n parse: <From, To = From>(\n text: string,\n options?: ParseOptions<From, To>,\n ): To | undefined => {\n const parsed: unknown = JSON.parse(text, options?.reviver);\n\n if (options?.typeCheck && !options?.typeCheck<To>(parsed))\n throw new ParseError(text);\n\n return options?.map\n ? options.map(parsed as MapperArgs<From, To>)\n : (parsed as To | undefined);\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 { JSONParser } from '../serialization';\nimport type { DefaultRecord } from '../typing';\nimport { deepEquals } from '../utils';\n\nexport class AssertionError extends Error {\n constructor(message: string) {\n super(message);\n }\n}\n\nexport const isSubset = (superObj: unknown, subObj: unknown): boolean => {\n const sup = superObj as DefaultRecord;\n const sub = subObj as DefaultRecord;\n\n assertOk(sup);\n assertOk(sub);\n\n return Object.keys(sub).every((ele: string) => {\n if (sub[ele] !== null && typeof sub[ele] == 'object') {\n return isSubset(sup[ele], sub[ele]);\n }\n return sub[ele] === sup[ele];\n });\n};\n\nexport const assertFails = (message?: string) => {\n throw new AssertionError(message ?? 'That should not ever happened, right?');\n};\n\nexport const assertThrowsAsync = async <TError extends Error>(\n fun: () => Promise<void>,\n errorCheck?: (error: Error) => boolean,\n): Promise<TError> => {\n try {\n await fun();\n } catch (error) {\n const typedError = error as TError;\n if (typedError instanceof AssertionError || !errorCheck) {\n assertFalse(\n typedError instanceof AssertionError,\n \"Function didn't throw expected error\",\n );\n return typedError;\n }\n\n assertTrue(\n errorCheck(typedError),\n `Error doesn't match the expected condition: ${JSONParser.stringify(error)}`,\n );\n\n return typedError;\n }\n throw new AssertionError(\"Function didn't throw expected error\");\n};\n\nexport const assertThrows = <TError extends Error>(\n fun: () => void,\n errorCheck?: (error: Error) => boolean,\n): TError => {\n try {\n fun();\n } catch (error) {\n const typedError = error as TError;\n\n if (errorCheck) {\n assertTrue(\n errorCheck(typedError),\n `Error doesn't match the expected condition: ${JSONParser.stringify(error)}`,\n );\n } else if (typedError instanceof AssertionError) {\n assertFalse(\n typedError instanceof AssertionError,\n \"Function didn't throw expected error\",\n );\n }\n\n return typedError;\n }\n throw new AssertionError(\"Function didn't throw expected error\");\n};\n\nexport const assertDoesNotThrow = <TError extends Error>(\n fun: () => void,\n errorCheck?: (error: Error) => boolean,\n): TError | null => {\n try {\n fun();\n return null;\n } catch (error) {\n const typedError = error as TError;\n\n if (errorCheck) {\n assertFalse(\n errorCheck(typedError),\n `Error matching the expected condition was thrown!: ${JSONParser.stringify(error)}`,\n );\n } else {\n assertFails(`Function threw an error: ${JSONParser.stringify(error)}`);\n }\n\n return typedError;\n }\n};\n\nexport const assertRejects = async <T, TError extends Error = Error>(\n promise: Promise<T>,\n errorCheck?: ((error: TError) => boolean) | TError,\n) => {\n try {\n await promise;\n throw new AssertionError(\"Function didn't throw expected error\");\n } catch (error) {\n if (!errorCheck) return;\n\n if (errorCheck instanceof Error) assertDeepEqual(error, errorCheck);\n else assertTrue(errorCheck(error as TError));\n }\n};\n\nexport const assertMatches = (\n actual: unknown,\n expected: unknown,\n message?: string,\n) => {\n if (!isSubset(actual, expected))\n throw new AssertionError(\n message ??\n `subObj:\\n${JSONParser.stringify(expected)}\\nis not subset of\\n${JSONParser.stringify(actual)}`,\n );\n};\n\nexport const assertDeepEqual = <T = unknown>(\n actual: T,\n expected: T,\n message?: string,\n) => {\n if (!deepEquals(actual, expected))\n throw new AssertionError(\n message ??\n `subObj:\\n${JSONParser.stringify(expected)}\\nis not equal to\\n${JSONParser.stringify(actual)}`,\n );\n};\n\nexport const assertNotDeepEqual = <T = unknown>(\n actual: T,\n expected: T,\n message?: string,\n) => {\n if (deepEquals(actual, expected))\n throw new AssertionError(\n message ??\n `subObj:\\n${JSONParser.stringify(expected)}\\nis equals to\\n${JSONParser.stringify(actual)}`,\n );\n};\n\nexport const assertThat = <T>(item: T) => {\n return {\n isEqualTo: (other: T) => assertTrue(deepEquals(item, other)),\n };\n};\n\nexport const assertDefined = (\n value: unknown,\n message?: string | Error,\n): asserts value => {\n assertOk(value, message instanceof Error ? message.message : message);\n};\n\nexport function assertFalse(\n condition: boolean,\n message?: string,\n): asserts condition is false {\n if (condition !== false)\n throw new AssertionError(message ?? `Condition is true`);\n}\n\nexport function assertTrue(\n condition: boolean,\n message?: string,\n): asserts condition is true {\n if (condition !== true)\n throw new AssertionError(message ?? `Condition is false`);\n}\n\n// TODO: replace with assertDefined\nexport function assertOk<T>(\n obj: T | null | undefined,\n message?: string,\n): asserts obj is T {\n if (!obj) throw new AssertionError(message ?? `Condition is not truthy`);\n}\n\nexport function assertEqual<T>(\n expected: T | null | undefined,\n actual: T | null | undefined,\n message?: string,\n): void {\n if (expected !== actual)\n throw new AssertionError(\n `${message ?? 'Objects are not equal'}:\\nExpected: ${JSONParser.stringify(expected)}\\nActual: ${JSONParser.stringify(actual)}`,\n );\n}\n\nexport function assertNotEqual<T>(\n obj: T | null | undefined,\n other: T | null | undefined,\n message?: string,\n): void {\n if (obj === other)\n throw new AssertionError(\n message ?? `Objects are equal: ${JSONParser.stringify(obj)}`,\n );\n}\n\nexport function assertIsNotNull<T extends object | bigint>(\n result: T | null,\n): asserts result is T {\n assertNotEqual(result, null);\n assertOk(result);\n}\n\nexport function assertIsNull<T extends object>(\n result: T | null,\n): asserts result is null {\n assertEqual(result, null);\n}\n\ntype Call = {\n arguments: unknown[];\n result: unknown;\n target: unknown;\n this: unknown;\n};\n\nexport type ArgumentMatcher = (arg: unknown) => boolean;\n\nexport const argValue =\n <T>(value: T): ArgumentMatcher =>\n (arg) =>\n deepEquals(arg, value);\n\nexport const argMatches =\n <T>(matches: (arg: T) => boolean): ArgumentMatcher =>\n (arg) =>\n matches(arg as T);\n\n// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\nexport type MockedFunction = Function & { mock?: { calls: Call[] } };\n\nexport function verifyThat(fn: MockedFunction) {\n return {\n calledTimes: (times: number) => {\n assertEqual(fn.mock?.calls?.length, times);\n },\n notCalled: () => {\n assertEqual(fn?.mock?.calls?.length, 0);\n },\n called: () => {\n assertTrue(\n fn.mock?.calls.length !== undefined && fn.mock.calls.length > 0,\n );\n },\n calledWith: (...args: unknown[]) => {\n assertTrue(\n fn.mock?.calls.length !== undefined &&\n fn.mock.calls.length >= 1 &&\n fn.mock.calls.some((call) => deepEquals(call.arguments, args)),\n );\n },\n calledOnceWith: (...args: unknown[]) => {\n assertTrue(\n fn.mock?.calls.length !== undefined &&\n fn.mock.calls.length === 1 &&\n fn.mock.calls.some((call) => deepEquals(call.arguments, args)),\n );\n },\n calledWithArgumentMatching: (...matches: ArgumentMatcher[]) => {\n assertTrue(\n fn.mock?.calls.length !== undefined && fn.mock.calls.length >= 1,\n );\n assertTrue(\n fn.mock?.calls.length !== undefined &&\n fn.mock.calls.length >= 1 &&\n fn.mock.calls.some(\n (call) =>\n call.arguments &&\n call.arguments.length >= matches.length &&\n matches.every((match, index) => match(call.arguments[index])),\n ),\n );\n },\n notCalledWithArgumentMatching: (...matches: ArgumentMatcher[]) => {\n assertFalse(\n fn.mock?.calls.length !== undefined &&\n fn.mock.calls.length >= 1 &&\n fn.mock.calls[0]!.arguments &&\n fn.mock.calls[0]!.arguments.length >= matches.length &&\n matches.every((match, index) =>\n match(fn.mock!.calls[0]!.arguments[index]),\n ),\n );\n },\n };\n}\n\nexport const assertThatArray = <T>(array: T[]) => {\n return {\n isEmpty: () =>\n assertEqual(\n array.length,\n 0,\n `Array is not empty ${JSONParser.stringify(array)}`,\n ),\n isNotEmpty: () => assertNotEqual(array.length, 0, `Array is empty`),\n hasSize: (length: number) => assertEqual(array.length, length),\n containsElements: (other: T[]) => {\n assertTrue(other.every((ts) => array.some((o) => deepEquals(ts, o))));\n },\n containsElementsMatching: (other: T[]) => {\n assertTrue(other.every((ts) => array.some((o) => isSubset(o, ts))));\n },\n containsOnlyElementsMatching: (other: T[]) => {\n assertEqual(array.length, other.length, `Arrays lengths don't match`);\n assertTrue(other.every((ts) => array.some((o) => isSubset(o, ts))));\n },\n containsExactlyInAnyOrder: (other: T[]) => {\n assertEqual(array.length, other.length);\n assertTrue(array.every((ts) => other.some((o) => deepEquals(ts, o))));\n },\n containsExactlyInAnyOrderElementsOf: (other: T[]) => {\n assertEqual(array.length, other.length);\n assertTrue(array.every((ts) => other.some((o) => deepEquals(ts, o))));\n },\n containsExactlyElementsOf: (other: T[]) => {\n assertEqual(array.length, other.length);\n for (let i = 0; i < array.length; i++) {\n assertTrue(deepEquals(array[i], other[i]));\n }\n },\n containsExactly: (elem: T) => {\n assertEqual(array.length, 1);\n assertTrue(deepEquals(array[0], elem));\n },\n contains: (elem: T) => {\n assertTrue(array.some((a) => deepEquals(a, elem)));\n },\n containsOnlyOnceElementsOf: (other: T[]) => {\n assertTrue(\n other\n .map((o) => array.filter((a) => deepEquals(a, o)).length)\n .filter((a) => a === 1).length === other.length,\n );\n },\n containsAnyOf: (other: T[]) => {\n assertTrue(array.some((a) => other.some((o) => deepEquals(a, o))));\n },\n allMatch: (matches: (item: T) => boolean) => {\n assertTrue(array.every(matches));\n },\n anyMatches: (matches: (item: T) => boolean) => {\n assertTrue(array.some(matches));\n },\n allMatchAsync: async (\n matches: (item: T) => Promise<boolean>,\n ): Promise<void> => {\n for (const item of array) {\n assertTrue(await matches(item));\n }\n },\n };\n};\n","import type {\n AggregateStreamOptions,\n AggregateStreamResult,\n AppendToStreamOptions,\n AppendToStreamResult,\n EventStore,\n EventStoreReadEventMetadata,\n ReadStreamOptions,\n ReadStreamResult,\n} from '../eventStore';\nimport type { Event, EventMetaDataOf } from '../typing';\n\nexport type TestEventStream<EventType extends Event = Event> = [\n string,\n EventType[],\n];\n\nexport type EventStoreWrapper<Store extends EventStore> = Store & {\n appendedEvents: Map<string, TestEventStream>;\n setup<EventType extends Event>(\n streamName: string,\n events: EventType[],\n ): Promise<AppendToStreamResult>;\n};\n\nexport const WrapEventStore = <Store extends EventStore>(\n eventStore: Store,\n): EventStoreWrapper<Store> => {\n const appendedEvents = new Map<string, TestEventStream>();\n\n const wrapped = {\n ...eventStore,\n aggregateStream<State, EventType extends Event>(\n streamName: string,\n options: AggregateStreamOptions<State, EventType>,\n ): Promise<AggregateStreamResult<State>> {\n return eventStore.aggregateStream(streamName, options);\n },\n\n async readStream<EventType extends Event>(\n streamName: string,\n options?: ReadStreamOptions<EventType>,\n ): Promise<\n ReadStreamResult<\n EventType,\n EventStoreReadEventMetadata<Store> & EventMetaDataOf<EventType>\n >\n > {\n return (await eventStore.readStream(\n streamName,\n options,\n )) as ReadStreamResult<\n EventType,\n EventStoreReadEventMetadata<Store> & EventMetaDataOf<EventType>\n >;\n },\n\n appendToStream: async <EventType extends Event>(\n streamName: string,\n events: EventType[],\n options?: AppendToStreamOptions<EventType>,\n ): Promise<AppendToStreamResult> => {\n const result = await eventStore.appendToStream(\n streamName,\n events,\n options,\n );\n\n const currentStream = appendedEvents.get(streamName) ?? [streamName, []];\n\n appendedEvents.set(streamName, [\n streamName,\n [...currentStream[1], ...events],\n ]);\n\n return result;\n },\n\n appendedEvents,\n\n setup: async <EventType extends Event>(\n streamName: string,\n events: EventType[],\n ): Promise<AppendToStreamResult> => {\n return eventStore.appendToStream(streamName, events);\n },\n\n // streamEvents: (): ReadableStream<\n // // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents\n // ReadEvent<Event, ReadEventMetadataType> | GlobalSubscriptionEvent\n // > => {\n // return eventStore.streamEvents();\n // },\n };\n\n return wrapped as EventStoreWrapper<Store>;\n};\n","import { isNumber } from '@event-driven-io/emmett';\nimport type { NextFunction, Request, Response } from 'express';\nimport { ProblemDocument } from 'http-problem-details';\nimport { sendProblem, type ErrorToProblemDetailsMapping } from '..';\n\nexport const problemDetailsMiddleware =\n (mapError?: ErrorToProblemDetailsMapping) =>\n (\n error: Error,\n request: Request,\n response: Response,\n _next: NextFunction,\n ): void => {\n let problemDetails: ProblemDocument | undefined;\n\n if (mapError) problemDetails = mapError(error, request);\n\n problemDetails =\n problemDetails ?? defaultErrorToProblemDetailsMapping(error);\n\n sendProblem(response, problemDetails.status, { problem: problemDetails });\n };\n\nexport const defaultErrorToProblemDetailsMapping = (\n error: Error,\n): ProblemDocument => {\n let statusCode = 500;\n\n if (\n 'errorCode' in error &&\n isNumber(error.errorCode) &&\n error.errorCode >= 100 &&\n error.errorCode < 600\n ) {\n statusCode = error.errorCode;\n }\n\n return new ProblemDocument({\n detail: error.message,\n status: statusCode,\n });\n};\n","import { type Brand, EmmettError } from '@event-driven-io/emmett';\nimport type { Request, Response } from 'express';\n\n//////////////////////////////////////\n/// ETAG\n//////////////////////////////////////\n\nexport const HeaderNames = {\n IF_MATCH: 'if-match',\n IF_NOT_MATCH: 'if-not-match',\n ETag: 'etag',\n};\n\nexport type WeakETag = Brand<`W/${string}`, 'ETag'>;\nexport type ETag = Brand<string, 'ETag'>;\n\nexport const WeakETagRegex = /W\\/\"(-?\\d+.*)\"/;\n\nexport const enum ETagErrors {\n WRONG_WEAK_ETAG_FORMAT = 'WRONG_WEAK_ETAG_FORMAT',\n MISSING_IF_MATCH_HEADER = 'MISSING_IF_MATCH_HEADER',\n MISSING_IF_NOT_MATCH_HEADER = 'MISSING_IF_NOT_MATCH_HEADER',\n}\n\nexport const isWeakETag = (etag: ETag): etag is WeakETag => {\n return WeakETagRegex.test(etag as string);\n};\n\nexport const getWeakETagValue = (etag: ETag): string => {\n const result = WeakETagRegex.exec(etag as string);\n if (result === null || result.length === 0) {\n throw new EmmettError({\n errorCode: EmmettError.Codes.ConcurrencyError,\n message: ETagErrors.WRONG_WEAK_ETAG_FORMAT,\n });\n }\n return result[1]!;\n};\n\nexport const toWeakETag = (value: number | bigint | string): WeakETag => {\n return `W/\"${value}\"` as WeakETag;\n};\n\nexport const getETagFromIfMatch = (request: Request): ETag => {\n const etag = request.headers[HeaderNames.IF_MATCH];\n\n if (etag === undefined) {\n throw new EmmettError({\n errorCode: EmmettError.Codes.ConcurrencyError,\n message: ETagErrors.MISSING_IF_MATCH_HEADER,\n });\n }\n\n return etag as ETag;\n};\n\nexport const getETagFromIfNotMatch = (request: Request): ETag => {\n const etag = request.headers[HeaderNames.IF_NOT_MATCH];\n\n if (etag === undefined) {\n throw new EmmettError({\n errorCode: EmmettError.Codes.ConcurrencyError,\n message: ETagErrors.MISSING_IF_NOT_MATCH_HEADER,\n });\n }\n\n return (Array.isArray(etag) ? etag[0] : etag) as ETag;\n};\n\nexport const setETag = (response: Response, etag: ETag): void => {\n response.setHeader(HeaderNames.ETag, etag as string);\n};\n\nexport const getETagValueFromIfMatch = (request: Request): string => {\n const eTagValue: ETag = getETagFromIfMatch(request);\n\n return isWeakETag(eTagValue)\n ? getWeakETagValue(eTagValue)\n : (eTagValue as string);\n};\n","import type { NextFunction, Request, Response } from 'express';\nimport {\n send,\n sendAccepted,\n sendCreated,\n sendProblem,\n type AcceptedHttpResponseOptions,\n type CreatedHttpResponseOptions,\n type HttpProblemResponseOptions,\n type HttpResponseOptions,\n type NoContentHttpResponseOptions,\n} from '.';\n\n// #region httpresponse-on\nexport type HttpResponse = (response: Response) => void;\n\nexport type HttpHandler<RequestType extends Request> = (\n request: RequestType,\n) => Promise<HttpResponse> | HttpResponse;\n\nexport const on =\n <RequestType extends Request>(handle: HttpHandler<RequestType>) =>\n async (\n request: RequestType,\n response: Response,\n _next: NextFunction,\n ): Promise<void> => {\n const setResponse = await Promise.resolve(handle(request));\n\n return setResponse(response);\n };\n// #endregion httpresponse-on\n\nexport const OK =\n (options?: HttpResponseOptions): HttpResponse =>\n (response: Response) => {\n send(response, 200, options);\n };\n\nexport const Created =\n (options: CreatedHttpResponseOptions): HttpResponse =>\n (response: Response) => {\n sendCreated(response, options);\n };\n\nexport const Accepted =\n (options: AcceptedHttpResponseOptions): HttpResponse =>\n (response: Response) => {\n sendAccepted(response, options);\n };\n\nexport const NoContent = (\n options?: NoContentHttpResponseOptions,\n): HttpResponse => HttpResponse(204, options);\n\nexport const HttpResponse =\n (statusCode: number, options?: HttpResponseOptions): HttpResponse =>\n (response: Response) => {\n send(response, statusCode, options);\n };\n\n/////////////////////\n// ERRORS\n/////////////////////\n\nexport const BadRequest = (\n options?: HttpProblemResponseOptions,\n): HttpResponse => HttpProblem(400, options);\n\nexport const Forbidden = (options?: HttpProblemResponseOptions): HttpResponse =>\n HttpProblem(403, options);\n\nexport const NotFound = (options?: HttpProblemResponseOptions): HttpResponse =>\n HttpProblem(404, options);\n\nexport const Conflict = (options?: HttpProblemResponseOptions): HttpResponse =>\n HttpProblem(409, options);\n\nexport const PreconditionFailed = (\n options: HttpProblemResponseOptions,\n): HttpResponse => HttpProblem(412, options);\n\nexport const HttpProblem =\n (statusCode: number, options?: HttpProblemResponseOptions): HttpResponse =>\n (response: Response) => {\n sendProblem(response, statusCode, options);\n };\n","import type { Request, Response } from 'express';\nimport { ProblemDocument } from 'http-problem-details';\nimport { setETag, type ETag } from './etag';\n\nexport type ErrorToProblemDetailsMapping = (\n error: Error,\n request: Request,\n) => ProblemDocument | undefined;\n\nexport type HttpResponseOptions = {\n body?: unknown;\n location?: string;\n eTag?: ETag;\n};\nexport const DefaultHttpResponseOptions: HttpResponseOptions = {};\n\nexport type HttpProblemResponseOptions = {\n location?: string;\n eTag?: ETag;\n} & Omit<HttpResponseOptions, 'body'> &\n (\n | {\n problem: ProblemDocument;\n }\n | { problemDetails: string }\n );\nexport const DefaultHttpProblemResponseOptions: HttpProblemResponseOptions = {\n problemDetails: 'Error occured!',\n};\n\nexport type CreatedHttpResponseOptions = (\n | {\n createdId: string;\n }\n | {\n createdId?: string;\n url: string;\n }\n) &\n HttpResponseOptions;\n\nexport const sendCreated = (\n response: Response,\n { eTag, ...options }: CreatedHttpResponseOptions,\n): void =>\n send(response, 201, {\n location:\n 'url' in options\n ? options.url\n : `${response.req.url}/${options.createdId}`,\n body: 'createdId' in options ? { id: options.createdId } : undefined,\n eTag,\n });\n\nexport type AcceptedHttpResponseOptions = {\n location: string;\n} & HttpResponseOptions;\n\nexport const sendAccepted = (\n response: Response,\n options: AcceptedHttpResponseOptions,\n): void => send(response, 202, options);\n\nexport type NoContentHttpResponseOptions = Omit<HttpResponseOptions, 'body'>;\n\nexport const send = (\n response: Response,\n statusCode: number,\n options?: HttpResponseOptions,\n): void => {\n const { location, body, eTag } = options ?? DefaultHttpResponseOptions;\n // HEADERS\n if (eTag) setETag(response, eTag);\n if (location) response.setHeader('Location', location);\n\n if (body) {\n response.statusCode = statusCode;\n response.send(body);\n } else {\n response.sendStatus(statusCode);\n }\n};\n\nexport const sendProblem = (\n response: Response,\n statusCode: number,\n options?: HttpProblemResponseOptions,\n): void => {\n options = options ?? DefaultHttpProblemResponseOptions;\n\n const { location, eTag } = options;\n\n const problemDetails =\n 'problem' in options\n ? options.problem\n : new ProblemDocument({\n detail: options.problemDetails,\n status: statusCode,\n });\n\n // HEADERS\n if (eTag) setETag(response, eTag);\n if (location) response.setHeader('Location', location);\n\n response.setHeader('Content-Type', 'application/problem+json');\n\n response.statusCode = statusCode;\n response.json(problemDetails);\n};\n","import supertest, { type Response } from 'supertest';\n\nimport type { EventStore } from '@event-driven-io/emmett';\nimport assert from 'assert';\nimport type { Application } from 'express';\nimport type { TestRequest } from './apiSpecification';\n\nexport type E2EResponseAssert = (response: Response) => boolean | void;\n\nexport type ApiE2ESpecificationAssert = [E2EResponseAssert];\n\nexport type ApiE2ESpecification = (...givenRequests: TestRequest[]) => {\n when: (setupRequest: TestRequest) => {\n then: (verify: ApiE2ESpecificationAssert) => Promise<void>;\n };\n};\n\nexport const ApiE2ESpecification = {\n for: <Store extends EventStore = EventStore>(\n getEventStore: () => Store,\n getApplication: (eventStore: Store) => Application,\n ): ApiE2ESpecification => {\n {\n return (...givenRequests: TestRequest[]) => {\n const eventStore = getEventStore();\n const application = getApplication(eventStore);\n\n return {\n when: (setupRequest: TestRequest) => {\n const handle = async () => {\n for (const requestFn of givenRequests) {\n await requestFn(supertest(application));\n }\n\n return setupRequest(supertest(application));\n };\n\n return {\n then: async (\n verify: ApiE2ESpecificationAssert,\n ): Promise<void> => {\n const response = await handle();\n\n verify.forEach((assertion) => {\n const succeeded = assertion(response);\n\n if (succeeded === false) assert.fail();\n });\n },\n };\n },\n };\n };\n }\n },\n};\n","import {\n WrapEventStore,\n assertEqual,\n assertFails,\n assertMatches,\n type Event,\n type EventStore,\n type TestEventStream,\n} from '@event-driven-io/emmett';\nimport type { Application } from 'express';\nimport type { ProblemDocument } from 'http-problem-details';\nimport type { Response, Test } from 'supertest';\nimport supertest from 'supertest';\nimport type TestAgent from 'supertest/lib/agent';\n\n////////////////////////////////\n/////////// Setup\n////////////////////////////////\n\nexport type TestRequest = (request: TestAgent<supertest.Test>) => Test;\n\nexport const existingStream = <EventType extends Event = Event>(\n streamId: string,\n events: EventType[],\n): TestEventStream<EventType> => {\n return [streamId, events];\n};\n\n////////////////////////////////\n/////////// Asserts\n////////////////////////////////\n\nexport type ResponseAssert = (response: Response) => boolean | void;\n\nexport type ApiSpecificationAssert<EventType extends Event = Event> =\n | TestEventStream<EventType>[]\n | ResponseAssert\n | [ResponseAssert, ...TestEventStream<EventType>[]];\n\nexport const expect = <EventType extends Event = Event>(\n streamId: string,\n events: EventType[],\n): TestEventStream<EventType> => {\n return [streamId, events];\n};\n\nexport const expectNewEvents = <EventType extends Event = Event>(\n streamId: string,\n events: EventType[],\n): TestEventStream<EventType> => {\n return [streamId, events];\n};\n\nexport const expectResponse =\n <Body = unknown>(\n statusCode: number,\n options?: { body?: Body; headers?: { [index: string]: string } },\n ) =>\n (response: Response): void => {\n const { body, headers } = options ?? {};\n assertEqual(statusCode, response.statusCode, \"Response code doesn't match\");\n if (body) assertMatches(response.body, body);\n if (headers) assertMatches(response.headers, headers);\n };\n\nexport const expectError = (\n errorCode: number,\n problemDetails?: Partial<ProblemDocument>,\n) =>\n expectResponse(\n errorCode,\n problemDetails ? { body: problemDetails } : undefined,\n );\n\n////////////////////////////////\n/////////// Api Specification\n////////////////////////////////\n\nexport type ApiSpecification<EventType extends Event = Event> = (\n ...givenStreams: TestEventStream<EventType>[]\n) => {\n when: (setupRequest: TestRequest) => {\n then: (verify: ApiSpecificationAssert<EventType>) => Promise<void>;\n };\n};\n\nexport const ApiSpecification = {\n for: <EventType extends Event = Event, Store extends EventStore = EventStore>(\n getEventStore: () => Store,\n getApplication: (eventStore: Store) => Application,\n ): ApiSpecification<EventType> => {\n {\n return (...givenStreams: TestEventStream<EventType>[]) => {\n const eventStore = WrapEventStore(getEventStore());\n const application = getApplication(eventStore);\n\n return {\n when: (setupRequest: TestRequest) => {\n const handle = async () => {\n for (const [streamName, events] of givenStreams) {\n await eventStore.setup(streamName, events);\n }\n\n return setupRequest(supertest(application));\n };\n\n return {\n then: async (\n verify: ApiSpecificationAssert<EventType>,\n ): Promise<void> => {\n const response = await handle();\n\n if (typeof verify === 'function') {\n const succeeded = verify(response);\n\n if (succeeded === false) assertFails();\n } else if (Array.isArray(verify)) {\n const [first, ...rest] = verify;\n\n if (typeof first === 'function') {\n const succeeded = first(response);\n\n if (succeeded === false) assertFails();\n }\n\n const events = typeof first === 'function' ? rest : verify;\n\n assertMatches(\n Array.from(eventStore.appendedEvents.values()),\n events,\n );\n }\n },\n };\n },\n };\n };\n }\n },\n};\n"]}
|