@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.d.ts 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 };
package/dist/index.js CHANGED
@@ -262,16 +262,61 @@ var { retry: retry2 } = streamTransformations;
262
262
  // src/eventStore/mongoDBEventStore.ts
263
263
  import "mongodb";
264
264
  import { v4 as uuid4 } from "uuid";
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: 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
  collection;
268
- constructor(collection) {
269
- this.collection = collection;
292
+ inlineProjections;
293
+ constructor(options) {
294
+ this.collection = options.collection;
295
+ this.inlineProjections = (options.projections ?? []).filter(({ type }) => type === "inline").map(
296
+ ({ projection }) => projection
297
+ );
270
298
  }
271
299
  async readStream(streamName, options) {
272
300
  const expectedStreamVersion = options?.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 = stream?.events.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 = options?.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 = stream?.metadata.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: uuid4(),
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: uuid4(),
331
- streamName,
332
- streamPosition: BigInt(currentStreamPosition),
377
+ ...metadata,
333
378
  ...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: stream?.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
- options?.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
+ options?.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
- currentStream?.events.length ?? -1,
360
- stream.events.length
409
+ currentStreamVersion,
410
+ options?.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
- options?.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((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}`;