@event-driven-io/emmett-mongodb 0.21.1 → 0.22.0-alpha.2

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 CHANGED
@@ -262,16 +262,61 @@ var { retry: retry2 } = streamTransformations;
262
262
  // src/eventStore/mongoDBEventStore.ts
263
263
  require('mongodb');
264
264
 
265
- var MongoDBEventStoreDefaultStreamVersion = 0;
265
+
266
+ // src/eventStore/projections/index.ts
267
+ var handleInlineProjections = async (options) => {
268
+ const {
269
+ events,
270
+ projections: allProjections,
271
+ updates: update,
272
+ collection,
273
+ readModels
274
+ } = options;
275
+ const eventTypes = events.map((e) => e.type);
276
+ const projections = allProjections.filter(
277
+ (p) => p.canHandle.some((type) => eventTypes.includes(type))
278
+ );
279
+ for (const projection of projections) {
280
+ await projection.handle(events, {
281
+ document: _nullishCoalesce(readModels[projection.name], () => ( null)),
282
+ collection,
283
+ updates: update
284
+ });
285
+ }
286
+ };
287
+
288
+ // src/eventStore/mongoDBEventStore.ts
289
+ var MongoDBEventStoreDefaultStreamVersion = 0n;
266
290
  var MongoDBEventStore = class {
267
291
 
268
- constructor(collection) {
269
- this.collection = collection;
292
+
293
+ constructor(options) {
294
+ this.collection = options.collection;
295
+ this.inlineProjections = (_nullishCoalesce(options.projections, () => ( []))).filter(({ type }) => type === "inline").map(
296
+ ({ projection }) => projection
297
+ );
270
298
  }
271
299
  async readStream(streamName, options) {
272
300
  const expectedStreamVersion = _optionalChain([options, 'optionalAccess', _10 => _10.expectedStreamVersion]);
273
- const stream = await this.collection.findOne({
301
+ const filter2 = {
274
302
  streamName: { $eq: streamName }
303
+ };
304
+ const eventsSliceArr = [];
305
+ if (options && "from" in options) {
306
+ eventsSliceArr.push(Number(options.from));
307
+ } else {
308
+ eventsSliceArr.push(0);
309
+ }
310
+ if (options && "to" in options) {
311
+ eventsSliceArr.push(Number(options.to));
312
+ }
313
+ const eventsSlice = eventsSliceArr.length > 1 ? { $slice: eventsSliceArr } : 1;
314
+ const stream = await this.collection.findOne(filter2, {
315
+ useBigInt64: true,
316
+ projection: {
317
+ metadata: 1,
318
+ events: eventsSlice
319
+ }
275
320
  });
276
321
  if (!stream) {
277
322
  return {
@@ -281,13 +326,13 @@ var MongoDBEventStore = class {
281
326
  };
282
327
  }
283
328
  assertExpectedVersionMatchesCurrent(
284
- stream.events.length,
329
+ stream.metadata.streamPosition,
285
330
  expectedStreamVersion,
286
331
  MongoDBEventStoreDefaultStreamVersion
287
332
  );
288
333
  return {
289
- events: stream.events.slice(0, maxEventIndex(expectedStreamVersion)),
290
- currentStreamVersion: stream.events.length,
334
+ events: stream.events,
335
+ currentStreamVersion: stream.metadata.streamPosition,
291
336
  streamExists: true
292
337
  };
293
338
  }
@@ -301,100 +346,91 @@ var MongoDBEventStore = class {
301
346
  };
302
347
  }
303
348
  async appendToStream(streamName, events, options) {
304
- let stream = await this.collection.findOne({
305
- streamName: { $eq: streamName }
306
- });
307
- let currentStreamPosition = _nullishCoalesce(_optionalChain([stream, 'optionalAccess', _12 => _12.events, 'access', _13 => _13.length]), () => ( 0));
308
- let createdNewStream = false;
309
- if (!stream) {
310
- const now = /* @__PURE__ */ new Date();
311
- const result = await this.collection.insertOne({
349
+ const expectedStreamVersion = _optionalChain([options, 'optionalAccess', _12 => _12.expectedStreamVersion]);
350
+ const stream = await this.collection.findOne(
351
+ { streamName: { $eq: streamName } },
352
+ {
353
+ useBigInt64: true,
354
+ projection: {
355
+ "metadata.streamPosition": 1,
356
+ projections: 1
357
+ }
358
+ }
359
+ );
360
+ const currentStreamVersion = _nullishCoalesce(_optionalChain([stream, 'optionalAccess', _13 => _13.metadata, 'access', _14 => _14.streamPosition]), () => ( MongoDBEventStoreDefaultStreamVersion));
361
+ assertExpectedVersionMatchesCurrent(
362
+ currentStreamVersion,
363
+ expectedStreamVersion,
364
+ MongoDBEventStoreDefaultStreamVersion
365
+ );
366
+ let streamOffset = currentStreamVersion;
367
+ const eventsToAppend = events.map((event) => {
368
+ const metadata = {
369
+ eventId: _uuid.v4.call(void 0, ),
312
370
  streamName,
313
- events: [],
314
- createdAt: now,
315
- updatedAt: now
316
- });
317
- stream = await this.collection.findOne({
318
- _id: result.insertedId
319
- });
320
- createdNewStream = true;
321
- }
322
- const eventCreateInputs = [];
323
- for (const event of events) {
324
- currentStreamPosition++;
325
- eventCreateInputs.push({
371
+ streamPosition: ++streamOffset
372
+ };
373
+ return {
326
374
  type: event.type,
327
375
  data: event.data,
328
376
  metadata: {
329
- now: /* @__PURE__ */ new Date(),
330
- eventId: _uuid.v4.call(void 0, ),
331
- streamName,
332
- streamPosition: BigInt(currentStreamPosition),
377
+ ...metadata,
333
378
  ..._nullishCoalesce(event.metadata, () => ( {}))
334
379
  }
380
+ };
381
+ });
382
+ const updates = {
383
+ $push: { events: { $each: eventsToAppend } },
384
+ $set: { "metadata.updatedAt": /* @__PURE__ */ new Date() },
385
+ $inc: { "metadata.streamPosition": BigInt(events.length) }
386
+ };
387
+ if (this.inlineProjections) {
388
+ await handleInlineProjections({
389
+ readModels: _nullishCoalesce(_optionalChain([stream, 'optionalAccess', _15 => _15.projections]), () => ( {})),
390
+ events: eventsToAppend,
391
+ projections: this.inlineProjections,
392
+ collection: this.collection,
393
+ updates,
394
+ client: {}
335
395
  });
336
396
  }
337
- if (!stream) throw new Error("Failed to create stream");
338
- assertExpectedVersionMatchesCurrent(
339
- stream.events.length,
340
- _optionalChain([options, 'optionalAccess', _14 => _14.expectedStreamVersion]),
341
- MongoDBEventStoreDefaultStreamVersion
342
- );
343
- const updatedStream = await this.collection.findOneAndUpdate(
397
+ const updatedStream = await this.collection.updateOne(
344
398
  {
345
399
  streamName: { $eq: streamName },
346
- events: { $size: stream.events.length }
347
- },
348
- {
349
- $push: { events: { $each: eventCreateInputs } },
350
- $set: { updatedAt: /* @__PURE__ */ new Date() }
400
+ "metadata.streamPosition": toExpectedVersion(
401
+ _optionalChain([options, 'optionalAccess', _16 => _16.expectedStreamVersion])
402
+ )
351
403
  },
352
- { returnDocument: "after" }
404
+ updates,
405
+ { useBigInt64: true, upsert: true }
353
406
  );
354
407
  if (!updatedStream) {
355
- const currentStream = await this.collection.findOne({
356
- streamName: { $eq: streamName }
357
- });
358
408
  throw new ExpectedVersionConflictError(
359
- _nullishCoalesce(_optionalChain([currentStream, 'optionalAccess', _15 => _15.events, 'access', _16 => _16.length]), () => ( -1)),
360
- stream.events.length
409
+ currentStreamVersion,
410
+ _nullishCoalesce(_optionalChain([options, 'optionalAccess', _17 => _17.expectedStreamVersion]), () => ( 0n))
361
411
  );
362
412
  }
363
- const { streamType, streamId } = fromStreamName(streamName);
364
- await executeProjections(
365
- {
366
- streamName,
367
- streamType,
368
- streamId,
369
- streamVersion: updatedStream.events.length,
370
- events: updatedStream.events
371
- },
372
- _optionalChain([options, 'optionalAccess', _17 => _17.projections])
373
- );
374
413
  return {
375
- nextExpectedStreamVersion: updatedStream.events.length,
376
- createdNewStream
414
+ nextExpectedStreamVersion: currentStreamVersion + BigInt(eventsToAppend.length),
415
+ createdNewStream: currentStreamVersion === MongoDBEventStoreDefaultStreamVersion
377
416
  };
378
417
  }
379
418
  };
380
- var getMongoDBEventStore = (collection) => {
381
- const eventStore = new MongoDBEventStore(collection);
419
+ var getMongoDBEventStore = (options) => {
420
+ const eventStore = new MongoDBEventStore(options);
382
421
  return eventStore;
383
422
  };
384
- function executeProjections(params, projections) {
385
- return Promise.all((_nullishCoalesce(projections, () => ( []))).map((project) => project(params)));
386
- }
387
- function maxEventIndex(expectedStreamVersion) {
423
+ function toExpectedVersion(expectedStreamVersion) {
388
424
  if (!expectedStreamVersion) return void 0;
389
- if (typeof expectedStreamVersion === "number") {
390
- return expectedStreamVersion;
391
- }
392
- switch (expectedStreamVersion) {
393
- case STREAM_DOES_NOT_EXIST:
394
- return 0;
395
- default:
396
- return void 0;
425
+ if (typeof expectedStreamVersion === "string") {
426
+ switch (expectedStreamVersion) {
427
+ case STREAM_DOES_NOT_EXIST:
428
+ return BigInt(0);
429
+ default:
430
+ return void 0;
431
+ }
397
432
  }
433
+ return expectedStreamVersion;
398
434
  }
399
435
  function toStreamName(streamType, streamId) {
400
436
  return `${streamType}:${streamId}`;
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/emmett/emmett/src/packages/emmett-mongodb/dist/index.cjs","../../emmett/src/validation/index.ts","../../emmett/src/errors/index.ts","../../emmett/src/eventStore/inMemoryEventStore.ts","../../emmett/src/eventStore/subscriptions/caughtUpTransformStream.ts","../../emmett/src/eventStore/subscriptions/streamingCoordinator.ts","../../emmett/src/streaming/transformations/notifyAboutNoActiveReaders.ts","../../emmett/src/utils/retry.ts","../../emmett/src/streaming/generators/fromArray.ts","../../emmett/src/streaming/restream.ts","../../emmett/src/streaming/transformations/filter.ts","../../emmett/src/streaming/transformations/map.ts","../../emmett/src/streaming/transformations/reduce.ts","../../emmett/src/streaming/transformations/retry.ts","../../emmett/src/streaming/transformations/skip.ts","../../emmett/src/streaming/transformations/stopAfter.ts","../../emmett/src/streaming/transformations/stopOn.ts","../../emmett/src/streaming/transformations/take.ts","../../emmett/src/streaming/transformations/waitAtMost.ts","../../emmett/src/eventStore/expectedVersion.ts","../../emmett/src/streaming/transformations/index.ts","../src/eventStore/mongoDBEventStore.ts"],"names":[],"mappings":"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","file":"/home/runner/work/emmett/emmett/src/packages/emmett-mongodb/dist/index.cjs","sourcesContent":[null,"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\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 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 : 500;\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\nexport class ConcurrencyError extends EmmettError {\n constructor(\n public current: string | undefined,\n public expected: string,\n message?: string,\n ) {\n super({\n errorCode: 412,\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\nexport class ValidationError extends EmmettError {\n constructor(message?: string) {\n super({\n errorCode: 400,\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: 403,\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: 404,\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 type {\n Event,\n ReadEvent,\n ReadEventMetadataWithGlobalPosition,\n} from '../typing';\nimport {\n type AggregateStreamOptions,\n type AggregateStreamResult,\n type AppendToStreamOptions,\n type AppendToStreamResult,\n type DefaultStreamVersionType,\n type EventStore,\n type ReadStreamOptions,\n type ReadStreamResult,\n} from './eventStore';\nimport { assertExpectedVersionMatchesCurrent } from './expectedVersion';\nimport { StreamingCoordinator } from './subscriptions';\n\nexport type EventHandler<E extends Event = Event> = (\n eventEnvelope: ReadEvent<E>,\n) => void;\n\nexport const InMemoryEventStoreDefaultStreamVersion = 0n;\n\nexport const getInMemoryEventStore = (): EventStore<\n DefaultStreamVersionType,\n ReadEventMetadataWithGlobalPosition\n> => {\n const streams = new Map<\n string,\n ReadEvent<Event, ReadEventMetadataWithGlobalPosition>[]\n >();\n const streamingCoordinator = StreamingCoordinator();\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 return {\n async aggregateStream<State, EventType extends Event>(\n streamName: string,\n options: AggregateStreamOptions<State, EventType>,\n ): Promise<AggregateStreamResult<State>> {\n const { evolve, initialState, read } = options;\n\n const result = await this.readStream<EventType>(streamName, read);\n\n const events = result?.events ?? [];\n\n return {\n currentStreamVersion: BigInt(events.length),\n state: events.reduce(evolve, initialState()),\n streamExists: result.streamExists,\n };\n },\n\n readStream: <EventType extends Event>(\n streamName: string,\n options?: ReadStreamOptions,\n ): Promise<\n ReadStreamResult<\n EventType,\n DefaultStreamVersionType,\n ReadEventMetadataWithGlobalPosition\n >\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' in options ? options.from : 0);\n const to = Number(\n options && 'to' in options\n ? options.to\n : options && 'maxCount' in options && options.maxCount\n ? options.from + options.maxCount\n : (events?.length ?? 1),\n );\n\n const resultEvents =\n events !== undefined && events.length > 0\n ? events\n .map(\n (e) =>\n e as ReadEvent<\n EventType,\n ReadEventMetadataWithGlobalPosition\n >,\n )\n .slice(from, to)\n : [];\n\n const result: ReadStreamResult<\n EventType,\n DefaultStreamVersionType,\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 <EventType extends Event>(\n streamName: string,\n events: EventType[],\n options?: AppendToStreamOptions,\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 return {\n ...event,\n metadata: {\n ...(event.metadata ?? {}),\n streamName,\n eventId: uuid(),\n streamPosition: BigInt(currentEvents.length + index + 1),\n globalPosition: BigInt(getAllEventsCount() + index + 1),\n },\n };\n });\n\n const positionOfLastEventInTheStream = BigInt(\n newEvents.slice(-1)[0]!.metadata.streamPosition,\n );\n\n streams.set(streamName, [...currentEvents, ...newEvents]);\n await streamingCoordinator.notify(newEvents);\n\n const result: AppendToStreamResult = {\n nextExpectedStreamVersion: positionOfLastEventInTheStream,\n createdNewStream:\n currentStreamVersion === InMemoryEventStoreDefaultStreamVersion,\n };\n\n return result;\n },\n\n //streamEvents: streamingCoordinator.stream,\n };\n};\n","import { TransformStream } from 'web-streams-polyfill';\nimport type {\n Event,\n ReadEvent,\n ReadEventMetadataWithGlobalPosition,\n} from '../../typing';\nimport { globalStreamCaughtUp, type GlobalSubscriptionEvent } from '../events';\n\nexport const streamTrackingGlobalPosition = (\n currentEvents: ReadEvent<Event, ReadEventMetadataWithGlobalPosition>[],\n) => new CaughtUpTransformStream(currentEvents);\n\nexport class CaughtUpTransformStream extends TransformStream<\n ReadEvent<Event, ReadEventMetadataWithGlobalPosition>,\n | ReadEvent<Event, ReadEventMetadataWithGlobalPosition>\n | GlobalSubscriptionEvent\n> {\n private _currentPosition: bigint;\n private _logPosition: bigint;\n\n constructor(events: ReadEvent<Event, ReadEventMetadataWithGlobalPosition>[]) {\n super({\n start: (controller) => {\n let globalPosition = 0n;\n for (const event of events) {\n controller.enqueue(event);\n globalPosition = event.metadata.globalPosition;\n }\n controller.enqueue(globalStreamCaughtUp({ globalPosition }));\n },\n transform: (event, controller) => {\n this._currentPosition = event.metadata.globalPosition;\n controller.enqueue(event);\n\n if (this._currentPosition < this._logPosition) return;\n\n controller.enqueue(\n globalStreamCaughtUp({ globalPosition: this._currentPosition }),\n );\n },\n });\n\n this._currentPosition = this._logPosition =\n events.length > 0\n ? events[events.length - 1]!.metadata.globalPosition\n : 0n;\n }\n\n public set logPosition(value: bigint) {\n this._logPosition = value;\n }\n}\n","import { v4 as uuid } from 'uuid';\nimport { notifyAboutNoActiveReadersStream } from '../../streaming/transformations/notifyAboutNoActiveReaders';\nimport { writeToStream } from '../../streaming/writers';\nimport type {\n Event,\n ReadEvent,\n ReadEventMetadataWithGlobalPosition,\n} from '../../typing';\nimport {\n CaughtUpTransformStream,\n streamTrackingGlobalPosition,\n} from './caughtUpTransformStream';\n\nexport const StreamingCoordinator = () => {\n const allEvents: ReadEvent<Event, ReadEventMetadataWithGlobalPosition>[] = [];\n const listeners = new Map<string, CaughtUpTransformStream>();\n\n return {\n notify: async (\n events: ReadEvent<Event, ReadEventMetadataWithGlobalPosition>[],\n ) => {\n if (events.length === 0) return;\n\n allEvents.push(...events);\n\n for (const listener of listeners.values()) {\n listener.logPosition =\n events[events.length - 1]!.metadata.globalPosition;\n\n await writeToStream(listener, events);\n }\n },\n\n stream: () => {\n const streamId = uuid();\n const transformStream = streamTrackingGlobalPosition(allEvents);\n\n listeners.set(streamId, transformStream);\n return transformStream.readable.pipeThrough(\n notifyAboutNoActiveReadersStream(\n (stream) => {\n if (listeners.has(stream.streamId))\n listeners.delete(stream.streamId);\n },\n { streamId },\n ),\n );\n },\n };\n};\n","import { v4 as uuid } from 'uuid';\nimport { TransformStream } from 'web-streams-polyfill';\n\nexport const notifyAboutNoActiveReadersStream = <Item>(\n onNoActiveReaderCallback: (\n stream: NotifyAboutNoActiveReadersStream<Item>,\n ) => void,\n options: { streamId?: string; intervalCheckInMs?: number } = {},\n) => new NotifyAboutNoActiveReadersStream(onNoActiveReaderCallback, options);\n\nexport class NotifyAboutNoActiveReadersStream<Item> extends TransformStream<\n Item,\n Item\n> {\n private checkInterval: NodeJS.Timeout | null = null;\n public readonly streamId: string;\n private _isStopped: boolean = false;\n public get hasActiveSubscribers() {\n return !this._isStopped;\n }\n\n constructor(\n private onNoActiveReaderCallback: (\n stream: NotifyAboutNoActiveReadersStream<Item>,\n ) => void,\n options: { streamId?: string; intervalCheckInMs?: number } = {},\n ) {\n super({\n cancel: (reason) => {\n console.log('Stream was canceled. Reason:', reason);\n this.stopChecking();\n },\n });\n this.streamId = options?.streamId ?? uuid();\n\n this.onNoActiveReaderCallback = onNoActiveReaderCallback;\n\n this.startChecking(options?.intervalCheckInMs ?? 20);\n }\n\n private startChecking(interval: number) {\n this.checkInterval = setInterval(() => {\n this.checkNoActiveReader();\n }, interval);\n }\n\n private stopChecking() {\n if (!this.checkInterval) return;\n\n clearInterval(this.checkInterval);\n this.checkInterval = null;\n this._isStopped = true;\n this.onNoActiveReaderCallback(this);\n }\n\n private checkNoActiveReader() {\n if (!this.readable.locked && !this._isStopped) {\n this.stopChecking();\n }\n }\n}\n","import retry from 'async-retry';\n\nexport type AsyncRetryOptions = retry.Options & {\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,\n): Promise<T> => {\n if (opts === undefined || opts.retries === 0) return fn();\n\n return retry(\n async (bail) => {\n try {\n return await fn();\n } catch (error) {\n if (opts?.shouldRetryError && !opts.shouldRetryError(error)) {\n bail(error as Error);\n }\n throw error;\n }\n },\n opts ?? { retries: 0 },\n );\n};\n","import { ReadableStream } from 'web-streams-polyfill';\n\nexport const fromArray = <T>(chunks: T[]) =>\n new ReadableStream<T>({\n start(controller) {\n for (const chunk of chunks) controller.enqueue(chunk);\n controller.close();\n },\n });\n","import {\n type ReadableStream,\n type ReadableStreamDefaultReadResult,\n type TransformStreamDefaultController,\n} from 'web-streams-polyfill';\nimport type { AsyncRetryOptions } from '../utils';\nimport type { Decoder } from './decoders';\nimport { DefaultDecoder } from './decoders/composite';\nimport { streamTransformations } from './transformations';\n\nconst { retry } = streamTransformations;\n\nexport const restream = <\n Source = unknown,\n Transformed = Source,\n StreamType = Source,\n>(\n createSourceStream: () => ReadableStream<StreamType>,\n transform: (input: Source) => Transformed = (source) =>\n source as unknown as Transformed,\n retryOptions: AsyncRetryOptions = { forever: true, minTimeout: 25 },\n decoder: Decoder<StreamType, Source> = new DefaultDecoder<Source>(),\n): ReadableStream<Transformed> =>\n retry(createSourceStream, handleChunk(transform, decoder), retryOptions)\n .readable;\n\nconst handleChunk =\n <Source = unknown, Transformed = Source, StreamType = Source>(\n transform: (input: Source) => Transformed = (source) =>\n source as unknown as Transformed,\n decoder: Decoder<StreamType, Source> = new DefaultDecoder<Source>(),\n ) =>\n (\n readResult: ReadableStreamDefaultReadResult<StreamType>,\n controller: TransformStreamDefaultController<Transformed>,\n ): void => {\n const { done: isDone, value } = readResult;\n\n if (value) decoder.addToBuffer(value);\n\n if (!isDone && !decoder.hasCompleteMessage()) return;\n\n decodeAndTransform(decoder, transform, controller);\n };\n\nconst decodeAndTransform = <StreamType, Source, Transformed = Source>(\n decoder: Decoder<StreamType, Source>,\n transform: (input: Source) => Transformed,\n controller: TransformStreamDefaultController<Transformed>,\n) => {\n try {\n const decoded = decoder.decode();\n if (!decoded) return; // TODO: Add a proper handling of decode errors\n\n const transformed = transform(decoded);\n controller.enqueue(transformed);\n } catch (error) {\n controller.error(new Error(`Decoding error: ${error?.toString()}`));\n }\n};\n","import { TransformStream } from 'web-streams-polyfill';\n\nexport const filter = <Item>(filter: (item: Item) => boolean) =>\n new TransformStream<Item, Item>({\n transform(chunk, controller) {\n if (filter(chunk)) {\n controller.enqueue(chunk);\n }\n },\n });\n","import { TransformStream } from 'web-streams-polyfill';\n\nexport const map = <From, To>(map: (item: From) => To) =>\n new TransformStream<From, To>({\n transform(chunk, controller) {\n controller.enqueue(map(chunk));\n },\n });\n","import { TransformStream } from 'web-streams-polyfill';\n\nexport const reduce = <I, O>(\n reducer: (accumulator: O, chunk: I) => O,\n initialValue: O,\n) => new ReduceTransformStream<I, O>(reducer, initialValue);\n\nexport class ReduceTransformStream<I, O> extends TransformStream<I, O> {\n private accumulator: O;\n private reducer: (accumulator: O, chunk: I) => O;\n\n constructor(reducer: (accumulator: O, chunk: I) => O, initialValue: O) {\n super({\n transform: (chunk) => {\n this.accumulator = this.reducer(this.accumulator, chunk);\n },\n flush: (controller) => {\n controller.enqueue(this.accumulator);\n controller.terminate();\n },\n });\n\n this.accumulator = initialValue;\n this.reducer = reducer;\n }\n}\n","import {\n type ReadableStream,\n type ReadableStreamDefaultReadResult,\n TransformStream,\n type TransformStreamDefaultController,\n} from 'web-streams-polyfill';\nimport { type AsyncRetryOptions, asyncRetry } from '../../utils';\n\nexport const retryStream = <\n Source = unknown,\n Transformed = Source,\n StreamType = Source,\n>(\n createSourceStream: () => ReadableStream<StreamType>,\n handleChunk: (\n readResult: ReadableStreamDefaultReadResult<StreamType>,\n controller: TransformStreamDefaultController<Transformed>,\n ) => Promise<void> | void,\n retryOptions: AsyncRetryOptions = { forever: true, minTimeout: 25 },\n): TransformStream<Source, Transformed> =>\n new TransformStream<Source, Transformed>({\n start(controller) {\n asyncRetry(\n () => onRestream(createSourceStream, handleChunk, controller),\n retryOptions,\n ).catch((error) => {\n controller.error(error);\n });\n },\n });\n\nconst onRestream = async <StreamType, Source, Transformed = Source>(\n createSourceStream: () => ReadableStream<StreamType>,\n handleChunk: (\n readResult: ReadableStreamDefaultReadResult<StreamType>,\n controller: TransformStreamDefaultController<Transformed>,\n ) => Promise<void> | void,\n controller: TransformStreamDefaultController<Transformed>,\n): Promise<void> => {\n const sourceStream = createSourceStream();\n const reader = sourceStream.getReader();\n\n try {\n let done: boolean;\n\n do {\n const result = await reader.read();\n done = result.done;\n\n await handleChunk(result, controller);\n\n if (done) {\n controller.terminate();\n }\n } while (!done);\n } finally {\n reader.releaseLock();\n }\n};\n","import { TransformStream } from 'web-streams-polyfill';\n\nexport const skip = <T>(limit: number) => new SkipTransformStream<T>(limit);\n\nexport class SkipTransformStream<T> extends TransformStream<T, T> {\n private count = 0;\n private skip: number;\n\n constructor(skip: number) {\n super({\n transform: (chunk, controller) => {\n this.count++;\n if (this.count > this.skip) {\n controller.enqueue(chunk);\n }\n },\n });\n\n this.skip = skip;\n }\n}\n","import { TransformStream } from 'web-streams-polyfill';\n\nexport const stopAfter = <Item>(stopCondition: (item: Item) => boolean) =>\n new TransformStream<Item, Item>({\n transform(chunk, controller) {\n controller.enqueue(chunk);\n\n if (stopCondition(chunk)) {\n controller.terminate();\n }\n },\n });\n","import { TransformStream } from 'web-streams-polyfill';\n\nexport const stopOn = <Item>(stopCondition: (item: Item) => boolean) =>\n new TransformStream<Item, Item>({\n async transform(chunk, controller) {\n if (!stopCondition(chunk)) {\n controller.enqueue(chunk);\n return;\n }\n await Promise.resolve();\n controller.terminate();\n },\n });\n","import { TransformStream } from 'web-streams-polyfill';\n\nexport const take = <T>(limit: number) => new TakeTransformStream<T>(limit);\n\nexport class TakeTransformStream<T> extends TransformStream<T, T> {\n private count = 0;\n private limit: number;\n\n constructor(limit: number) {\n super({\n transform: (chunk, controller) => {\n if (this.count < this.limit) {\n this.count++;\n controller.enqueue(chunk);\n } else {\n controller.terminate();\n }\n },\n });\n\n this.limit = limit;\n }\n}\n","import { TransformStream } from 'web-streams-polyfill';\n\nexport const waitAtMost = <Item>(waitTimeInMs: number) =>\n new TransformStream<Item, Item>({\n start(controller) {\n const timeoutId = setTimeout(() => {\n controller.terminate();\n }, waitTimeInMs);\n\n const originalTerminate = controller.terminate.bind(controller);\n\n // Clear the timeout if the stream is terminated early\n controller.terminate = () => {\n clearTimeout(timeoutId);\n originalTerminate();\n };\n },\n transform(chunk, controller) {\n controller.enqueue(chunk);\n },\n });\n","import { ConcurrencyError } from '../errors';\nimport type { Flavour } from '../typing';\nimport type { DefaultStreamVersionType } from './eventStore';\n\nexport type ExpectedStreamVersion<VersionType = DefaultStreamVersionType> =\n | ExpectedStreamVersionWithValue<VersionType>\n | ExpectedStreamVersionGeneral;\n\nexport type ExpectedStreamVersionWithValue<\n VersionType = DefaultStreamVersionType,\n> = Flavour<VersionType, 'StreamVersion'>;\n\nexport type ExpectedStreamVersionGeneral = Flavour<\n 'STREAM_EXISTS' | 'STREAM_DOES_NOT_EXIST' | 'NO_CONCURRENCY_CHECK',\n 'StreamVersion'\n>;\n\nexport const STREAM_EXISTS = 'STREAM_EXISTS' as ExpectedStreamVersionGeneral;\nexport const STREAM_DOES_NOT_EXIST =\n 'STREAM_DOES_NOT_EXIST' as ExpectedStreamVersionGeneral;\nexport const NO_CONCURRENCY_CHECK =\n 'NO_CONCURRENCY_CHECK' as ExpectedStreamVersionGeneral;\n\nexport const matchesExpectedVersion = <\n StreamVersion = DefaultStreamVersionType,\n>(\n current: StreamVersion | undefined,\n expected: ExpectedStreamVersion<StreamVersion>,\n defaultVersion: StreamVersion,\n): boolean => {\n if (expected === NO_CONCURRENCY_CHECK) return true;\n\n if (expected == STREAM_DOES_NOT_EXIST) return current === defaultVersion;\n\n if (expected == STREAM_EXISTS) return current !== defaultVersion;\n\n return current === expected;\n};\n\nexport const assertExpectedVersionMatchesCurrent = <\n StreamVersion = DefaultStreamVersionType,\n>(\n current: StreamVersion,\n expected: ExpectedStreamVersion<StreamVersion> | undefined,\n defaultVersion: StreamVersion,\n): void => {\n expected ??= NO_CONCURRENCY_CHECK;\n\n if (!matchesExpectedVersion(current, expected, defaultVersion))\n throw new ExpectedVersionConflictError(current, expected);\n};\n\nexport class ExpectedVersionConflictError<\n VersionType = DefaultStreamVersionType,\n> extends ConcurrencyError {\n constructor(\n current: VersionType,\n expected: ExpectedStreamVersion<VersionType>,\n ) {\n super(current?.toString(), expected?.toString());\n\n // 👇️ because we are extending a built-in class\n Object.setPrototypeOf(this, ExpectedVersionConflictError.prototype);\n }\n}\n\nexport const isExpectedVersionConflictError = (\n error: unknown,\n): error is ExpectedVersionConflictError =>\n error instanceof ExpectedVersionConflictError;\n","import { filter } from './filter';\nimport { map } from './map';\nimport {\n notifyAboutNoActiveReadersStream,\n NotifyAboutNoActiveReadersStream,\n} from './notifyAboutNoActiveReaders';\nimport { reduce, ReduceTransformStream } from './reduce';\nimport { retryStream } from './retry';\nimport { skip, SkipTransformStream } from './skip';\nimport { stopAfter } from './stopAfter';\nimport { stopOn } from './stopOn';\nimport { take, TakeTransformStream } from './take';\nimport { waitAtMost } from './waitAtMost';\n\nexport const streamTransformations = {\n filter,\n take,\n TakeTransformStream,\n skip,\n SkipTransformStream,\n map,\n notifyAboutNoActiveReadersStream,\n NotifyAboutNoActiveReadersStream,\n reduce,\n ReduceTransformStream,\n retry: retryStream,\n stopAfter,\n stopOn,\n waitAtMost,\n};\n","import {\n ExpectedVersionConflictError,\n STREAM_DOES_NOT_EXIST,\n assertExpectedVersionMatchesCurrent,\n type EventStore,\n type Event,\n type AggregateStreamOptions,\n type AggregateStreamResult,\n type AppendToStreamOptions,\n type AppendToStreamResult,\n type ReadStreamOptions,\n type ReadStreamResult,\n type ReadEvent,\n type ReadEventMetadata,\n type ExpectedStreamVersion,\n} from '@event-driven-io/emmett';\nimport { type Collection, type WithId } from 'mongodb';\nimport { v4 as uuid } from 'uuid';\n\nexport const MongoDBEventStoreDefaultStreamVersion = 0;\n\nexport type StreamType = string;\nexport type StreamName<T extends StreamType = StreamType> = `${T}:${string}`;\n\nexport type StreamNameParts<T extends StreamType = StreamType> = {\n streamType: T;\n streamId: string;\n};\n\nexport type StreamToProject<EventType extends Event> = {\n streamName: StreamName;\n streamType: StreamType;\n streamId: string;\n streamVersion: number;\n events: ReadEvent<EventType, ReadEventMetadata>[];\n};\n\nexport type Projection<EventType extends Event> = (\n stream: StreamToProject<EventType>,\n) => void | Promise<void>;\n\nexport interface EventStream<EventType extends Event = Event> {\n streamName: string;\n events: Array<ReadEvent<EventType, ReadEventMetadata>>;\n createdAt: Date;\n updatedAt: Date;\n}\nexport type EventStreamEvent<EventType extends Event = Event> =\n EventStream<EventType>['events'][number];\n\nexport interface MongoDBConnectionOptions {\n connectionString: string;\n database: string;\n collection?: string;\n}\n\nexport class MongoDBEventStore implements EventStore<number> {\n private readonly collection: Collection<EventStream>;\n\n constructor(collection: typeof this.collection) {\n this.collection = collection;\n }\n\n async readStream<EventType extends Event>(\n streamName: StreamName,\n options?: ReadStreamOptions<number>,\n ): Promise<Exclude<ReadStreamResult<EventType, number>, null>> {\n const expectedStreamVersion = options?.expectedStreamVersion;\n\n const stream = await this.collection.findOne<\n WithId<EventStream<EventType>>\n >({\n streamName: { $eq: streamName },\n });\n\n if (!stream) {\n return {\n events: [],\n currentStreamVersion: MongoDBEventStoreDefaultStreamVersion,\n streamExists: false,\n };\n }\n\n assertExpectedVersionMatchesCurrent(\n stream.events.length,\n expectedStreamVersion,\n MongoDBEventStoreDefaultStreamVersion,\n );\n\n return {\n events: stream.events.slice(0, maxEventIndex(expectedStreamVersion)),\n currentStreamVersion: stream.events.length,\n streamExists: true,\n };\n }\n\n async aggregateStream<State, EventType extends Event>(\n streamName: StreamName,\n options: AggregateStreamOptions<State, EventType, number>,\n ): Promise<AggregateStreamResult<State, number>> {\n const stream = await this.readStream<EventType>(streamName, options?.read);\n const state = stream.events.reduce(options.evolve, options.initialState());\n return {\n state,\n currentStreamVersion: stream.currentStreamVersion,\n streamExists: stream.streamExists,\n };\n }\n\n async appendToStream<EventType extends Event>(\n streamName: StreamName,\n events: EventType[],\n options?: AppendToStreamOptions<number> & {\n /**\n * These will be ran after a the events have been successfully appended to\n * the stream. `appendToStream` will return after every projection is completed.\n */\n projections?: Array<Projection<EventType>>;\n },\n ): Promise<AppendToStreamResult<number>> {\n let stream = await this.collection.findOne({\n streamName: { $eq: streamName },\n });\n let currentStreamPosition = stream?.events.length ?? 0;\n let createdNewStream = false;\n\n if (!stream) {\n const now = new Date();\n const result = await this.collection.insertOne({\n streamName,\n events: [],\n createdAt: now,\n updatedAt: now,\n });\n stream = await this.collection.findOne({\n _id: result.insertedId,\n });\n createdNewStream = true;\n }\n\n const eventCreateInputs: ReadEvent[] = [];\n for (const event of events) {\n currentStreamPosition++;\n eventCreateInputs.push({\n type: event.type,\n data: event.data,\n metadata: {\n now: new Date(),\n eventId: uuid(),\n streamName,\n streamPosition: BigInt(currentStreamPosition),\n ...(event.metadata ?? {}),\n },\n });\n }\n\n // TODO: better error here, should rarely happen if ever\n // if another error was not thrown before this\n if (!stream) throw new Error('Failed to create stream');\n\n assertExpectedVersionMatchesCurrent(\n stream.events.length,\n options?.expectedStreamVersion,\n MongoDBEventStoreDefaultStreamVersion,\n );\n\n // @ts-expect-error The actual `EventType` is different across each stream document,\n // but the collection was instantiated as being `EventStream<Event>`. Unlike `findOne`,\n // `findOneAndUpdate` does not allow a generic to override what the return type is.\n const updatedStream: WithId<EventStream<EventType>> | null =\n await this.collection.findOneAndUpdate(\n {\n streamName: { $eq: streamName },\n events: { $size: stream.events.length },\n },\n {\n $push: { events: { $each: eventCreateInputs } },\n $set: { updatedAt: new Date() },\n },\n { returnDocument: 'after' },\n );\n\n if (!updatedStream) {\n const currentStream = await this.collection.findOne({\n streamName: { $eq: streamName },\n });\n throw new ExpectedVersionConflictError(\n currentStream?.events.length ?? -1,\n stream.events.length,\n );\n }\n\n const { streamType, streamId } = fromStreamName(streamName);\n\n await executeProjections(\n {\n streamName,\n streamType,\n streamId,\n streamVersion: updatedStream.events.length,\n events: updatedStream.events,\n },\n options?.projections,\n );\n\n return {\n nextExpectedStreamVersion: updatedStream.events.length,\n createdNewStream,\n };\n }\n}\n\nexport const getMongoDBEventStore = (collection: Collection<EventStream>) => {\n const eventStore = new MongoDBEventStore(collection);\n return eventStore;\n};\n\nfunction executeProjections<EventType extends Event>(\n params: StreamToProject<EventType>,\n projections?: Array<Projection<EventType>>,\n) {\n return Promise.all((projections ?? []).map((project) => project(params)));\n}\n\nfunction maxEventIndex(\n expectedStreamVersion?: ExpectedStreamVersion<number>,\n): number | undefined {\n if (!expectedStreamVersion) return undefined;\n\n if (typeof expectedStreamVersion === 'number') {\n return expectedStreamVersion;\n }\n\n switch (expectedStreamVersion) {\n case STREAM_DOES_NOT_EXIST:\n return 0;\n default:\n return undefined;\n }\n}\n\n/**\n * Accepts a `streamType` (the type/category of the event stream) and an `streamId`\n * (the individual entity/object or aggregate ID) and combines them to a singular\n * `streamName` which can be used in `EventStore`.\n */\nexport function toStreamName<T extends StreamType>(\n streamType: T,\n streamId: string,\n): StreamName<T> {\n return `${streamType}:${streamId}`;\n}\n\n/**\n * Accepts a fully formatted `streamName` and returns the broken down\n * `streamType` and `streamId`.\n */\nexport function fromStreamName<T extends StreamType>(\n streamName: StreamName<T>,\n): StreamNameParts<T> {\n const parts = streamName.split(':') as [T, string];\n return {\n streamType: parts[0],\n streamId: parts[1],\n };\n}\n"]}
1
+ {"version":3,"sources":["/home/runner/work/emmett/emmett/src/packages/emmett-mongodb/dist/index.cjs","../../emmett/src/validation/index.ts","../../emmett/src/errors/index.ts","../../emmett/src/eventStore/inMemoryEventStore.ts","../../emmett/src/eventStore/subscriptions/caughtUpTransformStream.ts","../../emmett/src/eventStore/subscriptions/streamingCoordinator.ts","../../emmett/src/streaming/transformations/notifyAboutNoActiveReaders.ts","../../emmett/src/utils/retry.ts","../../emmett/src/streaming/generators/fromArray.ts","../../emmett/src/streaming/restream.ts","../../emmett/src/streaming/transformations/filter.ts","../../emmett/src/streaming/transformations/map.ts","../../emmett/src/streaming/transformations/reduce.ts","../../emmett/src/streaming/transformations/retry.ts","../../emmett/src/streaming/transformations/skip.ts","../../emmett/src/streaming/transformations/stopAfter.ts","../../emmett/src/streaming/transformations/stopOn.ts","../../emmett/src/streaming/transformations/take.ts","../../emmett/src/streaming/transformations/waitAtMost.ts","../../emmett/src/eventStore/expectedVersion.ts","../../emmett/src/streaming/transformations/index.ts","../src/eventStore/mongoDBEventStore.ts","../src/eventStore/projections/index.ts"],"names":[],"mappings":"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","file":"/home/runner/work/emmett/emmett/src/packages/emmett-mongodb/dist/index.cjs","sourcesContent":[null,"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\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 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 : 500;\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\nexport class ConcurrencyError extends EmmettError {\n constructor(\n public current: string | undefined,\n public expected: string,\n message?: string,\n ) {\n super({\n errorCode: 412,\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\nexport class ValidationError extends EmmettError {\n constructor(message?: string) {\n super({\n errorCode: 400,\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: 403,\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: 404,\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 type {\n BigIntStreamPosition,\n Event,\n ReadEvent,\n ReadEventMetadataWithGlobalPosition,\n} from '../typing';\nimport {\n type AggregateStreamOptions,\n type AggregateStreamResult,\n type AppendToStreamOptions,\n type AppendToStreamResult,\n type EventStore,\n type ReadStreamOptions,\n type ReadStreamResult,\n} from './eventStore';\nimport { assertExpectedVersionMatchesCurrent } from './expectedVersion';\nimport { StreamingCoordinator } from './subscriptions';\n\nexport type EventHandler<E extends Event = Event> = (\n eventEnvelope: ReadEvent<E>,\n) => void;\n\nexport const InMemoryEventStoreDefaultStreamVersion = 0n;\n\nexport type InMemoryEventStore =\n EventStore<ReadEventMetadataWithGlobalPosition>;\n\nexport const getInMemoryEventStore = (): InMemoryEventStore => {\n const streams = new Map<\n string,\n ReadEvent<Event, ReadEventMetadataWithGlobalPosition>[]\n >();\n const streamingCoordinator = StreamingCoordinator();\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 return {\n async aggregateStream<State, EventType extends Event>(\n streamName: string,\n options: AggregateStreamOptions<\n State,\n EventType,\n ReadEventMetadataWithGlobalPosition\n >,\n ): Promise<AggregateStreamResult<State>> {\n const { evolve, initialState, read } = options;\n\n const result = await this.readStream<EventType>(streamName, read);\n\n const events = result?.events ?? [];\n\n return {\n currentStreamVersion: BigInt(events.length),\n state: events.reduce(evolve, initialState()),\n streamExists: result.streamExists,\n };\n },\n\n readStream: <EventType extends Event>(\n streamName: string,\n options?: ReadStreamOptions<BigIntStreamPosition>,\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' in options ? options.from : 0);\n const to = Number(\n options && 'to' in options\n ? options.to\n : options && 'maxCount' in options && options.maxCount\n ? options.from + options.maxCount\n : (events?.length ?? 1),\n );\n\n const resultEvents =\n events !== undefined && events.length > 0\n ? events\n .map(\n (e) =>\n e as ReadEvent<\n EventType,\n ReadEventMetadataWithGlobalPosition\n >,\n )\n .slice(from, to)\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 <EventType extends Event>(\n streamName: string,\n events: EventType[],\n options?: AppendToStreamOptions,\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 return {\n ...event,\n metadata: {\n ...(event.metadata ?? {}),\n streamName,\n eventId: uuid(),\n streamPosition: BigInt(currentEvents.length + index + 1),\n globalPosition: BigInt(getAllEventsCount() + index + 1),\n },\n };\n });\n\n const positionOfLastEventInTheStream = BigInt(\n newEvents.slice(-1)[0]!.metadata.streamPosition,\n );\n\n streams.set(streamName, [...currentEvents, ...newEvents]);\n await streamingCoordinator.notify(newEvents);\n\n const result: AppendToStreamResult = {\n nextExpectedStreamVersion: positionOfLastEventInTheStream,\n createdNewStream:\n currentStreamVersion === InMemoryEventStoreDefaultStreamVersion,\n };\n\n return result;\n },\n\n //streamEvents: streamingCoordinator.stream,\n };\n};\n","import { TransformStream } from 'web-streams-polyfill';\nimport type {\n Event,\n ReadEvent,\n ReadEventMetadataWithGlobalPosition,\n} from '../../typing';\nimport { globalStreamCaughtUp, type GlobalSubscriptionEvent } from '../events';\n\nexport const streamTrackingGlobalPosition = (\n currentEvents: ReadEvent<Event, ReadEventMetadataWithGlobalPosition>[],\n) => new CaughtUpTransformStream(currentEvents);\n\nexport class CaughtUpTransformStream extends TransformStream<\n ReadEvent<Event, ReadEventMetadataWithGlobalPosition>,\n | ReadEvent<Event, ReadEventMetadataWithGlobalPosition>\n | GlobalSubscriptionEvent\n> {\n private _currentPosition: bigint;\n private _logPosition: bigint;\n\n constructor(events: ReadEvent<Event, ReadEventMetadataWithGlobalPosition>[]) {\n super({\n start: (controller) => {\n let globalPosition = 0n;\n for (const event of events) {\n controller.enqueue(event);\n globalPosition = event.metadata.globalPosition;\n }\n controller.enqueue(globalStreamCaughtUp({ globalPosition }));\n },\n transform: (event, controller) => {\n this._currentPosition = event.metadata.globalPosition;\n controller.enqueue(event);\n\n if (this._currentPosition < this._logPosition) return;\n\n controller.enqueue(\n globalStreamCaughtUp({ globalPosition: this._currentPosition }),\n );\n },\n });\n\n this._currentPosition = this._logPosition =\n events.length > 0\n ? events[events.length - 1]!.metadata.globalPosition\n : 0n;\n }\n\n public set logPosition(value: bigint) {\n this._logPosition = value;\n }\n}\n","import { v4 as uuid } from 'uuid';\nimport { notifyAboutNoActiveReadersStream } from '../../streaming/transformations/notifyAboutNoActiveReaders';\nimport { writeToStream } from '../../streaming/writers';\nimport type {\n Event,\n ReadEvent,\n ReadEventMetadataWithGlobalPosition,\n} from '../../typing';\nimport {\n CaughtUpTransformStream,\n streamTrackingGlobalPosition,\n} from './caughtUpTransformStream';\n\nexport const StreamingCoordinator = () => {\n const allEvents: ReadEvent<Event, ReadEventMetadataWithGlobalPosition>[] = [];\n const listeners = new Map<string, CaughtUpTransformStream>();\n\n return {\n notify: async (\n events: ReadEvent<Event, ReadEventMetadataWithGlobalPosition>[],\n ) => {\n if (events.length === 0) return;\n\n allEvents.push(...events);\n\n for (const listener of listeners.values()) {\n listener.logPosition =\n events[events.length - 1]!.metadata.globalPosition;\n\n await writeToStream(listener, events);\n }\n },\n\n stream: () => {\n const streamId = uuid();\n const transformStream = streamTrackingGlobalPosition(allEvents);\n\n listeners.set(streamId, transformStream);\n return transformStream.readable.pipeThrough(\n notifyAboutNoActiveReadersStream(\n (stream) => {\n if (listeners.has(stream.streamId))\n listeners.delete(stream.streamId);\n },\n { streamId },\n ),\n );\n },\n };\n};\n","import { v4 as uuid } from 'uuid';\nimport { TransformStream } from 'web-streams-polyfill';\n\nexport const notifyAboutNoActiveReadersStream = <Item>(\n onNoActiveReaderCallback: (\n stream: NotifyAboutNoActiveReadersStream<Item>,\n ) => void,\n options: { streamId?: string; intervalCheckInMs?: number } = {},\n) => new NotifyAboutNoActiveReadersStream(onNoActiveReaderCallback, options);\n\nexport class NotifyAboutNoActiveReadersStream<Item> extends TransformStream<\n Item,\n Item\n> {\n private checkInterval: NodeJS.Timeout | null = null;\n public readonly streamId: string;\n private _isStopped: boolean = false;\n public get hasActiveSubscribers() {\n return !this._isStopped;\n }\n\n constructor(\n private onNoActiveReaderCallback: (\n stream: NotifyAboutNoActiveReadersStream<Item>,\n ) => void,\n options: { streamId?: string; intervalCheckInMs?: number } = {},\n ) {\n super({\n cancel: (reason) => {\n console.log('Stream was canceled. Reason:', reason);\n this.stopChecking();\n },\n });\n this.streamId = options?.streamId ?? uuid();\n\n this.onNoActiveReaderCallback = onNoActiveReaderCallback;\n\n this.startChecking(options?.intervalCheckInMs ?? 20);\n }\n\n private startChecking(interval: number) {\n this.checkInterval = setInterval(() => {\n this.checkNoActiveReader();\n }, interval);\n }\n\n private stopChecking() {\n if (!this.checkInterval) return;\n\n clearInterval(this.checkInterval);\n this.checkInterval = null;\n this._isStopped = true;\n this.onNoActiveReaderCallback(this);\n }\n\n private checkNoActiveReader() {\n if (!this.readable.locked && !this._isStopped) {\n this.stopChecking();\n }\n }\n}\n","import retry from 'async-retry';\n\nexport type AsyncRetryOptions = retry.Options & {\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,\n): Promise<T> => {\n if (opts === undefined || opts.retries === 0) return fn();\n\n return retry(\n async (bail) => {\n try {\n return await fn();\n } catch (error) {\n if (opts?.shouldRetryError && !opts.shouldRetryError(error)) {\n bail(error as Error);\n }\n throw error;\n }\n },\n opts ?? { retries: 0 },\n );\n};\n","import { ReadableStream } from 'web-streams-polyfill';\n\nexport const fromArray = <T>(chunks: T[]) =>\n new ReadableStream<T>({\n start(controller) {\n for (const chunk of chunks) controller.enqueue(chunk);\n controller.close();\n },\n });\n","import {\n type ReadableStream,\n type ReadableStreamDefaultReadResult,\n type TransformStreamDefaultController,\n} from 'web-streams-polyfill';\nimport type { AsyncRetryOptions } from '../utils';\nimport type { Decoder } from './decoders';\nimport { DefaultDecoder } from './decoders/composite';\nimport { streamTransformations } from './transformations';\n\nconst { retry } = streamTransformations;\n\nexport const restream = <\n Source = unknown,\n Transformed = Source,\n StreamType = Source,\n>(\n createSourceStream: () => ReadableStream<StreamType>,\n transform: (input: Source) => Transformed = (source) =>\n source as unknown as Transformed,\n retryOptions: AsyncRetryOptions = { forever: true, minTimeout: 25 },\n decoder: Decoder<StreamType, Source> = new DefaultDecoder<Source>(),\n): ReadableStream<Transformed> =>\n retry(createSourceStream, handleChunk(transform, decoder), retryOptions)\n .readable;\n\nconst handleChunk =\n <Source = unknown, Transformed = Source, StreamType = Source>(\n transform: (input: Source) => Transformed = (source) =>\n source as unknown as Transformed,\n decoder: Decoder<StreamType, Source> = new DefaultDecoder<Source>(),\n ) =>\n (\n readResult: ReadableStreamDefaultReadResult<StreamType>,\n controller: TransformStreamDefaultController<Transformed>,\n ): void => {\n const { done: isDone, value } = readResult;\n\n if (value) decoder.addToBuffer(value);\n\n if (!isDone && !decoder.hasCompleteMessage()) return;\n\n decodeAndTransform(decoder, transform, controller);\n };\n\nconst decodeAndTransform = <StreamType, Source, Transformed = Source>(\n decoder: Decoder<StreamType, Source>,\n transform: (input: Source) => Transformed,\n controller: TransformStreamDefaultController<Transformed>,\n) => {\n try {\n const decoded = decoder.decode();\n if (!decoded) return; // TODO: Add a proper handling of decode errors\n\n const transformed = transform(decoded);\n controller.enqueue(transformed);\n } catch (error) {\n controller.error(new Error(`Decoding error: ${error?.toString()}`));\n }\n};\n","import { TransformStream } from 'web-streams-polyfill';\n\nexport const filter = <Item>(filter: (item: Item) => boolean) =>\n new TransformStream<Item, Item>({\n transform(chunk, controller) {\n if (filter(chunk)) {\n controller.enqueue(chunk);\n }\n },\n });\n","import { TransformStream } from 'web-streams-polyfill';\n\nexport const map = <From, To>(map: (item: From) => To) =>\n new TransformStream<From, To>({\n transform(chunk, controller) {\n controller.enqueue(map(chunk));\n },\n });\n","import { TransformStream } from 'web-streams-polyfill';\n\nexport const reduce = <I, O>(\n reducer: (accumulator: O, chunk: I) => O,\n initialValue: O,\n) => new ReduceTransformStream<I, O>(reducer, initialValue);\n\nexport class ReduceTransformStream<I, O> extends TransformStream<I, O> {\n private accumulator: O;\n private reducer: (accumulator: O, chunk: I) => O;\n\n constructor(reducer: (accumulator: O, chunk: I) => O, initialValue: O) {\n super({\n transform: (chunk) => {\n this.accumulator = this.reducer(this.accumulator, chunk);\n },\n flush: (controller) => {\n controller.enqueue(this.accumulator);\n controller.terminate();\n },\n });\n\n this.accumulator = initialValue;\n this.reducer = reducer;\n }\n}\n","import {\n type ReadableStream,\n type ReadableStreamDefaultReadResult,\n TransformStream,\n type TransformStreamDefaultController,\n} from 'web-streams-polyfill';\nimport { type AsyncRetryOptions, asyncRetry } from '../../utils';\n\nexport const retryStream = <\n Source = unknown,\n Transformed = Source,\n StreamType = Source,\n>(\n createSourceStream: () => ReadableStream<StreamType>,\n handleChunk: (\n readResult: ReadableStreamDefaultReadResult<StreamType>,\n controller: TransformStreamDefaultController<Transformed>,\n ) => Promise<void> | void,\n retryOptions: AsyncRetryOptions = { forever: true, minTimeout: 25 },\n): TransformStream<Source, Transformed> =>\n new TransformStream<Source, Transformed>({\n start(controller) {\n asyncRetry(\n () => onRestream(createSourceStream, handleChunk, controller),\n retryOptions,\n ).catch((error) => {\n controller.error(error);\n });\n },\n });\n\nconst onRestream = async <StreamType, Source, Transformed = Source>(\n createSourceStream: () => ReadableStream<StreamType>,\n handleChunk: (\n readResult: ReadableStreamDefaultReadResult<StreamType>,\n controller: TransformStreamDefaultController<Transformed>,\n ) => Promise<void> | void,\n controller: TransformStreamDefaultController<Transformed>,\n): Promise<void> => {\n const sourceStream = createSourceStream();\n const reader = sourceStream.getReader();\n\n try {\n let done: boolean;\n\n do {\n const result = await reader.read();\n done = result.done;\n\n await handleChunk(result, controller);\n\n if (done) {\n controller.terminate();\n }\n } while (!done);\n } finally {\n reader.releaseLock();\n }\n};\n","import { TransformStream } from 'web-streams-polyfill';\n\nexport const skip = <T>(limit: number) => new SkipTransformStream<T>(limit);\n\nexport class SkipTransformStream<T> extends TransformStream<T, T> {\n private count = 0;\n private skip: number;\n\n constructor(skip: number) {\n super({\n transform: (chunk, controller) => {\n this.count++;\n if (this.count > this.skip) {\n controller.enqueue(chunk);\n }\n },\n });\n\n this.skip = skip;\n }\n}\n","import { TransformStream } from 'web-streams-polyfill';\n\nexport const stopAfter = <Item>(stopCondition: (item: Item) => boolean) =>\n new TransformStream<Item, Item>({\n transform(chunk, controller) {\n controller.enqueue(chunk);\n\n if (stopCondition(chunk)) {\n controller.terminate();\n }\n },\n });\n","import { TransformStream } from 'web-streams-polyfill';\n\nexport const stopOn = <Item>(stopCondition: (item: Item) => boolean) =>\n new TransformStream<Item, Item>({\n async transform(chunk, controller) {\n if (!stopCondition(chunk)) {\n controller.enqueue(chunk);\n return;\n }\n await Promise.resolve();\n controller.terminate();\n },\n });\n","import { TransformStream } from 'web-streams-polyfill';\n\nexport const take = <T>(limit: number) => new TakeTransformStream<T>(limit);\n\nexport class TakeTransformStream<T> extends TransformStream<T, T> {\n private count = 0;\n private limit: number;\n\n constructor(limit: number) {\n super({\n transform: (chunk, controller) => {\n if (this.count < this.limit) {\n this.count++;\n controller.enqueue(chunk);\n } else {\n controller.terminate();\n }\n },\n });\n\n this.limit = limit;\n }\n}\n","import { TransformStream } from 'web-streams-polyfill';\n\nexport const waitAtMost = <Item>(waitTimeInMs: number) =>\n new TransformStream<Item, Item>({\n start(controller) {\n const timeoutId = setTimeout(() => {\n controller.terminate();\n }, waitTimeInMs);\n\n const originalTerminate = controller.terminate.bind(controller);\n\n // Clear the timeout if the stream is terminated early\n controller.terminate = () => {\n clearTimeout(timeoutId);\n originalTerminate();\n };\n },\n transform(chunk, controller) {\n controller.enqueue(chunk);\n },\n });\n","import { ConcurrencyError } from '../errors';\nimport type { BigIntStreamPosition, Flavour } from '../typing';\n\nexport type ExpectedStreamVersion<VersionType = BigIntStreamPosition> =\n | ExpectedStreamVersionWithValue<VersionType>\n | ExpectedStreamVersionGeneral;\n\nexport type ExpectedStreamVersionWithValue<VersionType = BigIntStreamPosition> =\n Flavour<VersionType, 'StreamVersion'>;\n\nexport type ExpectedStreamVersionGeneral = Flavour<\n 'STREAM_EXISTS' | 'STREAM_DOES_NOT_EXIST' | 'NO_CONCURRENCY_CHECK',\n 'StreamVersion'\n>;\n\nexport const STREAM_EXISTS = 'STREAM_EXISTS' as ExpectedStreamVersionGeneral;\nexport const STREAM_DOES_NOT_EXIST =\n 'STREAM_DOES_NOT_EXIST' as ExpectedStreamVersionGeneral;\nexport const NO_CONCURRENCY_CHECK =\n 'NO_CONCURRENCY_CHECK' as ExpectedStreamVersionGeneral;\n\nexport const matchesExpectedVersion = <StreamVersion = BigIntStreamPosition>(\n current: StreamVersion | undefined,\n expected: ExpectedStreamVersion<StreamVersion>,\n defaultVersion: StreamVersion,\n): boolean => {\n if (expected === NO_CONCURRENCY_CHECK) return true;\n\n if (expected == STREAM_DOES_NOT_EXIST) return current === defaultVersion;\n\n if (expected == STREAM_EXISTS) return current !== defaultVersion;\n\n return current === expected;\n};\n\nexport const assertExpectedVersionMatchesCurrent = <\n StreamVersion = BigIntStreamPosition,\n>(\n current: StreamVersion,\n expected: ExpectedStreamVersion<StreamVersion> | undefined,\n defaultVersion: StreamVersion,\n): void => {\n expected ??= NO_CONCURRENCY_CHECK;\n\n if (!matchesExpectedVersion(current, expected, defaultVersion))\n throw new ExpectedVersionConflictError(current, expected);\n};\n\nexport class ExpectedVersionConflictError<\n VersionType = BigIntStreamPosition,\n> extends ConcurrencyError {\n constructor(\n current: VersionType,\n expected: ExpectedStreamVersion<VersionType>,\n ) {\n super(current?.toString(), expected?.toString());\n\n // 👇️ because we are extending a built-in class\n Object.setPrototypeOf(this, ExpectedVersionConflictError.prototype);\n }\n}\n\nexport const isExpectedVersionConflictError = (\n error: unknown,\n): error is ExpectedVersionConflictError =>\n error instanceof ExpectedVersionConflictError;\n","import { filter } from './filter';\nimport { map } from './map';\nimport {\n notifyAboutNoActiveReadersStream,\n NotifyAboutNoActiveReadersStream,\n} from './notifyAboutNoActiveReaders';\nimport { reduce, ReduceTransformStream } from './reduce';\nimport { retryStream } from './retry';\nimport { skip, SkipTransformStream } from './skip';\nimport { stopAfter } from './stopAfter';\nimport { stopOn } from './stopOn';\nimport { take, TakeTransformStream } from './take';\nimport { waitAtMost } from './waitAtMost';\n\nexport const streamTransformations = {\n filter,\n take,\n TakeTransformStream,\n skip,\n SkipTransformStream,\n map,\n notifyAboutNoActiveReadersStream,\n NotifyAboutNoActiveReadersStream,\n reduce,\n ReduceTransformStream,\n retry: retryStream,\n stopAfter,\n stopOn,\n waitAtMost,\n};\n","import {\n ExpectedVersionConflictError,\n STREAM_DOES_NOT_EXIST,\n assertExpectedVersionMatchesCurrent,\n type AggregateStreamOptions,\n type AggregateStreamResult,\n type AppendToStreamOptions,\n type AppendToStreamResult,\n type Event,\n type EventMetaDataOf,\n type EventStore,\n type ExpectedStreamVersion,\n type ProjectionRegistration,\n type ReadEvent,\n type ReadEventMetadataWithoutGlobalPosition,\n type ReadStreamOptions,\n type ReadStreamResult,\n} from '@event-driven-io/emmett';\nimport {\n type Collection,\n type Document,\n type UpdateFilter,\n type WithId,\n} from 'mongodb';\nimport { v4 as uuid } from 'uuid';\nimport {\n handleInlineProjections,\n type MongoDBInlineProjectionDefinition,\n type MongoDBProjectionInlineHandlerContext,\n} from './projections';\n\nexport const MongoDBEventStoreDefaultStreamVersion = 0n;\n\nexport type StreamType = string;\nexport type StreamName<T extends StreamType = StreamType> = `${T}:${string}`;\n\nexport type StreamNameParts<T extends StreamType = StreamType> = {\n streamType: T;\n streamId: string;\n};\n\nexport type MongoDBReadModelMetadata = {\n name: string;\n schemaVersion: number;\n streamPosition: bigint;\n};\n\nexport type MongoDBReadModel<Doc extends Document = Document> = Doc & {\n _metadata: MongoDBReadModelMetadata;\n};\n\nexport interface EventStream<\n EventType extends Event = Event,\n EventMetaDataType extends EventMetaDataOf<EventType> &\n MongoDBReadEventMetadata = EventMetaDataOf<EventType> &\n MongoDBReadEventMetadata,\n> {\n streamName: string;\n events: Array<ReadEvent<EventType, EventMetaDataType>>;\n metadata: {\n streamId: string;\n streamType: StreamType;\n streamPosition: bigint;\n createdAt: Date;\n updatedAt: Date;\n };\n projections: Record<string, MongoDBReadModel>;\n}\n\nexport interface MongoDBConnectionOptions {\n connectionString: string;\n database: string;\n collection?: string;\n}\n\nexport type MongoDBReadEventMetadata =\n ReadEventMetadataWithoutGlobalPosition<bigint>;\n\nexport type MongoDBReadEvent<EventType extends Event = Event> = ReadEvent<\n EventType,\n MongoDBReadEventMetadata\n>;\n\nexport type MongoDBEventStoreOptions = {\n collection: Collection<EventStream>;\n projections?: ProjectionRegistration<\n 'inline',\n MongoDBReadEventMetadata,\n MongoDBProjectionInlineHandlerContext\n >[];\n};\n\nexport class MongoDBEventStore implements EventStore<MongoDBReadEventMetadata> {\n private readonly collection: Collection<EventStream>;\n private readonly inlineProjections: MongoDBInlineProjectionDefinition[];\n\n constructor(options: MongoDBEventStoreOptions) {\n this.collection = options.collection;\n this.inlineProjections = (options.projections ?? [])\n .filter(({ type }) => type === 'inline')\n .map(\n ({ projection }) => projection,\n ) as MongoDBInlineProjectionDefinition[];\n }\n\n async readStream<EventType extends Event>(\n streamName: StreamName,\n options?: ReadStreamOptions,\n ): Promise<\n Exclude<ReadStreamResult<EventType, MongoDBReadEventMetadata>, null>\n > {\n const expectedStreamVersion = options?.expectedStreamVersion;\n // TODO: use `to` and `from`\n\n const filter = {\n streamName: { $eq: streamName },\n };\n\n const eventsSliceArr: number[] = [];\n\n if (options && 'from' in options) {\n eventsSliceArr.push(Number(options.from));\n } else {\n eventsSliceArr.push(0);\n }\n\n if (options && 'to' in options) {\n eventsSliceArr.push(Number(options.to));\n }\n\n const eventsSlice =\n eventsSliceArr.length > 1 ? { $slice: eventsSliceArr } : 1;\n\n const stream = await this.collection.findOne<\n WithId<Pick<EventStream<EventType>, 'metadata' | 'events'>>\n >(filter, {\n useBigInt64: true,\n projection: {\n metadata: 1,\n events: eventsSlice,\n },\n });\n\n if (!stream) {\n return {\n events: [],\n currentStreamVersion: MongoDBEventStoreDefaultStreamVersion,\n streamExists: false,\n };\n }\n\n assertExpectedVersionMatchesCurrent(\n stream.metadata.streamPosition,\n expectedStreamVersion,\n MongoDBEventStoreDefaultStreamVersion,\n );\n\n return {\n events: stream.events,\n currentStreamVersion: stream.metadata.streamPosition,\n streamExists: true,\n };\n }\n\n async aggregateStream<State, EventType extends Event>(\n streamName: StreamName,\n options: AggregateStreamOptions<State, EventType, MongoDBReadEventMetadata>,\n ): Promise<AggregateStreamResult<State>> {\n const stream = await this.readStream<EventType>(streamName, options?.read);\n\n const state = stream.events.reduce(options.evolve, options.initialState());\n return {\n state,\n currentStreamVersion: stream.currentStreamVersion,\n streamExists: stream.streamExists,\n };\n }\n\n async appendToStream<EventType extends Event>(\n streamName: StreamName,\n events: EventType[],\n options?: AppendToStreamOptions,\n ): Promise<AppendToStreamResult> {\n const expectedStreamVersion = options?.expectedStreamVersion;\n\n const stream = await this.collection.findOne<\n WithId<Pick<EventStream<EventType>, 'metadata' | 'projections'>>\n >(\n { streamName: { $eq: streamName } },\n {\n useBigInt64: true,\n projection: {\n 'metadata.streamPosition': 1,\n projections: 1,\n },\n },\n );\n\n const currentStreamVersion =\n stream?.metadata.streamPosition ?? MongoDBEventStoreDefaultStreamVersion;\n\n assertExpectedVersionMatchesCurrent(\n currentStreamVersion,\n expectedStreamVersion,\n MongoDBEventStoreDefaultStreamVersion,\n );\n\n let streamOffset = currentStreamVersion;\n\n const eventsToAppend: ReadEvent<\n EventType,\n EventMetaDataOf<EventType> & MongoDBReadEventMetadata\n >[] = events.map((event) => {\n const metadata: MongoDBReadEventMetadata = {\n eventId: uuid(),\n streamName,\n streamPosition: ++streamOffset,\n };\n return {\n type: event.type,\n data: event.data,\n metadata: {\n ...metadata,\n ...(event.metadata ?? {}),\n },\n } as ReadEvent<\n EventType,\n EventMetaDataOf<EventType> & MongoDBReadEventMetadata\n >;\n });\n\n const updates: UpdateFilter<EventStream> = {\n $push: { events: { $each: eventsToAppend } },\n $set: { 'metadata.updatedAt': new Date() },\n $inc: { 'metadata.streamPosition': BigInt(events.length) },\n };\n\n if (this.inlineProjections) {\n await handleInlineProjections({\n readModels: stream?.projections ?? {},\n events: eventsToAppend,\n projections: this.inlineProjections,\n collection: this.collection,\n updates,\n client: {},\n });\n }\n\n const updatedStream = await this.collection.updateOne(\n {\n streamName: { $eq: streamName },\n 'metadata.streamPosition': toExpectedVersion(\n options?.expectedStreamVersion,\n ),\n },\n updates,\n { useBigInt64: true, upsert: true },\n );\n\n if (!updatedStream) {\n throw new ExpectedVersionConflictError(\n currentStreamVersion,\n options?.expectedStreamVersion ?? 0n,\n );\n }\n\n return {\n nextExpectedStreamVersion:\n currentStreamVersion + BigInt(eventsToAppend.length),\n createdNewStream:\n currentStreamVersion === MongoDBEventStoreDefaultStreamVersion,\n };\n }\n}\n\nexport const getMongoDBEventStore = (\n options: ConstructorParameters<typeof MongoDBEventStore>[0],\n) => {\n const eventStore = new MongoDBEventStore(options);\n return eventStore;\n};\n\nfunction toExpectedVersion(\n expectedStreamVersion?: ExpectedStreamVersion,\n): bigint | undefined {\n if (!expectedStreamVersion) return undefined;\n\n if (typeof expectedStreamVersion === 'string') {\n switch (expectedStreamVersion) {\n case STREAM_DOES_NOT_EXIST:\n return BigInt(0);\n default:\n return undefined;\n }\n }\n\n return expectedStreamVersion;\n}\n\n/**\n * Accepts a `streamType` (the type/category of the event stream) and an `streamId`\n * (the individual entity/object or aggregate ID) and combines them to a singular\n * `streamName` which can be used in `EventStore`.\n */\nexport function toStreamName<T extends StreamType>(\n streamType: T,\n streamId: string,\n): StreamName<T> {\n return `${streamType}:${streamId}`;\n}\n\n/**\n * Accepts a fully formatted `streamName` and returns the broken down\n * `streamType` and `streamId`.\n */\nexport function fromStreamName<T extends StreamType>(\n streamName: StreamName<T>,\n): StreamNameParts<T> {\n const parts = streamName.split(':') as [T, string];\n return {\n streamType: parts[0],\n streamId: parts[1],\n };\n}\n","import {\n type CanHandle,\n type Event,\n type EventMetaDataOf,\n type ProjectionHandler,\n type ReadEvent,\n type TypedProjectionDefinition,\n} from '@event-driven-io/emmett';\nimport type { Collection, Document, UpdateFilter } from 'mongodb';\nimport type {\n EventStream,\n MongoDBReadEventMetadata,\n MongoDBReadModel,\n MongoDBReadModelMetadata,\n} from '../mongoDBEventStore';\n\nexport type MongoDBProjectionInlineHandlerContext<\n EventType extends Event = Event,\n EventMetaDataType extends EventMetaDataOf<EventType> &\n MongoDBReadEventMetadata = EventMetaDataOf<EventType> &\n MongoDBReadEventMetadata,\n> = {\n document: MongoDBReadModel | null;\n updates: UpdateFilter<EventStream<EventType, EventMetaDataType>>;\n collection: Collection<EventStream<EventType, EventMetaDataType>>;\n};\n\nexport type MongoDBInlineProjectionHandler<\n EventType extends Event = Event,\n EventMetaDataType extends EventMetaDataOf<EventType> &\n MongoDBReadEventMetadata = EventMetaDataOf<EventType> &\n MongoDBReadEventMetadata,\n> = ProjectionHandler<\n EventType,\n EventMetaDataType,\n MongoDBProjectionInlineHandlerContext\n>;\n\nexport type MongoDBInlineProjectionDefinition<\n EventType extends Event = Event,\n EventMetaDataType extends EventMetaDataOf<EventType> &\n MongoDBReadEventMetadata = EventMetaDataOf<EventType> &\n MongoDBReadEventMetadata,\n> = TypedProjectionDefinition<\n EventType,\n EventMetaDataType,\n MongoDBProjectionInlineHandlerContext\n> & { name: string };\n\nexport type InlineProjectionHandlerOptions<\n EventType extends Event = Event,\n EventMetaDataType extends EventMetaDataOf<EventType> &\n MongoDBReadEventMetadata = EventMetaDataOf<EventType> &\n MongoDBReadEventMetadata,\n> = {\n readModels: Record<string, MongoDBReadModel>;\n events: Array<ReadEvent<EventType, EventMetaDataType>>;\n projections: MongoDBInlineProjectionDefinition<\n EventType,\n EventMetaDataType\n >[];\n collection: Collection<EventStream>;\n updates: UpdateFilter<EventStream<Event>>;\n // eslint-disable-next-line @typescript-eslint/no-empty-object-type\n client: {\n //todo: add client here\n };\n};\n\nexport const handleInlineProjections = async <\n EventType extends Event = Event,\n EventMetaDataType extends EventMetaDataOf<EventType> &\n MongoDBReadEventMetadata = EventMetaDataOf<EventType> &\n MongoDBReadEventMetadata,\n>(\n options: InlineProjectionHandlerOptions<EventType, EventMetaDataType>,\n): Promise<void> => {\n const {\n events,\n projections: allProjections,\n updates: update,\n collection,\n readModels,\n } = options;\n\n const eventTypes = events.map((e) => e.type);\n\n const projections = allProjections.filter((p) =>\n p.canHandle.some((type) => eventTypes.includes(type)),\n );\n\n for (const projection of projections) {\n await projection.handle(events, {\n document: readModels[projection.name] ?? null,\n collection,\n updates: update,\n });\n }\n};\n\nexport type MongoDBWithNotNullDocumentEvolve<\n Doc extends Document,\n EventType extends Event,\n EventMetaDataType extends EventMetaDataOf<EventType> &\n MongoDBReadEventMetadata = EventMetaDataOf<EventType> &\n MongoDBReadEventMetadata,\n> =\n | ((\n document: Doc,\n event: ReadEvent<EventType, EventMetaDataType>,\n ) => Doc | null)\n | ((document: Doc, event: ReadEvent<EventType>) => Promise<Doc | null>);\n\nexport type MongoDBWithNullableDocumentEvolve<\n Doc extends Document,\n EventType extends Event,\n EventMetaDataType extends EventMetaDataOf<EventType> &\n MongoDBReadEventMetadata = EventMetaDataOf<EventType> &\n MongoDBReadEventMetadata,\n> =\n | ((\n document: Doc | null,\n event: ReadEvent<EventType, EventMetaDataType>,\n ) => Doc | null)\n | ((\n document: Doc | null,\n event: ReadEvent<EventType>,\n ) => Promise<Doc | null>);\n\nexport type MongoDBInlineProjectionOptions<\n Doc extends Document,\n EventType extends Event,\n EventMetaDataType extends EventMetaDataOf<EventType> &\n MongoDBReadEventMetadata = EventMetaDataOf<EventType> &\n MongoDBReadEventMetadata,\n> = {\n name?: string;\n schemaVersion?: number;\n canHandle: CanHandle<EventType>;\n} & (\n | {\n evolve: MongoDBWithNullableDocumentEvolve<\n Doc,\n EventType,\n EventMetaDataType\n >;\n }\n | {\n evolve: MongoDBWithNotNullDocumentEvolve<\n Doc,\n EventType,\n EventMetaDataType\n >;\n initialState: () => Doc;\n }\n);\n\nexport const mongoDBInlineProjection = <\n Doc extends Document,\n EventType extends Event,\n EventMetaDataType extends EventMetaDataOf<EventType> &\n MongoDBReadEventMetadata = EventMetaDataOf<EventType> &\n MongoDBReadEventMetadata,\n>(\n options: MongoDBInlineProjectionOptions<Doc, EventType, EventMetaDataType>,\n): MongoDBInlineProjectionDefinition => {\n const projectionName = options.name ?? '_default';\n const schemaVersion = options.schemaVersion ?? 1;\n\n return {\n name: projectionName,\n canHandle: options.canHandle,\n handle: async (events, { document, updates }) => {\n if (events.length === 0) return;\n\n let state =\n 'initialState' in options\n ? (document ?? options.initialState())\n : document;\n\n for (const event of events) {\n state = await options.evolve(\n state as Doc,\n event as ReadEvent<EventType, EventMetaDataType>,\n );\n }\n\n const metadata: MongoDBReadModelMetadata = {\n name: projectionName,\n schemaVersion,\n streamPosition: events[events.length - 1]!.metadata.streamPosition,\n };\n\n updates.$set![`projections.${options.name}`] =\n state !== null\n ? {\n ...state,\n _metadata: metadata,\n }\n : null;\n },\n };\n};\n"]}
package/dist/index.d.cts CHANGED
@@ -1,47 +1,59 @@
1
- import { Event, ReadEvent, ReadEventMetadata, EventStore, ReadStreamOptions, ReadStreamResult, AggregateStreamOptions, AggregateStreamResult, AppendToStreamOptions, AppendToStreamResult } from '@event-driven-io/emmett';
2
- import { Collection } from 'mongodb';
1
+ import { Event, EventMetaDataOf, ReadEvent, ReadEventMetadataWithoutGlobalPosition, ProjectionRegistration, EventStore, ReadStreamOptions, ReadStreamResult, AggregateStreamOptions, AggregateStreamResult, AppendToStreamOptions, AppendToStreamResult } from '@event-driven-io/emmett';
2
+ import { UpdateFilter, Collection, Document } from 'mongodb';
3
3
 
4
- declare const MongoDBEventStoreDefaultStreamVersion = 0;
4
+ type MongoDBProjectionInlineHandlerContext<EventType extends Event = Event, EventMetaDataType extends EventMetaDataOf<EventType> & MongoDBReadEventMetadata = EventMetaDataOf<EventType> & MongoDBReadEventMetadata> = {
5
+ document: MongoDBReadModel | null;
6
+ updates: UpdateFilter<EventStream<EventType, EventMetaDataType>>;
7
+ collection: Collection<EventStream<EventType, EventMetaDataType>>;
8
+ };
9
+
10
+ declare const MongoDBEventStoreDefaultStreamVersion = 0n;
5
11
  type StreamType = string;
6
12
  type StreamName<T extends StreamType = StreamType> = `${T}:${string}`;
7
13
  type StreamNameParts<T extends StreamType = StreamType> = {
8
14
  streamType: T;
9
15
  streamId: string;
10
16
  };
11
- type StreamToProject<EventType extends Event> = {
12
- streamName: StreamName;
13
- streamType: StreamType;
14
- streamId: string;
15
- streamVersion: number;
16
- events: ReadEvent<EventType, ReadEventMetadata>[];
17
+ type MongoDBReadModelMetadata = {
18
+ name: string;
19
+ schemaVersion: number;
20
+ streamPosition: bigint;
17
21
  };
18
- type Projection<EventType extends Event> = (stream: StreamToProject<EventType>) => void | Promise<void>;
19
- interface EventStream<EventType extends Event = Event> {
22
+ type MongoDBReadModel<Doc extends Document = Document> = Doc & {
23
+ _metadata: MongoDBReadModelMetadata;
24
+ };
25
+ interface EventStream<EventType extends Event = Event, EventMetaDataType extends EventMetaDataOf<EventType> & MongoDBReadEventMetadata = EventMetaDataOf<EventType> & MongoDBReadEventMetadata> {
20
26
  streamName: string;
21
- events: Array<ReadEvent<EventType, ReadEventMetadata>>;
22
- createdAt: Date;
23
- updatedAt: Date;
27
+ events: Array<ReadEvent<EventType, EventMetaDataType>>;
28
+ metadata: {
29
+ streamId: string;
30
+ streamType: StreamType;
31
+ streamPosition: bigint;
32
+ createdAt: Date;
33
+ updatedAt: Date;
34
+ };
35
+ projections: Record<string, MongoDBReadModel>;
24
36
  }
25
- type EventStreamEvent<EventType extends Event = Event> = EventStream<EventType>['events'][number];
26
37
  interface MongoDBConnectionOptions {
27
38
  connectionString: string;
28
39
  database: string;
29
40
  collection?: string;
30
41
  }
31
- declare class MongoDBEventStore implements EventStore<number> {
42
+ type MongoDBReadEventMetadata = ReadEventMetadataWithoutGlobalPosition<bigint>;
43
+ type MongoDBReadEvent<EventType extends Event = Event> = ReadEvent<EventType, MongoDBReadEventMetadata>;
44
+ type MongoDBEventStoreOptions = {
45
+ collection: Collection<EventStream>;
46
+ projections?: ProjectionRegistration<'inline', MongoDBReadEventMetadata, MongoDBProjectionInlineHandlerContext>[];
47
+ };
48
+ declare class MongoDBEventStore implements EventStore<MongoDBReadEventMetadata> {
32
49
  private readonly collection;
33
- constructor(collection: typeof this.collection);
34
- readStream<EventType extends Event>(streamName: StreamName, options?: ReadStreamOptions<number>): Promise<Exclude<ReadStreamResult<EventType, number>, null>>;
35
- aggregateStream<State, EventType extends Event>(streamName: StreamName, options: AggregateStreamOptions<State, EventType, number>): Promise<AggregateStreamResult<State, number>>;
36
- appendToStream<EventType extends Event>(streamName: StreamName, events: EventType[], options?: AppendToStreamOptions<number> & {
37
- /**
38
- * These will be ran after a the events have been successfully appended to
39
- * the stream. `appendToStream` will return after every projection is completed.
40
- */
41
- projections?: Array<Projection<EventType>>;
42
- }): Promise<AppendToStreamResult<number>>;
50
+ private readonly inlineProjections;
51
+ constructor(options: MongoDBEventStoreOptions);
52
+ readStream<EventType extends Event>(streamName: StreamName, options?: ReadStreamOptions): Promise<Exclude<ReadStreamResult<EventType, MongoDBReadEventMetadata>, null>>;
53
+ aggregateStream<State, EventType extends Event>(streamName: StreamName, options: AggregateStreamOptions<State, EventType, MongoDBReadEventMetadata>): Promise<AggregateStreamResult<State>>;
54
+ appendToStream<EventType extends Event>(streamName: StreamName, events: EventType[], options?: AppendToStreamOptions): Promise<AppendToStreamResult>;
43
55
  }
44
- declare const getMongoDBEventStore: (collection: Collection<EventStream>) => MongoDBEventStore;
56
+ declare const getMongoDBEventStore: (options: ConstructorParameters<typeof MongoDBEventStore>[0]) => MongoDBEventStore;
45
57
  /**
46
58
  * Accepts a `streamType` (the type/category of the event stream) and an `streamId`
47
59
  * (the individual entity/object or aggregate ID) and combines them to a singular
@@ -54,4 +66,4 @@ declare function toStreamName<T extends StreamType>(streamType: T, streamId: str
54
66
  */
55
67
  declare function fromStreamName<T extends StreamType>(streamName: StreamName<T>): StreamNameParts<T>;
56
68
 
57
- export { type EventStream, type EventStreamEvent, type MongoDBConnectionOptions, MongoDBEventStore, MongoDBEventStoreDefaultStreamVersion, type Projection, type StreamName, type StreamNameParts, type StreamToProject, type StreamType, fromStreamName, getMongoDBEventStore, toStreamName };
69
+ export { type EventStream, type MongoDBConnectionOptions, MongoDBEventStore, MongoDBEventStoreDefaultStreamVersion, type MongoDBEventStoreOptions, type MongoDBReadEvent, type MongoDBReadEventMetadata, type MongoDBReadModel, type MongoDBReadModelMetadata, type StreamName, type StreamNameParts, type StreamType, fromStreamName, getMongoDBEventStore, toStreamName };