@event-driven-io/emmett-mongodb 0.43.0-beta.2 → 0.43.0-beta.21

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.js CHANGED
@@ -1,1121 +1,521 @@
1
- // ../emmett/dist/chunk-AZDDB5SF.js
2
- var isNumber = (val) => typeof val === "number" && val === val;
3
- var isString = (val) => typeof val === "string";
4
- var isErrorConstructor = (expect) => {
5
- return typeof expect === "function" && expect.prototype && // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
6
- expect.prototype.constructor === expect;
7
- };
8
- var EmmettError = class _EmmettError extends Error {
9
- static Codes = {
10
- ValidationError: 400,
11
- IllegalStateError: 403,
12
- NotFoundError: 404,
13
- ConcurrencyError: 412,
14
- InternalServerError: 500
15
- };
16
- errorCode;
17
- constructor(options) {
18
- const errorCode = options && typeof options === "object" && "errorCode" in options ? options.errorCode : isNumber(options) ? options : _EmmettError.Codes.InternalServerError;
19
- const message = options && typeof options === "object" && "message" in options ? options.message : isString(options) ? options : `Error with status code '${errorCode}' ocurred during Emmett processing`;
20
- super(message);
21
- this.errorCode = errorCode;
22
- Object.setPrototypeOf(this, _EmmettError.prototype);
23
- }
24
- static mapFrom(error) {
25
- if (_EmmettError.isInstanceOf(error)) {
26
- return error;
27
- }
28
- return new _EmmettError({
29
- errorCode: "errorCode" in error && error.errorCode !== void 0 && error.errorCode !== null ? error.errorCode : _EmmettError.Codes.InternalServerError,
30
- message: error.message ?? "An unknown error occurred"
31
- });
32
- }
33
- static isInstanceOf(error, errorCode) {
34
- return typeof error === "object" && error !== null && "errorCode" in error && isNumber(error.errorCode) && (errorCode === void 0 || error.errorCode === errorCode);
35
- }
36
- };
37
- var ConcurrencyError = class _ConcurrencyError extends EmmettError {
38
- constructor(current, expected, message) {
39
- super({
40
- errorCode: EmmettError.Codes.ConcurrencyError,
41
- message: message ?? `Expected version ${expected.toString()} does not match current ${current?.toString()}`
42
- });
43
- this.current = current;
44
- this.expected = expected;
45
- Object.setPrototypeOf(this, _ConcurrencyError.prototype);
46
- }
47
- };
48
-
49
- // ../emmett/dist/index.js
50
- import { v4 as uuid4 } from "uuid";
51
- import { v7 as uuid } from "uuid";
52
- import retry from "async-retry";
53
- import { v7 as uuid2 } from "uuid";
54
- import { v4 as uuid3 } from "uuid";
55
- import { v7 as uuid5 } from "uuid";
56
- async function tryPublishMessagesAfterCommit(messages, options, context) {
57
- if (options?.onAfterCommit === void 0) return false;
58
- try {
59
- await options?.onAfterCommit(messages, context);
60
- return true;
61
- } catch (error) {
62
- console.error(`Error in on after commit hook`, error);
63
- return false;
64
- }
65
- }
66
- var emmettPrefix = "emt";
67
- var defaultTag = `${emmettPrefix}:default`;
68
- var unknownTag = `${emmettPrefix}:unknown`;
69
- var STREAM_EXISTS = "STREAM_EXISTS";
70
- var STREAM_DOES_NOT_EXIST = "STREAM_DOES_NOT_EXIST";
71
- var NO_CONCURRENCY_CHECK = "NO_CONCURRENCY_CHECK";
72
- var matchesExpectedVersion = (current, expected, defaultVersion) => {
73
- if (expected === NO_CONCURRENCY_CHECK) return true;
74
- if (expected == STREAM_DOES_NOT_EXIST) return current === defaultVersion;
75
- if (expected == STREAM_EXISTS) return current !== defaultVersion;
76
- return current === expected;
77
- };
78
- var assertExpectedVersionMatchesCurrent = (current, expected, defaultVersion) => {
79
- expected ??= NO_CONCURRENCY_CHECK;
80
- if (!matchesExpectedVersion(current, expected, defaultVersion))
81
- throw new ExpectedVersionConflictError(current, expected);
82
- };
83
- var ExpectedVersionConflictError = class _ExpectedVersionConflictError extends ConcurrencyError {
84
- constructor(current, expected) {
85
- super(current?.toString(), expected?.toString());
86
- Object.setPrototypeOf(this, _ExpectedVersionConflictError.prototype);
87
- }
88
- };
89
- var hasDuplicates = (array, predicate) => {
90
- const mapped = array.map(predicate);
91
- const uniqueValues = new Set(mapped);
92
- return uniqueValues.size < mapped.length;
93
- };
94
- var getDuplicates = (array, predicate) => {
95
- const map = /* @__PURE__ */ new Map();
96
- for (let i = 0; i < array.length; i++) {
97
- const item = array[i];
98
- const key = predicate(item, i, array);
99
- if (!map.has(key)) {
100
- map.set(key, []);
101
- }
102
- map.get(key).push(item);
103
- }
104
- return Array.from(map.values()).filter((group) => group.length > 1).flat();
105
- };
106
- var merge = (array, item, where, onExisting, onNotFound = () => void 0) => {
107
- let wasFound = false;
108
- const result = array.map((p) => {
109
- if (!where(p)) return p;
110
- wasFound = true;
111
- return onExisting(p);
112
- }).filter((p) => p !== void 0).map((p) => {
113
- if (!p) throw Error("That should not happen");
114
- return p;
115
- });
116
- if (!wasFound) {
117
- const result2 = onNotFound();
118
- if (result2 !== void 0) return [...array, item];
119
- }
120
- return result;
121
- };
122
- var arrayUtils = {
123
- merge,
124
- hasDuplicates,
125
- getDuplicates
126
- };
127
- var isPrimitive = (value) => {
128
- const type = typeof value;
129
- return value === null || value === void 0 || type === "boolean" || type === "number" || type === "string" || type === "symbol" || type === "bigint";
130
- };
131
- var compareArrays = (left, right) => {
132
- if (left.length !== right.length) {
133
- return false;
134
- }
135
- for (let i = 0; i < left.length; i++) {
136
- const leftHas = i in left;
137
- const rightHas = i in right;
138
- if (leftHas !== rightHas) return false;
139
- if (leftHas && !deepEquals(left[i], right[i])) return false;
140
- }
141
- return true;
142
- };
143
- var compareDates = (left, right) => {
144
- return left.getTime() === right.getTime();
145
- };
146
- var compareRegExps = (left, right) => {
147
- return left.toString() === right.toString();
148
- };
149
- var compareErrors = (left, right) => {
150
- if (left.message !== right.message || left.name !== right.name) {
151
- return false;
152
- }
153
- const leftKeys = Object.keys(left);
154
- const rightKeys = Object.keys(right);
155
- if (leftKeys.length !== rightKeys.length) return false;
156
- const rightKeySet = new Set(rightKeys);
157
- for (const key of leftKeys) {
158
- if (!rightKeySet.has(key)) return false;
159
- if (!deepEquals(left[key], right[key])) return false;
160
- }
161
- return true;
162
- };
163
- var compareMaps = (left, right) => {
164
- if (left.size !== right.size) return false;
165
- for (const [key, value] of left) {
166
- if (isPrimitive(key)) {
167
- if (!right.has(key) || !deepEquals(value, right.get(key))) {
168
- return false;
169
- }
170
- } else {
171
- let found = false;
172
- for (const [rightKey, rightValue] of right) {
173
- if (deepEquals(key, rightKey) && deepEquals(value, rightValue)) {
174
- found = true;
175
- break;
176
- }
177
- }
178
- if (!found) return false;
179
- }
180
- }
181
- return true;
182
- };
183
- var compareSets = (left, right) => {
184
- if (left.size !== right.size) return false;
185
- for (const leftItem of left) {
186
- if (isPrimitive(leftItem)) {
187
- if (!right.has(leftItem)) return false;
188
- } else {
189
- let found = false;
190
- for (const rightItem of right) {
191
- if (deepEquals(leftItem, rightItem)) {
192
- found = true;
193
- break;
194
- }
195
- }
196
- if (!found) return false;
197
- }
198
- }
199
- return true;
200
- };
201
- var compareArrayBuffers = (left, right) => {
202
- if (left.byteLength !== right.byteLength) return false;
203
- const leftView = new Uint8Array(left);
204
- const rightView = new Uint8Array(right);
205
- for (let i = 0; i < leftView.length; i++) {
206
- if (leftView[i] !== rightView[i]) return false;
207
- }
208
- return true;
209
- };
210
- var compareTypedArrays = (left, right) => {
211
- if (left.constructor !== right.constructor) return false;
212
- if (left.byteLength !== right.byteLength) return false;
213
- const leftArray = new Uint8Array(
214
- left.buffer,
215
- left.byteOffset,
216
- left.byteLength
217
- );
218
- const rightArray = new Uint8Array(
219
- right.buffer,
220
- right.byteOffset,
221
- right.byteLength
222
- );
223
- for (let i = 0; i < leftArray.length; i++) {
224
- if (leftArray[i] !== rightArray[i]) return false;
225
- }
226
- return true;
227
- };
228
- var compareObjects = (left, right) => {
229
- const keys1 = Object.keys(left);
230
- const keys2 = Object.keys(right);
231
- if (keys1.length !== keys2.length) {
232
- return false;
233
- }
234
- for (const key of keys1) {
235
- if (left[key] instanceof Function && right[key] instanceof Function) {
236
- continue;
237
- }
238
- const isEqual = deepEquals(left[key], right[key]);
239
- if (!isEqual) {
240
- return false;
241
- }
242
- }
243
- return true;
244
- };
245
- var getType = (value) => {
246
- if (value === null) return "null";
247
- if (value === void 0) return "undefined";
248
- const primitiveType = typeof value;
249
- if (primitiveType !== "object") return primitiveType;
250
- if (Array.isArray(value)) return "array";
251
- if (value instanceof Boolean) return "boxed-boolean";
252
- if (value instanceof Number) return "boxed-number";
253
- if (value instanceof String) return "boxed-string";
254
- if (value instanceof Date) return "date";
255
- if (value instanceof RegExp) return "regexp";
256
- if (value instanceof Error) return "error";
257
- if (value instanceof Map) return "map";
258
- if (value instanceof Set) return "set";
259
- if (value instanceof ArrayBuffer) return "arraybuffer";
260
- if (value instanceof DataView) return "dataview";
261
- if (value instanceof WeakMap) return "weakmap";
262
- if (value instanceof WeakSet) return "weakset";
263
- if (ArrayBuffer.isView(value)) return "typedarray";
264
- return "object";
265
- };
266
- var deepEquals = (left, right) => {
267
- if (left === right) return true;
268
- if (isEquatable(left)) {
269
- return left.equals(right);
270
- }
271
- const leftType = getType(left);
272
- const rightType = getType(right);
273
- if (leftType !== rightType) return false;
274
- switch (leftType) {
275
- case "null":
276
- case "undefined":
277
- case "boolean":
278
- case "number":
279
- case "bigint":
280
- case "string":
281
- case "symbol":
282
- case "function":
283
- return left === right;
284
- case "array":
285
- return compareArrays(left, right);
286
- case "date":
287
- return compareDates(left, right);
288
- case "regexp":
289
- return compareRegExps(left, right);
290
- case "error":
291
- return compareErrors(left, right);
292
- case "map":
293
- return compareMaps(
294
- left,
295
- right
296
- );
297
- case "set":
298
- return compareSets(left, right);
299
- case "arraybuffer":
300
- return compareArrayBuffers(left, right);
301
- case "dataview":
302
- case "weakmap":
303
- case "weakset":
304
- return false;
305
- case "typedarray":
306
- return compareTypedArrays(
307
- left,
308
- right
309
- );
310
- case "boxed-boolean":
311
- return left.valueOf() === right.valueOf();
312
- case "boxed-number":
313
- return left.valueOf() === right.valueOf();
314
- case "boxed-string":
315
- return left.valueOf() === right.valueOf();
316
- case "object":
317
- return compareObjects(
318
- left,
319
- right
320
- );
321
- default:
322
- return false;
323
- }
324
- };
325
- var isEquatable = (left) => {
326
- return left !== null && left !== void 0 && typeof left === "object" && "equals" in left && typeof left["equals"] === "function";
327
- };
328
- var ParseError = class extends Error {
329
- constructor(text) {
330
- super(`Cannot parse! ${text}`);
331
- }
332
- };
333
- var JSONParser = {
334
- stringify: (value, options) => {
335
- return JSON.stringify(
336
- options?.map ? options.map(value) : value,
337
- //TODO: Consider adding support to DateTime and adding specific format to mark that's a bigint
338
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
339
- (_, v) => typeof v === "bigint" ? v.toString() : v
340
- );
341
- },
342
- parse: (text, options) => {
343
- const parsed = JSON.parse(text, options?.reviver);
344
- if (options?.typeCheck && !options?.typeCheck(parsed))
345
- throw new ParseError(text);
346
- return options?.map ? options.map(parsed) : parsed;
347
- }
348
- };
349
- var textEncoder = new TextEncoder();
350
- var AssertionError = class extends Error {
351
- constructor(message2) {
352
- super(message2);
353
- }
354
- };
355
- var isSubset = (superObj, subObj) => {
356
- const sup = superObj;
357
- const sub = subObj;
358
- assertOk(sup);
359
- assertOk(sub);
360
- return Object.keys(sub).every((ele) => {
361
- if (sub[ele] !== null && typeof sub[ele] == "object") {
362
- return isSubset(sup[ele], sub[ele]);
363
- }
364
- return sub[ele] === sup[ele];
365
- });
366
- };
367
- var assertFails = (message2) => {
368
- throw new AssertionError(message2 ?? "That should not ever happened, right?");
369
- };
370
- function assertTrue(condition, message2) {
371
- if (condition !== true)
372
- throw new AssertionError(message2 ?? `Condition is false`);
373
- }
374
- function assertOk(obj, message2) {
375
- if (!obj) throw new AssertionError(message2 ?? `Condition is not truthy`);
376
- }
377
- var downcastRecordedMessage = (recordedMessage, options) => {
378
- if (!options?.downcast)
379
- return recordedMessage;
380
- const downcasted = options.downcast(
381
- recordedMessage
382
- );
383
- return {
384
- ...recordedMessage,
385
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
386
- data: downcasted.data,
387
- ..."metadata" in recordedMessage || "metadata" in downcasted ? {
388
- metadata: {
389
- ..."metadata" in recordedMessage ? recordedMessage.metadata : {},
390
- ..."metadata" in downcasted ? downcasted.metadata : {}
391
- }
392
- } : {}
393
- };
394
- };
395
- var upcastRecordedMessage = (recordedMessage, options) => {
396
- if (!options?.upcast)
397
- return recordedMessage;
398
- const upcasted = options.upcast(
399
- recordedMessage
400
- );
401
- return {
402
- ...recordedMessage,
403
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
404
- data: upcasted.data,
405
- ..."metadata" in recordedMessage || "metadata" in upcasted ? {
406
- metadata: {
407
- ..."metadata" in recordedMessage ? recordedMessage.metadata : {},
408
- ..."metadata" in upcasted ? upcasted.metadata : {}
409
- }
410
- } : {}
411
- };
412
- };
413
- var upcastRecordedMessages = (recordedMessages, options) => {
414
- if (!options?.upcast)
415
- return recordedMessages;
416
- return recordedMessages.map(
417
- (recordedMessage) => upcastRecordedMessage(recordedMessage, options)
418
- );
419
- };
420
- var filterProjections = (type, projections2) => {
421
- const inlineProjections2 = projections2.filter((projection2) => projection2.type === type).map(({ projection: projection2 }) => projection2);
422
- const duplicateRegistrations = arrayUtils.getDuplicates(
423
- inlineProjections2,
424
- (proj) => proj.name
425
- );
426
- if (duplicateRegistrations.length > 0) {
427
- throw new EmmettError(`You cannot register multiple projections with the same name (or without the name).
428
- Ensure that:
429
- ${JSONParser.stringify(duplicateRegistrations)}
430
- have different names`);
431
- }
432
- return inlineProjections2;
433
- };
434
- var inlineProjections = (definitions) => definitions.map((definition) => ({
435
- type: "inline",
436
- projection: definition
437
- }));
438
- var asyncProjections = (definitions) => definitions.map((definition) => ({
439
- type: "inline",
440
- projection: definition
441
- }));
442
- var projections = {
443
- inline: inlineProjections,
444
- async: asyncProjections
445
- };
446
-
447
- // src/eventStore/mongoDBEventStore.ts
448
- import {
449
- MongoClient as MongoClient2
450
- } from "mongodb";
451
- import { v4 as uuid6 } from "uuid";
1
+ import { AssertionError, ExpectedVersionConflictError, assertExpectedVersionMatchesCurrent, assertFails, assertTrue, deepEquals, downcastRecordedMessage, filterProjections, isErrorConstructor, isSubset, noopScope, projections, tryPublishMessagesAfterCommit, upcastRecordedMessages } from "@event-driven-io/emmett";
2
+ import { MongoClient } from "mongodb";
3
+ import { v4 } from "uuid";
452
4
 
453
- // src/eventStore/projections/mongoDBInlineProjection.ts
454
- var MongoDBDefaultInlineProjectionName = "_default";
455
- var handleInlineProjections = async (options) => {
456
- const {
457
- events,
458
- projections: allProjections,
459
- updates: update,
460
- streamId,
461
- collection,
462
- readModels
463
- } = options;
464
- const eventTypes = events.map((e) => e.type);
465
- const projections2 = allProjections.filter(
466
- (p) => p.canHandle.some((type) => eventTypes.includes(type))
467
- );
468
- for (const projection of projections2) {
469
- await projection.handle(events, {
470
- document: readModels[projection.name] ?? null,
471
- streamId,
472
- collection,
473
- updates: update
474
- });
475
- }
476
- };
477
- var mongoDBInlineProjection = (options) => {
478
- const projectionName = options.name ?? MongoDBDefaultInlineProjectionName;
479
- const schemaVersion = options.schemaVersion ?? 1;
480
- return {
481
- name: projectionName,
482
- canHandle: options.canHandle,
483
- handle: async (events, { document, updates, streamId }) => {
484
- if (events.length === 0) return;
485
- let state = "initialState" in options ? document ?? options.initialState() : document;
486
- for (const event of events) {
487
- state = await options.evolve(
488
- state,
489
- event
490
- );
491
- }
492
- const metadata = {
493
- streamId,
494
- name: projectionName,
495
- schemaVersion,
496
- streamPosition: events[events.length - 1].metadata.streamPosition
497
- };
498
- updates.$set[`projections.${projectionName}`] = state !== null ? {
499
- ...state,
500
- _metadata: metadata
501
- } : null;
502
- }
503
- };
5
+ //#region src/eventStore/projections/mongoDBInlineProjection.ts
6
+ const MongoDBDefaultInlineProjectionName = "_default";
7
+ const handleInlineProjections = async (options) => {
8
+ const { events, projections: allProjections, updates: update, streamId, collection, readModels } = options;
9
+ const eventTypes = events.map((e) => e.type);
10
+ const projections = allProjections.filter((p) => p.canHandle.some((type) => eventTypes.includes(type)));
11
+ for (const projection of projections) await projection.handle(events, {
12
+ document: readModels[projection.name] ?? null,
13
+ streamId,
14
+ collection,
15
+ updates: update,
16
+ observabilityScope: noopScope
17
+ });
18
+ };
19
+ const mongoDBInlineProjection = (options) => {
20
+ const projectionName = options.name ?? "_default";
21
+ const schemaVersion = options.schemaVersion ?? 1;
22
+ return {
23
+ name: projectionName,
24
+ canHandle: options.canHandle,
25
+ handle: async (events, { document, updates, streamId }) => {
26
+ if (events.length === 0) return;
27
+ let state = "initialState" in options ? document ?? options.initialState() : document;
28
+ for (const event of events) state = await options.evolve(state, event);
29
+ const metadata = {
30
+ streamId,
31
+ name: projectionName,
32
+ schemaVersion,
33
+ streamPosition: events[events.length - 1].metadata.streamPosition
34
+ };
35
+ updates.$set[`projections.${projectionName}`] = state !== null ? {
36
+ ...state,
37
+ _metadata: metadata
38
+ } : null;
39
+ }
40
+ };
504
41
  };
505
42
 
506
- // src/eventStore/projections/mongoDBInlineProjectionSpec.ts
507
- import { MongoClient } from "mongodb";
508
- var MongoDBInlineProjectionSpec = {
509
- for: (options) => {
510
- {
511
- const { projection, ...connectionOptions } = options;
512
- return (givenStream) => {
513
- const { streamName, events: givenEvents } = givenStream;
514
- return {
515
- when: (events) => {
516
- const allEvents = [...givenEvents, ...events];
517
- const run = (eventStore) => eventStore.appendToStream(streamName, allEvents);
518
- return {
519
- then: async (assert, message) => {
520
- const client = "client" in connectionOptions && connectionOptions.client ? connectionOptions.client : new MongoClient(
521
- connectionOptions.connectionString,
522
- connectionOptions.clientOptions
523
- );
524
- const eventStore = getMongoDBEventStore({
525
- projections: projections.inline([projection]),
526
- client
527
- });
528
- try {
529
- await run(eventStore);
530
- const succeeded = await assert({ eventStore, streamName });
531
- if (succeeded !== void 0 && succeeded === false)
532
- assertFails(
533
- message ?? "Projection specification didn't match the criteria"
534
- );
535
- } finally {
536
- await client.close();
537
- }
538
- },
539
- thenThrows: async (...args) => {
540
- const client = "client" in connectionOptions && connectionOptions.client ? connectionOptions.client : new MongoClient(
541
- connectionOptions.connectionString,
542
- connectionOptions.clientOptions
543
- );
544
- const eventStore = getMongoDBEventStore({
545
- projections: projections.inline([projection]),
546
- client
547
- });
548
- try {
549
- await run(eventStore);
550
- throw new AssertionError("Handler did not fail as expected");
551
- } catch (error) {
552
- if (error instanceof AssertionError) throw error;
553
- if (args.length === 0) return;
554
- if (!isErrorConstructor(args[0])) {
555
- assertTrue(
556
- args[0](error),
557
- `Error didn't match the error condition: ${error?.toString()}`
558
- );
559
- return;
560
- }
561
- assertTrue(
562
- error instanceof args[0],
563
- `Caught error is not an instance of the expected type: ${error?.toString()}`
564
- );
565
- if (args[1]) {
566
- assertTrue(
567
- args[1](error),
568
- `Error didn't match the error condition: ${error?.toString()}`
569
- );
570
- }
571
- } finally {
572
- await client.close();
573
- }
574
- }
575
- };
576
- }
577
- };
578
- };
579
- }
580
- }
581
- };
582
- var eventInStream = (streamName, event) => ({
583
- streamName,
584
- events: [event]
43
+ //#endregion
44
+ //#region src/eventStore/projections/mongoDBInlineProjectionSpec.ts
45
+ const MongoDBInlineProjectionSpec = { for: (options) => {
46
+ {
47
+ const { projection, ...connectionOptions } = options;
48
+ return (givenStream) => {
49
+ const { streamName, events: givenEvents } = givenStream;
50
+ return { when: (events) => {
51
+ const allEvents = [...givenEvents, ...events];
52
+ const run = (eventStore) => eventStore.appendToStream(streamName, allEvents);
53
+ return {
54
+ then: async (assert, message) => {
55
+ const client = "client" in connectionOptions && connectionOptions.client ? connectionOptions.client : new MongoClient(connectionOptions.connectionString, connectionOptions.clientOptions);
56
+ const eventStore = getMongoDBEventStore({
57
+ projections: projections.inline([projection]),
58
+ client
59
+ });
60
+ try {
61
+ await run(eventStore);
62
+ const succeeded = await assert({
63
+ eventStore,
64
+ streamName
65
+ });
66
+ if (succeeded !== void 0 && succeeded === false) assertFails(message ?? "Projection specification didn't match the criteria");
67
+ } finally {
68
+ await client.close();
69
+ }
70
+ },
71
+ thenThrows: async (...args) => {
72
+ const client = "client" in connectionOptions && connectionOptions.client ? connectionOptions.client : new MongoClient(connectionOptions.connectionString, connectionOptions.clientOptions);
73
+ const eventStore = getMongoDBEventStore({
74
+ projections: projections.inline([projection]),
75
+ client
76
+ });
77
+ try {
78
+ await run(eventStore);
79
+ throw new AssertionError("Handler did not fail as expected");
80
+ } catch (error) {
81
+ if (error instanceof AssertionError) throw error;
82
+ if (args.length === 0) return;
83
+ if (!isErrorConstructor(args[0])) {
84
+ assertTrue(args[0](error), `Error didn't match the error condition: ${error?.toString()}`);
85
+ return;
86
+ }
87
+ assertTrue(error instanceof args[0], `Caught error is not an instance of the expected type: ${error?.toString()}`);
88
+ if (args[1]) assertTrue(args[1](error), `Error didn't match the error condition: ${error?.toString()}`);
89
+ } finally {
90
+ await client.close();
91
+ }
92
+ }
93
+ };
94
+ } };
95
+ };
96
+ }
97
+ } };
98
+ const eventInStream = (streamName, event) => ({
99
+ streamName,
100
+ events: [event]
585
101
  });
586
- var eventsInStream = (streamName, events) => ({
587
- streamName,
588
- events
102
+ const eventsInStream = (streamName, events) => ({
103
+ streamName,
104
+ events
589
105
  });
590
- var expectReadModelToMatch = async (options) => {
591
- const { streamName, projectionName, eventStore, match } = options;
592
- const readModel = await eventStore.projections.inline.findOne({
593
- streamName,
594
- projectionName
595
- });
596
- return match(readModel);
597
- };
598
- var expectInlineReadModelWithName = (projectionName) => ({
599
- toHave: (expected) => ({ eventStore, streamName }) => expectReadModelToMatch({
600
- eventStore,
601
- streamName,
602
- projectionName,
603
- match: (readModel) => isSubset(readModel, expected)
604
- }),
605
- toDeepEquals: (expected) => ({ eventStore, streamName }) => expectReadModelToMatch({
606
- eventStore,
607
- streamName,
608
- projectionName,
609
- match: (readModel) => deepEquals(readModel, expected)
610
- }),
611
- toMatch: (match) => ({ eventStore, streamName }) => expectReadModelToMatch({
612
- eventStore,
613
- streamName,
614
- projectionName,
615
- match
616
- }),
617
- notToExist: () => ({ eventStore, streamName }) => expectReadModelToMatch({
618
- eventStore,
619
- streamName,
620
- projectionName,
621
- match: (readModel) => readModel === null
622
- }),
623
- toExist: () => ({ eventStore, streamName }) => expectReadModelToMatch({
624
- eventStore,
625
- streamName,
626
- projectionName,
627
- match: (readModel) => readModel !== null
628
- })
106
+ const expectReadModelToMatch = async (options) => {
107
+ const { streamName, projectionName, eventStore, match } = options;
108
+ return match(await eventStore.projections.inline.findOne({
109
+ streamName,
110
+ projectionName
111
+ }));
112
+ };
113
+ const expectInlineReadModelWithName = (projectionName) => ({
114
+ toHave: (expected) => ({ eventStore, streamName }) => expectReadModelToMatch({
115
+ eventStore,
116
+ streamName,
117
+ projectionName,
118
+ match: (readModel) => isSubset(readModel, expected)
119
+ }),
120
+ toDeepEquals: (expected) => ({ eventStore, streamName }) => expectReadModelToMatch({
121
+ eventStore,
122
+ streamName,
123
+ projectionName,
124
+ match: (readModel) => deepEquals(readModel, expected)
125
+ }),
126
+ toMatch: (match) => ({ eventStore, streamName }) => expectReadModelToMatch({
127
+ eventStore,
128
+ streamName,
129
+ projectionName,
130
+ match
131
+ }),
132
+ notToExist: () => ({ eventStore, streamName }) => expectReadModelToMatch({
133
+ eventStore,
134
+ streamName,
135
+ projectionName,
136
+ match: (readModel) => readModel === null
137
+ }),
138
+ toExist: () => ({ eventStore, streamName }) => expectReadModelToMatch({
139
+ eventStore,
140
+ streamName,
141
+ projectionName,
142
+ match: (readModel) => readModel !== null
143
+ })
629
144
  });
630
- var expectInlineReadModel = {
631
- withName: (name) => expectInlineReadModelWithName(name),
632
- ...expectInlineReadModelWithName(MongoDBDefaultInlineProjectionName)
145
+ const expectInlineReadModel = {
146
+ withName: (name) => expectInlineReadModelWithName(name),
147
+ ...expectInlineReadModelWithName(MongoDBDefaultInlineProjectionName)
633
148
  };
634
149
 
635
- // src/eventStore/storage/mongoDBEventStoreStorage.ts
636
- var DefaultMongoDBEventStoreStorageOptions = "COLLECTION_PER_STREAM_TYPE";
637
- var DefaultMongoDBEventStoreCollectionName = "emt:streams";
638
- var resolveCollectionAndDatabase = (streamType, options) => {
639
- if (options === "SINGLE_COLLECTION" || typeof options === "object" && options.type === "SINGLE_COLLECTION") {
640
- return {
641
- collectionName: typeof options === "object" ? options.collectionName ?? DefaultMongoDBEventStoreCollectionName : DefaultMongoDBEventStoreCollectionName,
642
- databaseName: typeof options === "object" ? options.databaseName : void 0
643
- };
644
- } else if (options === "COLLECTION_PER_STREAM_TYPE" || typeof options === "object" && options.type === "COLLECTION_PER_STREAM_TYPE") {
645
- return {
646
- collectionName: toStreamCollectionName(streamType),
647
- databaseName: typeof options === "object" ? options.databaseName : void 0
648
- };
649
- } else {
650
- const result = options.collectionFor(streamType);
651
- return {
652
- collectionName: typeof result === "object" ? result.collectionName : result,
653
- databaseName: typeof result === "object" ? result.databaseName ?? options.databaseName : options.databaseName
654
- };
655
- }
656
- };
657
- var getDB = async (options) => {
658
- const { dbsCache, databaseName, getConnectedClient } = options;
659
- const safeDbName = databaseName ?? "___default";
660
- let db = dbsCache.get(safeDbName);
661
- if (!db) {
662
- const connectedClient = await getConnectedClient();
663
- db = connectedClient.db(databaseName);
664
- dbsCache.set(safeDbName, db);
665
- }
666
- return db;
667
- };
668
- var collectionFor = async (options) => {
669
- const { collectionName, db, streamCollections } = options;
670
- let collection = streamCollections.get(collectionName);
671
- if (!collection) {
672
- collection = db.collection(collectionName);
673
- await collection.createIndex({ streamName: 1 }, { unique: true });
674
- streamCollections.set(
675
- collectionName,
676
- collection
677
- );
678
- }
679
- return collection;
680
- };
681
- var mongoDBEventStoreStorage = (options) => {
682
- const dbsCache = /* @__PURE__ */ new Map();
683
- const streamCollections = /* @__PURE__ */ new Map();
684
- const storageOptions = options.storage ?? DefaultMongoDBEventStoreStorageOptions;
685
- const { getConnectedClient } = options;
686
- return {
687
- collectionFor: async (streamType) => {
688
- const { collectionName, databaseName } = resolveCollectionAndDatabase(
689
- streamType,
690
- storageOptions
691
- );
692
- let collection = streamCollections.get(collectionName);
693
- if (!collection) {
694
- const db = await getDB({ databaseName, dbsCache, getConnectedClient });
695
- collection = await collectionFor({
696
- collectionName,
697
- streamCollections,
698
- db
699
- });
700
- }
701
- return collection;
702
- }
703
- };
150
+ //#endregion
151
+ //#region src/eventStore/storage/mongoDBEventStoreStorage.ts
152
+ const DefaultMongoDBEventStoreStorageOptions = "COLLECTION_PER_STREAM_TYPE";
153
+ const DefaultMongoDBEventStoreCollectionName = "emt:streams";
154
+ const resolveCollectionAndDatabase = (streamType, options) => {
155
+ if (options === "SINGLE_COLLECTION" || typeof options === "object" && options.type === "SINGLE_COLLECTION") return {
156
+ collectionName: typeof options === "object" ? options.collectionName ?? "emt:streams" : DefaultMongoDBEventStoreCollectionName,
157
+ databaseName: typeof options === "object" ? options.databaseName : void 0
158
+ };
159
+ else if (options === "COLLECTION_PER_STREAM_TYPE" || typeof options === "object" && options.type === "COLLECTION_PER_STREAM_TYPE") return {
160
+ collectionName: toStreamCollectionName(streamType),
161
+ databaseName: typeof options === "object" ? options.databaseName : void 0
162
+ };
163
+ else {
164
+ const result = options.collectionFor(streamType);
165
+ return {
166
+ collectionName: typeof result === "object" ? result.collectionName : result,
167
+ databaseName: typeof result === "object" ? result.databaseName ?? options.databaseName : options.databaseName
168
+ };
169
+ }
170
+ };
171
+ const getDB = async (options) => {
172
+ const { dbsCache, databaseName, getConnectedClient } = options;
173
+ const safeDbName = databaseName ?? "___default";
174
+ let db = dbsCache.get(safeDbName);
175
+ if (!db) {
176
+ db = (await getConnectedClient()).db(databaseName);
177
+ dbsCache.set(safeDbName, db);
178
+ }
179
+ return db;
180
+ };
181
+ const collectionFor = async (options) => {
182
+ const { collectionName, db, streamCollections } = options;
183
+ let collection = streamCollections.get(collectionName);
184
+ if (!collection) {
185
+ collection = db.collection(collectionName);
186
+ await collection.createIndex({ streamName: 1 }, { unique: true });
187
+ streamCollections.set(collectionName, collection);
188
+ }
189
+ return collection;
190
+ };
191
+ const mongoDBEventStoreStorage = (options) => {
192
+ const dbsCache = /* @__PURE__ */ new Map();
193
+ const streamCollections = /* @__PURE__ */ new Map();
194
+ const storageOptions = options.storage ?? "COLLECTION_PER_STREAM_TYPE";
195
+ const { getConnectedClient } = options;
196
+ return { collectionFor: async (streamType) => {
197
+ const { collectionName, databaseName } = resolveCollectionAndDatabase(streamType, storageOptions);
198
+ let collection = streamCollections.get(collectionName);
199
+ if (!collection) collection = await collectionFor({
200
+ collectionName,
201
+ streamCollections,
202
+ db: await getDB({
203
+ databaseName,
204
+ dbsCache,
205
+ getConnectedClient
206
+ })
207
+ });
208
+ return collection;
209
+ } };
704
210
  };
705
211
 
706
- // src/eventStore/mongoDBEventStore.ts
707
- var MongoDBEventStoreDefaultStreamVersion = 0n;
212
+ //#endregion
213
+ //#region src/eventStore/mongoDBEventStore.ts
214
+ const MongoDBEventStoreDefaultStreamVersion = 0n;
708
215
  var MongoDBEventStoreImplementation = class {
709
- client;
710
- inlineProjections;
711
- shouldManageClientLifetime;
712
- isClosed = false;
713
- storage;
714
- options;
715
- projections;
716
- constructor(options) {
717
- this.options = options;
718
- this.client = "client" in options && options.client ? options.client : new MongoClient2(options.connectionString, options.clientOptions);
719
- this.shouldManageClientLifetime = !("client" in options);
720
- this.storage = mongoDBEventStoreStorage({
721
- storage: options.storage,
722
- getConnectedClient: () => this.getConnectedClient()
723
- });
724
- this.inlineProjections = filterProjections(
725
- "inline",
726
- options.projections ?? []
727
- );
728
- this.projections = {
729
- inline: {
730
- findOne: this.findOneInlineProjection.bind(this),
731
- find: this.findInlineProjection.bind(this),
732
- count: this.countInlineProjection.bind(this)
733
- }
734
- };
735
- }
736
- async readStream(streamName, options) {
737
- const { streamType } = fromStreamName(streamName);
738
- const expectedStreamVersion = options?.expectedStreamVersion;
739
- const collection = await this.storage.collectionFor(streamType);
740
- const filter = {
741
- streamName: { $eq: streamName }
742
- };
743
- const eventsSliceArr = [];
744
- if (options && "from" in options) {
745
- eventsSliceArr.push(Number(options.from));
746
- } else {
747
- eventsSliceArr.push(0);
748
- }
749
- if (options && "to" in options) {
750
- eventsSliceArr.push(Number(options.to));
751
- }
752
- const eventsSlice = eventsSliceArr.length > 1 ? { $slice: eventsSliceArr } : 1;
753
- const stream = await collection.findOne(filter, {
754
- useBigInt64: true,
755
- projection: {
756
- metadata: 1,
757
- messages: eventsSlice
758
- }
759
- });
760
- if (!stream) {
761
- return {
762
- events: [],
763
- currentStreamVersion: MongoDBEventStoreDefaultStreamVersion,
764
- streamExists: false
765
- };
766
- }
767
- assertExpectedVersionMatchesCurrent(
768
- stream.metadata.streamPosition,
769
- expectedStreamVersion,
770
- MongoDBEventStoreDefaultStreamVersion
771
- );
772
- const events = upcastRecordedMessages(
773
- stream.messages,
774
- options?.schema?.versioning
775
- );
776
- return {
777
- events,
778
- currentStreamVersion: stream.metadata.streamPosition,
779
- streamExists: true
780
- };
781
- }
782
- async aggregateStream(streamName, options) {
783
- const stream = await this.readStream(
784
- streamName,
785
- options?.read
786
- );
787
- const { evolve, initialState } = options;
788
- const state = stream.events.reduce(evolve, initialState());
789
- return {
790
- state,
791
- currentStreamVersion: stream.currentStreamVersion,
792
- streamExists: stream.streamExists
793
- };
794
- }
795
- async appendToStream(streamName, events, options) {
796
- const { streamId, streamType } = fromStreamName(streamName);
797
- const expectedStreamVersion = options?.expectedStreamVersion;
798
- const collection = await this.storage.collectionFor(streamType);
799
- const stream = await collection.findOne(
800
- { streamName: { $eq: streamName } },
801
- {
802
- useBigInt64: true,
803
- projection: {
804
- "metadata.streamPosition": 1,
805
- projections: 1
806
- }
807
- }
808
- );
809
- const currentStreamVersion = stream?.metadata.streamPosition ?? MongoDBEventStoreDefaultStreamVersion;
810
- assertExpectedVersionMatchesCurrent(
811
- currentStreamVersion,
812
- expectedStreamVersion,
813
- MongoDBEventStoreDefaultStreamVersion
814
- );
815
- let streamOffset = currentStreamVersion;
816
- const eventsToAppend = events.map((event) => {
817
- const metadata = {
818
- messageId: uuid6(),
819
- streamName,
820
- streamPosition: ++streamOffset
821
- };
822
- return downcastRecordedMessage(
823
- {
824
- type: event.type,
825
- data: event.data,
826
- metadata: {
827
- ...metadata,
828
- ..."metadata" in event ? event.metadata ?? {} : {}
829
- }
830
- },
831
- options?.schema?.versioning
832
- );
833
- });
834
- const now = /* @__PURE__ */ new Date();
835
- const updates = {
836
- $push: { messages: { $each: eventsToAppend } },
837
- $set: {
838
- "metadata.updatedAt": now,
839
- "metadata.streamPosition": currentStreamVersion + BigInt(events.length)
840
- },
841
- $setOnInsert: {
842
- streamName,
843
- "metadata.streamId": streamId,
844
- "metadata.streamType": streamType,
845
- "metadata.createdAt": now
846
- }
847
- };
848
- if (this.inlineProjections) {
849
- await handleInlineProjections({
850
- readModels: stream?.projections ?? {},
851
- streamId,
852
- events: eventsToAppend,
853
- projections: this.inlineProjections,
854
- collection,
855
- updates,
856
- client: {}
857
- });
858
- }
859
- const updatedStream = await collection.updateOne(
860
- {
861
- streamName: { $eq: streamName },
862
- "metadata.streamPosition": currentStreamVersion
863
- },
864
- updates,
865
- { useBigInt64: true, upsert: true }
866
- );
867
- if (!updatedStream) {
868
- throw new ExpectedVersionConflictError(
869
- currentStreamVersion,
870
- options?.expectedStreamVersion ?? 0n
871
- );
872
- }
873
- await tryPublishMessagesAfterCommit(
874
- eventsToAppend,
875
- this.options.hooks
876
- // {
877
- // TODO: same context as InlineProjectionHandlerContext for mongodb?
878
- // },
879
- );
880
- return {
881
- nextExpectedStreamVersion: currentStreamVersion + BigInt(eventsToAppend.length),
882
- createdNewStream: currentStreamVersion === MongoDBEventStoreDefaultStreamVersion
883
- };
884
- }
885
- async streamExists(streamName) {
886
- const { streamType } = fromStreamName(streamName);
887
- const collection = await this.storage.collectionFor(streamType);
888
- const filter = {
889
- streamName: { $eq: streamName }
890
- };
891
- const count = await collection.countDocuments(filter, {
892
- useBigInt64: true,
893
- limit: 1
894
- });
895
- return Boolean(count > 0);
896
- }
897
- collectionFor = async (streamType) => {
898
- return this.storage.collectionFor(streamType);
899
- };
900
- /**
901
- * Gracefully cleans up managed resources by the MongoDBEventStore.
902
- * It closes MongoDB client created for the provided connection string
903
- * through event store options.
904
- *
905
- * @memberof Closeable
906
- */
907
- close = () => {
908
- if (this.isClosed) return Promise.resolve();
909
- this.isClosed = true;
910
- if (!this.shouldManageClientLifetime) return Promise.resolve();
911
- return this.client.close();
912
- };
913
- async findOneInlineProjection(streamFilter, projectionQuery) {
914
- const { projectionName, streamName, streamType } = parseSingleProjectionQueryStreamFilter(streamFilter);
915
- const collection = await this.storage.collectionFor(streamType);
916
- const query = prependMongoFilterWithProjectionPrefix(projectionQuery, `projections.${projectionName}`);
917
- const filters = [
918
- { [`projections.${projectionName}`]: { $exists: true } }
919
- ];
920
- if (query) {
921
- filters.push(query);
922
- }
923
- if (streamName) {
924
- filters.push({ streamName: { $eq: streamName } });
925
- }
926
- const result = await collection.findOne(
927
- { $and: filters },
928
- {
929
- useBigInt64: true,
930
- projection: { [`projections.${projectionName}`]: 1 }
931
- }
932
- );
933
- return result?.projections?.[projectionName] ?? null;
934
- }
935
- async findInlineProjection(streamFilter, projectionQuery, queryOptions) {
936
- const parsedStreamFilter = parseMultiProjectionQueryStreamFilter(streamFilter);
937
- if (!parsedStreamFilter) return [];
938
- const { projectionName, streamNames, streamType } = parsedStreamFilter;
939
- const collection = await this.storage.collectionFor(streamType);
940
- const prefix = `projections.${projectionName}`;
941
- const projectionFilter = prependMongoFilterWithProjectionPrefix(projectionQuery, prefix);
942
- const filters = [
943
- { [`projections.${projectionName}`]: { $ne: null } }
944
- ];
945
- if (projectionFilter) {
946
- filters.push(projectionFilter);
947
- }
948
- if (streamNames) {
949
- filters.push({ streamName: { $in: streamNames } });
950
- }
951
- let query = collection.find(
952
- { $and: filters },
953
- {
954
- useBigInt64: true,
955
- projection: { [`projections.${projectionName}`]: 1 }
956
- }
957
- );
958
- if (queryOptions?.skip) {
959
- query = query.skip(queryOptions.skip);
960
- }
961
- if (queryOptions?.limit) {
962
- query = query.limit(queryOptions.limit);
963
- }
964
- if (queryOptions?.sort) {
965
- const sort = prependMongoFilterWithProjectionPrefix(
966
- queryOptions.sort,
967
- prefix
968
- );
969
- query = query.sort(sort);
970
- }
971
- const streams = await query.toArray();
972
- return streams.map((s) => s.projections[projectionName]).filter((p) => !!p);
973
- }
974
- async countInlineProjection(streamFilter, projectionQuery) {
975
- const parsedStreamFilter = parseMultiProjectionQueryStreamFilter(streamFilter);
976
- if (!parsedStreamFilter) return 0;
977
- const { projectionName, streamNames, streamType } = parsedStreamFilter;
978
- const collection = await this.storage.collectionFor(streamType);
979
- const prefix = `projections.${projectionName}`;
980
- const projectionFilter = prependMongoFilterWithProjectionPrefix(projectionQuery, prefix);
981
- const filters = [
982
- { [`projections.${projectionName}`]: { $ne: null } }
983
- ];
984
- if (projectionFilter) {
985
- filters.push(projectionFilter);
986
- }
987
- if (streamNames) {
988
- filters.push({ streamName: { $in: streamNames } });
989
- }
990
- const total = await collection.countDocuments({ $and: filters });
991
- return total;
992
- }
993
- getConnectedClient = async () => {
994
- if (!this.isClosed) await this.client.connect();
995
- return this.client;
996
- };
216
+ client;
217
+ inlineProjections;
218
+ shouldManageClientLifetime;
219
+ isClosed = false;
220
+ storage;
221
+ options;
222
+ projections;
223
+ constructor(options) {
224
+ this.options = options;
225
+ this.client = "client" in options && options.client ? options.client : new MongoClient(options.connectionString, options.clientOptions);
226
+ this.shouldManageClientLifetime = !("client" in options);
227
+ this.storage = mongoDBEventStoreStorage({
228
+ storage: options.storage,
229
+ getConnectedClient: () => this.getConnectedClient()
230
+ });
231
+ this.inlineProjections = filterProjections("inline", options.projections ?? []);
232
+ this.projections = { inline: {
233
+ findOne: this.findOneInlineProjection.bind(this),
234
+ find: this.findInlineProjection.bind(this),
235
+ count: this.countInlineProjection.bind(this)
236
+ } };
237
+ }
238
+ async readStream(streamName, options) {
239
+ const { streamType } = fromStreamName(streamName);
240
+ const expectedStreamVersion = options?.expectedStreamVersion;
241
+ const collection = await this.storage.collectionFor(streamType);
242
+ const filter = { streamName: { $eq: streamName } };
243
+ const eventsSliceArr = [];
244
+ if (options && "from" in options) eventsSliceArr.push(Number(options.from));
245
+ else eventsSliceArr.push(0);
246
+ if (options && "to" in options) eventsSliceArr.push(Number(options.to));
247
+ const eventsSlice = eventsSliceArr.length > 1 ? { $slice: eventsSliceArr } : 1;
248
+ const stream = await collection.findOne(filter, {
249
+ useBigInt64: true,
250
+ projection: {
251
+ metadata: 1,
252
+ messages: eventsSlice
253
+ }
254
+ });
255
+ if (!stream) return {
256
+ events: [],
257
+ currentStreamVersion: MongoDBEventStoreDefaultStreamVersion,
258
+ streamExists: false
259
+ };
260
+ assertExpectedVersionMatchesCurrent(stream.metadata.streamPosition, expectedStreamVersion, MongoDBEventStoreDefaultStreamVersion);
261
+ return {
262
+ events: upcastRecordedMessages(stream.messages, options?.schema?.versioning),
263
+ currentStreamVersion: stream.metadata.streamPosition,
264
+ streamExists: true
265
+ };
266
+ }
267
+ async aggregateStream(streamName, options) {
268
+ const stream = await this.readStream(streamName, options?.read);
269
+ const { evolve, initialState } = options;
270
+ return {
271
+ state: stream.events.reduce(evolve, initialState()),
272
+ currentStreamVersion: stream.currentStreamVersion,
273
+ streamExists: stream.streamExists
274
+ };
275
+ }
276
+ async appendToStream(streamName, events, options) {
277
+ const { streamId, streamType } = fromStreamName(streamName);
278
+ const expectedStreamVersion = options?.expectedStreamVersion;
279
+ const collection = await this.storage.collectionFor(streamType);
280
+ const stream = await collection.findOne({ streamName: { $eq: streamName } }, {
281
+ useBigInt64: true,
282
+ projection: {
283
+ "metadata.streamPosition": 1,
284
+ projections: 1
285
+ }
286
+ });
287
+ const currentStreamVersion = stream?.metadata.streamPosition ?? 0n;
288
+ assertExpectedVersionMatchesCurrent(currentStreamVersion, expectedStreamVersion, MongoDBEventStoreDefaultStreamVersion);
289
+ let streamOffset = currentStreamVersion;
290
+ const eventsToAppend = events.map((event) => {
291
+ const metadata = {
292
+ messageId: v4(),
293
+ streamName,
294
+ streamPosition: ++streamOffset
295
+ };
296
+ return downcastRecordedMessage({
297
+ type: event.type,
298
+ data: event.data,
299
+ metadata: {
300
+ ...metadata,
301
+ ..."metadata" in event ? event.metadata ?? {} : {}
302
+ }
303
+ }, options?.schema?.versioning);
304
+ });
305
+ const now = /* @__PURE__ */ new Date();
306
+ const updates = {
307
+ $push: { messages: { $each: eventsToAppend } },
308
+ $set: {
309
+ "metadata.updatedAt": now,
310
+ "metadata.streamPosition": currentStreamVersion + BigInt(events.length)
311
+ },
312
+ $setOnInsert: {
313
+ streamName,
314
+ "metadata.streamId": streamId,
315
+ "metadata.streamType": streamType,
316
+ "metadata.createdAt": now
317
+ }
318
+ };
319
+ if (this.inlineProjections) await handleInlineProjections({
320
+ readModels: stream?.projections ?? {},
321
+ streamId,
322
+ events: eventsToAppend,
323
+ projections: this.inlineProjections,
324
+ collection,
325
+ updates,
326
+ client: {}
327
+ });
328
+ if (!await collection.updateOne({
329
+ streamName: { $eq: streamName },
330
+ "metadata.streamPosition": currentStreamVersion
331
+ }, updates, {
332
+ useBigInt64: true,
333
+ upsert: true
334
+ })) throw new ExpectedVersionConflictError(currentStreamVersion, options?.expectedStreamVersion ?? 0n);
335
+ await tryPublishMessagesAfterCommit(eventsToAppend, this.options.hooks);
336
+ return {
337
+ nextExpectedStreamVersion: currentStreamVersion + BigInt(eventsToAppend.length),
338
+ createdNewStream: currentStreamVersion === MongoDBEventStoreDefaultStreamVersion
339
+ };
340
+ }
341
+ async streamExists(streamName) {
342
+ const { streamType } = fromStreamName(streamName);
343
+ const collection = await this.storage.collectionFor(streamType);
344
+ const filter = { streamName: { $eq: streamName } };
345
+ const count = await collection.countDocuments(filter, {
346
+ useBigInt64: true,
347
+ limit: 1
348
+ });
349
+ return Boolean(count > 0);
350
+ }
351
+ collectionFor = async (streamType) => {
352
+ return this.storage.collectionFor(streamType);
353
+ };
354
+ /**
355
+ * Gracefully cleans up managed resources by the MongoDBEventStore.
356
+ * It closes MongoDB client created for the provided connection string
357
+ * through event store options.
358
+ *
359
+ * @memberof Closeable
360
+ */
361
+ close = () => {
362
+ if (this.isClosed) return Promise.resolve();
363
+ this.isClosed = true;
364
+ if (!this.shouldManageClientLifetime) return Promise.resolve();
365
+ return this.client.close();
366
+ };
367
+ async findOneInlineProjection(streamFilter, projectionQuery) {
368
+ const { projectionName, streamName, streamType } = parseSingleProjectionQueryStreamFilter(streamFilter);
369
+ const collection = await this.storage.collectionFor(streamType);
370
+ const query = prependMongoFilterWithProjectionPrefix(projectionQuery, `projections.${projectionName}`);
371
+ const filters = [{ [`projections.${projectionName}`]: { $exists: true } }];
372
+ if (query) filters.push(query);
373
+ if (streamName) filters.push({ streamName: { $eq: streamName } });
374
+ return (await collection.findOne({ $and: filters }, {
375
+ useBigInt64: true,
376
+ projection: { [`projections.${projectionName}`]: 1 }
377
+ }))?.projections?.[projectionName] ?? null;
378
+ }
379
+ async findInlineProjection(streamFilter, projectionQuery, queryOptions) {
380
+ const parsedStreamFilter = parseMultiProjectionQueryStreamFilter(streamFilter);
381
+ if (!parsedStreamFilter) return [];
382
+ const { projectionName, streamNames, streamType } = parsedStreamFilter;
383
+ const collection = await this.storage.collectionFor(streamType);
384
+ const prefix = `projections.${projectionName}`;
385
+ const projectionFilter = prependMongoFilterWithProjectionPrefix(projectionQuery, prefix);
386
+ const filters = [{ [`projections.${projectionName}`]: { $ne: null } }];
387
+ if (projectionFilter) filters.push(projectionFilter);
388
+ if (streamNames) filters.push({ streamName: { $in: streamNames } });
389
+ let query = collection.find({ $and: filters }, {
390
+ useBigInt64: true,
391
+ projection: { [`projections.${projectionName}`]: 1 }
392
+ });
393
+ if (queryOptions?.skip) query = query.skip(queryOptions.skip);
394
+ if (queryOptions?.limit) query = query.limit(queryOptions.limit);
395
+ if (queryOptions?.sort) {
396
+ const sort = prependMongoFilterWithProjectionPrefix(queryOptions.sort, prefix);
397
+ query = query.sort(sort);
398
+ }
399
+ return (await query.toArray()).map((s) => s.projections[projectionName]).filter((p) => !!p);
400
+ }
401
+ async countInlineProjection(streamFilter, projectionQuery) {
402
+ const parsedStreamFilter = parseMultiProjectionQueryStreamFilter(streamFilter);
403
+ if (!parsedStreamFilter) return 0;
404
+ const { projectionName, streamNames, streamType } = parsedStreamFilter;
405
+ const collection = await this.storage.collectionFor(streamType);
406
+ const projectionFilter = prependMongoFilterWithProjectionPrefix(projectionQuery, `projections.${projectionName}`);
407
+ const filters = [{ [`projections.${projectionName}`]: { $ne: null } }];
408
+ if (projectionFilter) filters.push(projectionFilter);
409
+ if (streamNames) filters.push({ streamName: { $in: streamNames } });
410
+ return await collection.countDocuments({ $and: filters });
411
+ }
412
+ getConnectedClient = async () => {
413
+ if (!this.isClosed) await this.client.connect();
414
+ return this.client;
415
+ };
997
416
  };
998
417
  function parseSingleProjectionQueryStreamFilter(streamFilter) {
999
- const projectionName = streamFilter.projectionName ?? MongoDBDefaultInlineProjectionName;
1000
- if ("streamName" in streamFilter) {
1001
- const { streamType } = fromStreamName(streamFilter.streamName);
1002
- return {
1003
- projectionName,
1004
- streamName: streamFilter.streamName,
1005
- streamType
1006
- };
1007
- }
1008
- if (streamFilter.streamId) {
1009
- const streamName = toStreamName(
1010
- streamFilter.streamType,
1011
- streamFilter.streamId
1012
- );
1013
- return {
1014
- projectionName,
1015
- streamName,
1016
- streamType: streamFilter.streamType
1017
- };
1018
- }
1019
- return {
1020
- projectionName,
1021
- streamType: streamFilter.streamType
1022
- };
418
+ const projectionName = streamFilter.projectionName ?? "_default";
419
+ if ("streamName" in streamFilter) {
420
+ const { streamType } = fromStreamName(streamFilter.streamName);
421
+ return {
422
+ projectionName,
423
+ streamName: streamFilter.streamName,
424
+ streamType
425
+ };
426
+ }
427
+ if (streamFilter.streamId) return {
428
+ projectionName,
429
+ streamName: toStreamName(streamFilter.streamType, streamFilter.streamId),
430
+ streamType: streamFilter.streamType
431
+ };
432
+ return {
433
+ projectionName,
434
+ streamType: streamFilter.streamType
435
+ };
1023
436
  }
1024
437
  function parseMultiProjectionQueryStreamFilter(streamFilter) {
1025
- const projectionName = streamFilter.projectionName ?? MongoDBDefaultInlineProjectionName;
1026
- if ("streamNames" in streamFilter) {
1027
- if (streamFilter.streamNames.length == 0) return null;
1028
- const { streamType } = fromStreamName(streamFilter.streamNames[0]);
1029
- return {
1030
- projectionName,
1031
- streamNames: streamFilter.streamNames,
1032
- streamType
1033
- };
1034
- }
1035
- if (streamFilter.streamIds && streamFilter.streamIds.length > 0) {
1036
- const streamNames = streamFilter.streamIds.map(
1037
- (id) => toStreamName(streamFilter.streamType, id)
1038
- );
1039
- return {
1040
- projectionName,
1041
- streamNames,
1042
- streamType: streamFilter.streamType
1043
- };
1044
- }
1045
- return {
1046
- projectionName,
1047
- streamType: streamFilter.streamType
1048
- };
438
+ const projectionName = streamFilter.projectionName ?? "_default";
439
+ if ("streamNames" in streamFilter) {
440
+ if (streamFilter.streamNames.length == 0) return null;
441
+ const { streamType } = fromStreamName(streamFilter.streamNames[0]);
442
+ return {
443
+ projectionName,
444
+ streamNames: streamFilter.streamNames,
445
+ streamType
446
+ };
447
+ }
448
+ if (streamFilter.streamIds && streamFilter.streamIds.length > 0) return {
449
+ projectionName,
450
+ streamNames: streamFilter.streamIds.map((id) => toStreamName(streamFilter.streamType, id)),
451
+ streamType: streamFilter.streamType
452
+ };
453
+ return {
454
+ projectionName,
455
+ streamType: streamFilter.streamType
456
+ };
1049
457
  }
458
+ /**
459
+ * Prepends `prefix` to all object keys that don't start with a '$'
460
+ */
1050
461
  function prependMongoFilterWithProjectionPrefix(obj, prefix) {
1051
- if (typeof obj !== "object" || obj === null || obj === void 0) {
1052
- return obj;
1053
- }
1054
- if (Array.isArray(obj)) {
1055
- for (let i = 0; i < obj.length; i++) {
1056
- obj[i] = prependMongoFilterWithProjectionPrefix(obj[i], prefix);
1057
- }
1058
- return obj;
1059
- }
1060
- for (const key in obj) {
1061
- const k = addProjectionPrefixToMongoKey(key, prefix);
1062
- if (k !== key) {
1063
- obj[k] = obj[key];
1064
- delete obj[key];
1065
- }
1066
- obj[k] = prependMongoFilterWithProjectionPrefix(obj[k], prefix);
1067
- }
1068
- return obj;
462
+ if (typeof obj !== "object" || obj === null || obj === void 0) return obj;
463
+ if (Array.isArray(obj)) {
464
+ for (let i = 0; i < obj.length; i++) obj[i] = prependMongoFilterWithProjectionPrefix(obj[i], prefix);
465
+ return obj;
466
+ }
467
+ for (const key in obj) {
468
+ const k = addProjectionPrefixToMongoKey(key, prefix);
469
+ if (k !== key) {
470
+ obj[k] = obj[key];
471
+ delete obj[key];
472
+ }
473
+ obj[k] = prependMongoFilterWithProjectionPrefix(obj[k], prefix);
474
+ }
475
+ return obj;
1069
476
  }
1070
477
  function addProjectionPrefixToMongoKey(key, prefix) {
1071
- if (key[0] === "$") {
1072
- return key;
1073
- }
1074
- return `${prefix}${key.length > 0 ? "." : ""}${key}`;
478
+ if (key[0] === "$") return key;
479
+ return `${prefix}${key.length > 0 ? "." : ""}${key}`;
1075
480
  }
1076
481
  function getMongoDBEventStore(options) {
1077
- const impl = new MongoDBEventStoreImplementation(options);
1078
- if ("client" in options && "close" in impl) {
1079
- delete impl.close;
1080
- }
1081
- return impl;
482
+ const impl = new MongoDBEventStoreImplementation(options);
483
+ if ("client" in options && "close" in impl) delete impl.close;
484
+ return impl;
1082
485
  }
486
+ /**
487
+ * Accepts a `streamType` (the type/category of the event stream) and an `streamId`
488
+ * (the individual entity/object or aggregate ID) and combines them to a singular
489
+ * `streamName` which can be used in `EventStore`.
490
+ */
1083
491
  function toStreamName(streamType, streamId) {
1084
- return `${streamType}:${streamId}`;
492
+ return `${streamType}:${streamId}`;
1085
493
  }
494
+ /**
495
+ * Accepts a fully formatted `streamName` and returns the broken down
496
+ * `streamType` and `streamId`.
497
+ */
1086
498
  function fromStreamName(streamName) {
1087
- const parts = streamName.split(":");
1088
- return {
1089
- streamType: parts[0],
1090
- streamId: parts[1]
1091
- };
499
+ const parts = streamName.split(":");
500
+ return {
501
+ streamType: parts[0],
502
+ streamId: parts[1]
503
+ };
1092
504
  }
505
+ /**
506
+ * Accepts a `streamType` (the type/category of the event stream)
507
+ * and combines them to a `collectionName` which can be used in `EventStore`.
508
+ */
1093
509
  function toStreamCollectionName(streamType) {
1094
- return `emt:${streamType}`;
510
+ return `emt:${streamType}`;
1095
511
  }
512
+ /**
513
+ * Accepts a fully formatted `streamCollectionName` and returns the parsed `streamType`.
514
+ */
1096
515
  function fromStreamCollectionName(streamCollectionName) {
1097
- const parts = streamCollectionName.split(":");
1098
- return {
1099
- streamType: parts[1]
1100
- };
516
+ return { streamType: streamCollectionName.split(":")[1] };
1101
517
  }
1102
- export {
1103
- DefaultMongoDBEventStoreCollectionName,
1104
- DefaultMongoDBEventStoreStorageOptions,
1105
- MongoDBDefaultInlineProjectionName,
1106
- MongoDBEventStoreDefaultStreamVersion,
1107
- MongoDBInlineProjectionSpec,
1108
- eventInStream,
1109
- eventsInStream,
1110
- expectInlineReadModel,
1111
- fromStreamCollectionName,
1112
- fromStreamName,
1113
- getMongoDBEventStore,
1114
- handleInlineProjections,
1115
- mongoDBEventStoreStorage,
1116
- mongoDBInlineProjection,
1117
- prependMongoFilterWithProjectionPrefix,
1118
- toStreamCollectionName,
1119
- toStreamName
1120
- };
518
+
519
+ //#endregion
520
+ export { DefaultMongoDBEventStoreCollectionName, DefaultMongoDBEventStoreStorageOptions, MongoDBDefaultInlineProjectionName, MongoDBEventStoreDefaultStreamVersion, MongoDBInlineProjectionSpec, eventInStream, eventsInStream, expectInlineReadModel, fromStreamCollectionName, fromStreamName, getMongoDBEventStore, handleInlineProjections, mongoDBEventStoreStorage, mongoDBInlineProjection, prependMongoFilterWithProjectionPrefix, toStreamCollectionName, toStreamName };
1121
521
  //# sourceMappingURL=index.js.map