@event-driven-io/emmett-mongodb 0.43.0-beta.13 → 0.43.0-beta.14

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