@event-driven-io/emmett 0.43.0-beta.12 → 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/README.md +591 -0
- package/dist/cli.cjs +124 -179
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts +12 -9
- package/dist/cli.d.ts +12 -9
- package/dist/cli.js +115 -178
- package/dist/cli.js.map +1 -1
- package/dist/index-BXN5muYb.d.cts +73 -0
- package/dist/index-C0agmFA7.d.ts +73 -0
- package/dist/index.cjs +2309 -2872
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +689 -623
- package/dist/index.d.ts +689 -623
- package/dist/index.js +2090 -2800
- package/dist/index.js.map +1 -1
- package/dist/plugins-CUbnGFPp.js +131 -0
- package/dist/plugins-CUbnGFPp.js.map +1 -0
- package/dist/plugins-DB9xe8AV.cjs +272 -0
- package/dist/plugins-DB9xe8AV.cjs.map +1 -0
- package/package.json +4 -4
- package/dist/chunk-AZDDB5SF.js +0 -161
- package/dist/chunk-AZDDB5SF.js.map +0 -1
- package/dist/chunk-WND32L6P.cjs +0 -161
- package/dist/chunk-WND32L6P.cjs.map +0 -1
- package/dist/index-D13Bsibj.d.cts +0 -69
- package/dist/index-D13Bsibj.d.ts +0 -69
package/dist/index.js
CHANGED
|
@@ -1,2915 +1,2205 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
EmmettError,
|
|
5
|
-
IllegalStateError,
|
|
6
|
-
NotFoundError,
|
|
7
|
-
ValidationError,
|
|
8
|
-
ValidationErrors,
|
|
9
|
-
assertNotEmptyString,
|
|
10
|
-
assertPositiveNumber,
|
|
11
|
-
assertUnsignedBigInt,
|
|
12
|
-
formatDateToUtcYYYYMMDD,
|
|
13
|
-
isBigint,
|
|
14
|
-
isErrorConstructor,
|
|
15
|
-
isNumber,
|
|
16
|
-
isPluginConfig,
|
|
17
|
-
isString,
|
|
18
|
-
isValidYYYYMMDD,
|
|
19
|
-
parseDateFromUtcYYYYMMDD
|
|
20
|
-
} from "./chunk-AZDDB5SF.js";
|
|
1
|
+
import { _ as isValidYYYYMMDD, a as IllegalStateError, c as isErrorConstructor, d as assertPositiveNumber, f as assertUnsignedBigInt, g as formatDateToUtcYYYYMMDD, h as isString, i as EmmettError, l as ValidationErrors, m as isNumber, n as ConcurrencyError, o as NotFoundError, p as isBigint, r as ConcurrencyInMemoryDatabaseError, s as ValidationError, t as isPluginConfig, u as assertNotEmptyString, v as parseDateFromUtcYYYYMMDD } from "./plugins-CUbnGFPp.js";
|
|
2
|
+
import { v4, v7 } from "uuid";
|
|
3
|
+
import retry from "async-retry";
|
|
21
4
|
|
|
22
|
-
|
|
5
|
+
//#region src/eventStore/afterCommit/afterEventStoreCommitHandler.ts
|
|
23
6
|
async function tryPublishMessagesAfterCommit(messages, options, context) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
7
|
+
if (options?.onAfterCommit === void 0) return false;
|
|
8
|
+
try {
|
|
9
|
+
await options?.onAfterCommit(messages, context);
|
|
10
|
+
return true;
|
|
11
|
+
} catch (error) {
|
|
12
|
+
console.error(`Error in on after commit hook`, error);
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
32
15
|
}
|
|
33
16
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
17
|
+
//#endregion
|
|
18
|
+
//#region src/eventStore/afterCommit/forwardToMessageBus.ts
|
|
19
|
+
const forwardToMessageBus = (eventPublisher) => (async (messages) => {
|
|
20
|
+
for (const message of messages) await eventPublisher.publish(message);
|
|
39
21
|
});
|
|
40
22
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/typing/command.ts
|
|
25
|
+
const command = (...args) => {
|
|
26
|
+
const [type, data, metadata] = args;
|
|
27
|
+
return metadata !== void 0 ? {
|
|
28
|
+
type,
|
|
29
|
+
data,
|
|
30
|
+
metadata,
|
|
31
|
+
kind: "Command"
|
|
32
|
+
} : {
|
|
33
|
+
type,
|
|
34
|
+
data,
|
|
35
|
+
kind: "Command"
|
|
36
|
+
};
|
|
45
37
|
};
|
|
46
38
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/typing/event.ts
|
|
41
|
+
const event = (...args) => {
|
|
42
|
+
const [type, data, metadata] = args;
|
|
43
|
+
return metadata !== void 0 ? {
|
|
44
|
+
type,
|
|
45
|
+
data,
|
|
46
|
+
metadata,
|
|
47
|
+
kind: "Event"
|
|
48
|
+
} : {
|
|
49
|
+
type,
|
|
50
|
+
data,
|
|
51
|
+
kind: "Event"
|
|
52
|
+
};
|
|
51
53
|
};
|
|
52
54
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
//#endregion
|
|
56
|
+
//#region src/typing/message.ts
|
|
57
|
+
const message = (...args) => {
|
|
58
|
+
const [kind, type, data, metadata] = args;
|
|
59
|
+
return metadata !== void 0 ? {
|
|
60
|
+
type,
|
|
61
|
+
data,
|
|
62
|
+
metadata,
|
|
63
|
+
kind
|
|
64
|
+
} : {
|
|
65
|
+
type,
|
|
66
|
+
data,
|
|
67
|
+
kind
|
|
68
|
+
};
|
|
57
69
|
};
|
|
58
70
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
71
|
+
//#endregion
|
|
72
|
+
//#region src/typing/index.ts
|
|
73
|
+
const emmettPrefix = "emt";
|
|
74
|
+
const globalTag = "global";
|
|
75
|
+
const defaultTag = `${"emt"}:default`;
|
|
76
|
+
const unknownTag = `${"emt"}:unknown`;
|
|
64
77
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
var isNotInternalEvent = (event2) => !isGlobalStreamCaughtUp(event2);
|
|
78
|
+
//#endregion
|
|
79
|
+
//#region src/eventStore/events/index.ts
|
|
80
|
+
const GlobalStreamCaughtUpType = "__emt:GlobalStreamCaughtUp";
|
|
81
|
+
const isGlobalStreamCaughtUp = (event) => event.type === GlobalStreamCaughtUpType;
|
|
82
|
+
const caughtUpEventFrom = (position) => (event) => event.type === "__emt:GlobalStreamCaughtUp" && event.metadata?.globalPosition >= position;
|
|
83
|
+
const globalStreamCaughtUp = (data) => event(GlobalStreamCaughtUpType, data, { globalPosition: data.globalPosition });
|
|
84
|
+
const isSubscriptionEvent = (event) => isGlobalStreamCaughtUp(event);
|
|
85
|
+
const isNotInternalEvent = (event) => !isGlobalStreamCaughtUp(event);
|
|
74
86
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
});
|
|
87
|
+
//#endregion
|
|
88
|
+
//#region src/eventStore/eventStore.ts
|
|
89
|
+
const canCreateEventStoreSession = (eventStore) => "withSession" in eventStore;
|
|
90
|
+
const nulloSessionFactory = (eventStore) => ({ withSession: (callback) => {
|
|
91
|
+
return callback({
|
|
92
|
+
eventStore,
|
|
93
|
+
close: () => Promise.resolve()
|
|
94
|
+
});
|
|
95
|
+
} });
|
|
86
96
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
};
|
|
102
|
-
var ExpectedVersionConflictError = class
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
error,
|
|
110
|
-
ExpectedVersionConflictError.Codes.ConcurrencyError
|
|
111
|
-
);
|
|
97
|
+
//#endregion
|
|
98
|
+
//#region src/eventStore/expectedVersion.ts
|
|
99
|
+
const STREAM_EXISTS = "STREAM_EXISTS";
|
|
100
|
+
const STREAM_DOES_NOT_EXIST = "STREAM_DOES_NOT_EXIST";
|
|
101
|
+
const NO_CONCURRENCY_CHECK = "NO_CONCURRENCY_CHECK";
|
|
102
|
+
const matchesExpectedVersion = (current, expected, defaultVersion) => {
|
|
103
|
+
if (expected === "NO_CONCURRENCY_CHECK") return true;
|
|
104
|
+
if (expected == "STREAM_DOES_NOT_EXIST") return current === defaultVersion;
|
|
105
|
+
if (expected == "STREAM_EXISTS") return current !== defaultVersion;
|
|
106
|
+
return current === expected;
|
|
107
|
+
};
|
|
108
|
+
const assertExpectedVersionMatchesCurrent = (current, expected, defaultVersion) => {
|
|
109
|
+
expected ??= NO_CONCURRENCY_CHECK;
|
|
110
|
+
if (!matchesExpectedVersion(current, expected, defaultVersion)) throw new ExpectedVersionConflictError(current, expected);
|
|
111
|
+
};
|
|
112
|
+
var ExpectedVersionConflictError = class ExpectedVersionConflictError extends ConcurrencyError {
|
|
113
|
+
constructor(current, expected) {
|
|
114
|
+
super(current?.toString(), expected?.toString());
|
|
115
|
+
Object.setPrototypeOf(this, ExpectedVersionConflictError.prototype);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
const isExpectedVersionConflictError = (error) => error instanceof ExpectedVersionConflictError || EmmettError.isInstanceOf(error, ExpectedVersionConflictError.Codes.ConcurrencyError);
|
|
112
119
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
120
|
+
//#endregion
|
|
121
|
+
//#region src/utils/async/mapAsync.ts
|
|
122
|
+
async function reduceAsync(items, fn, initial) {
|
|
123
|
+
let accumulator = initial;
|
|
124
|
+
for (let i = 0; i < items.length; i++) accumulator = await fn(accumulator, items[i], i);
|
|
125
|
+
return accumulator;
|
|
126
|
+
}
|
|
118
127
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
return Array.from(map.values()).filter((group) => group.length > 1).flat();
|
|
128
|
+
//#endregion
|
|
129
|
+
//#region src/utils/collections/duplicates.ts
|
|
130
|
+
const hasDuplicates = (array, predicate) => {
|
|
131
|
+
const mapped = array.map(predicate);
|
|
132
|
+
return new Set(mapped).size < mapped.length;
|
|
133
|
+
};
|
|
134
|
+
const getDuplicates = (array, predicate) => {
|
|
135
|
+
const map = /* @__PURE__ */ new Map();
|
|
136
|
+
for (let i = 0; i < array.length; i++) {
|
|
137
|
+
const item = array[i];
|
|
138
|
+
const key = predicate(item, i, array);
|
|
139
|
+
if (!map.has(key)) map.set(key, []);
|
|
140
|
+
map.get(key).push(item);
|
|
141
|
+
}
|
|
142
|
+
return Array.from(map.values()).filter((group) => group.length > 1).flat();
|
|
136
143
|
};
|
|
137
144
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
145
|
+
//#endregion
|
|
146
|
+
//#region src/utils/collections/merge.ts
|
|
147
|
+
const merge = (array, item, where, onExisting, onNotFound = () => void 0) => {
|
|
148
|
+
let wasFound = false;
|
|
149
|
+
const result = array.map((p) => {
|
|
150
|
+
if (!where(p)) return p;
|
|
151
|
+
wasFound = true;
|
|
152
|
+
return onExisting(p);
|
|
153
|
+
}).filter((p) => p !== void 0).map((p) => {
|
|
154
|
+
if (!p) throw Error("That should not happen");
|
|
155
|
+
return p;
|
|
156
|
+
});
|
|
157
|
+
if (!wasFound) {
|
|
158
|
+
if (onNotFound() !== void 0) return [...array, item];
|
|
159
|
+
}
|
|
160
|
+
return result;
|
|
154
161
|
};
|
|
155
162
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
163
|
+
//#endregion
|
|
164
|
+
//#region src/utils/collections/index.ts
|
|
165
|
+
const arrayUtils = {
|
|
166
|
+
merge,
|
|
167
|
+
hasDuplicates,
|
|
168
|
+
getDuplicates
|
|
161
169
|
};
|
|
162
170
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
if (left === right) return true;
|
|
305
|
-
if (isEquatable(left)) {
|
|
306
|
-
return left.equals(right);
|
|
307
|
-
}
|
|
308
|
-
const leftType = getType(left);
|
|
309
|
-
const rightType = getType(right);
|
|
310
|
-
if (leftType !== rightType) return false;
|
|
311
|
-
switch (leftType) {
|
|
312
|
-
case "null":
|
|
313
|
-
case "undefined":
|
|
314
|
-
case "boolean":
|
|
315
|
-
case "number":
|
|
316
|
-
case "bigint":
|
|
317
|
-
case "string":
|
|
318
|
-
case "symbol":
|
|
319
|
-
case "function":
|
|
320
|
-
return left === right;
|
|
321
|
-
case "array":
|
|
322
|
-
return compareArrays(left, right);
|
|
323
|
-
case "date":
|
|
324
|
-
return compareDates(left, right);
|
|
325
|
-
case "regexp":
|
|
326
|
-
return compareRegExps(left, right);
|
|
327
|
-
case "error":
|
|
328
|
-
return compareErrors(left, right);
|
|
329
|
-
case "map":
|
|
330
|
-
return compareMaps(
|
|
331
|
-
left,
|
|
332
|
-
right
|
|
333
|
-
);
|
|
334
|
-
case "set":
|
|
335
|
-
return compareSets(left, right);
|
|
336
|
-
case "arraybuffer":
|
|
337
|
-
return compareArrayBuffers(left, right);
|
|
338
|
-
case "dataview":
|
|
339
|
-
case "weakmap":
|
|
340
|
-
case "weakset":
|
|
341
|
-
return false;
|
|
342
|
-
case "typedarray":
|
|
343
|
-
return compareTypedArrays(
|
|
344
|
-
left,
|
|
345
|
-
right
|
|
346
|
-
);
|
|
347
|
-
case "boxed-boolean":
|
|
348
|
-
return left.valueOf() === right.valueOf();
|
|
349
|
-
case "boxed-number":
|
|
350
|
-
return left.valueOf() === right.valueOf();
|
|
351
|
-
case "boxed-string":
|
|
352
|
-
return left.valueOf() === right.valueOf();
|
|
353
|
-
case "object":
|
|
354
|
-
return compareObjects(
|
|
355
|
-
left,
|
|
356
|
-
right
|
|
357
|
-
);
|
|
358
|
-
default:
|
|
359
|
-
return false;
|
|
360
|
-
}
|
|
361
|
-
};
|
|
362
|
-
var isEquatable = (left) => {
|
|
363
|
-
return left !== null && left !== void 0 && typeof left === "object" && "equals" in left && typeof left["equals"] === "function";
|
|
171
|
+
//#endregion
|
|
172
|
+
//#region src/utils/deepEquals.ts
|
|
173
|
+
const isPrimitive = (value) => {
|
|
174
|
+
const type = typeof value;
|
|
175
|
+
return value === null || value === void 0 || type === "boolean" || type === "number" || type === "string" || type === "symbol" || type === "bigint";
|
|
176
|
+
};
|
|
177
|
+
const compareArrays = (left, right) => {
|
|
178
|
+
if (left.length !== right.length) return false;
|
|
179
|
+
for (let i = 0; i < left.length; i++) {
|
|
180
|
+
const leftHas = i in left;
|
|
181
|
+
if (leftHas !== i in right) return false;
|
|
182
|
+
if (leftHas && !deepEquals(left[i], right[i])) return false;
|
|
183
|
+
}
|
|
184
|
+
return true;
|
|
185
|
+
};
|
|
186
|
+
const compareDates = (left, right) => {
|
|
187
|
+
return left.getTime() === right.getTime();
|
|
188
|
+
};
|
|
189
|
+
const compareRegExps = (left, right) => {
|
|
190
|
+
return left.toString() === right.toString();
|
|
191
|
+
};
|
|
192
|
+
const compareErrors = (left, right) => {
|
|
193
|
+
if (left.message !== right.message || left.name !== right.name) return false;
|
|
194
|
+
const leftKeys = Object.keys(left);
|
|
195
|
+
const rightKeys = Object.keys(right);
|
|
196
|
+
if (leftKeys.length !== rightKeys.length) return false;
|
|
197
|
+
const rightKeySet = new Set(rightKeys);
|
|
198
|
+
for (const key of leftKeys) {
|
|
199
|
+
if (!rightKeySet.has(key)) return false;
|
|
200
|
+
if (!deepEquals(left[key], right[key])) return false;
|
|
201
|
+
}
|
|
202
|
+
return true;
|
|
203
|
+
};
|
|
204
|
+
const compareMaps = (left, right) => {
|
|
205
|
+
if (left.size !== right.size) return false;
|
|
206
|
+
for (const [key, value] of left) if (isPrimitive(key)) {
|
|
207
|
+
if (!right.has(key) || !deepEquals(value, right.get(key))) return false;
|
|
208
|
+
} else {
|
|
209
|
+
let found = false;
|
|
210
|
+
for (const [rightKey, rightValue] of right) if (deepEquals(key, rightKey) && deepEquals(value, rightValue)) {
|
|
211
|
+
found = true;
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
if (!found) return false;
|
|
215
|
+
}
|
|
216
|
+
return true;
|
|
217
|
+
};
|
|
218
|
+
const compareSets = (left, right) => {
|
|
219
|
+
if (left.size !== right.size) return false;
|
|
220
|
+
for (const leftItem of left) if (isPrimitive(leftItem)) {
|
|
221
|
+
if (!right.has(leftItem)) return false;
|
|
222
|
+
} else {
|
|
223
|
+
let found = false;
|
|
224
|
+
for (const rightItem of right) if (deepEquals(leftItem, rightItem)) {
|
|
225
|
+
found = true;
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
if (!found) return false;
|
|
229
|
+
}
|
|
230
|
+
return true;
|
|
231
|
+
};
|
|
232
|
+
const compareArrayBuffers = (left, right) => {
|
|
233
|
+
if (left.byteLength !== right.byteLength) return false;
|
|
234
|
+
const leftView = new Uint8Array(left);
|
|
235
|
+
const rightView = new Uint8Array(right);
|
|
236
|
+
for (let i = 0; i < leftView.length; i++) if (leftView[i] !== rightView[i]) return false;
|
|
237
|
+
return true;
|
|
238
|
+
};
|
|
239
|
+
const compareTypedArrays = (left, right) => {
|
|
240
|
+
if (left.constructor !== right.constructor) return false;
|
|
241
|
+
if (left.byteLength !== right.byteLength) return false;
|
|
242
|
+
const leftArray = new Uint8Array(left.buffer, left.byteOffset, left.byteLength);
|
|
243
|
+
const rightArray = new Uint8Array(right.buffer, right.byteOffset, right.byteLength);
|
|
244
|
+
for (let i = 0; i < leftArray.length; i++) if (leftArray[i] !== rightArray[i]) return false;
|
|
245
|
+
return true;
|
|
246
|
+
};
|
|
247
|
+
const compareObjects = (left, right) => {
|
|
248
|
+
const keys1 = Object.keys(left);
|
|
249
|
+
const keys2 = Object.keys(right);
|
|
250
|
+
if (keys1.length !== keys2.length) return false;
|
|
251
|
+
for (const key of keys1) {
|
|
252
|
+
if (left[key] instanceof Function && right[key] instanceof Function) continue;
|
|
253
|
+
if (!deepEquals(left[key], right[key])) return false;
|
|
254
|
+
}
|
|
255
|
+
return true;
|
|
256
|
+
};
|
|
257
|
+
const getType = (value) => {
|
|
258
|
+
if (value === null) return "null";
|
|
259
|
+
if (value === void 0) return "undefined";
|
|
260
|
+
const primitiveType = typeof value;
|
|
261
|
+
if (primitiveType !== "object") return primitiveType;
|
|
262
|
+
if (Array.isArray(value)) return "array";
|
|
263
|
+
if (value instanceof Boolean) return "boxed-boolean";
|
|
264
|
+
if (value instanceof Number) return "boxed-number";
|
|
265
|
+
if (value instanceof String) return "boxed-string";
|
|
266
|
+
if (value instanceof Date) return "date";
|
|
267
|
+
if (value instanceof RegExp) return "regexp";
|
|
268
|
+
if (value instanceof Error) return "error";
|
|
269
|
+
if (value instanceof Map) return "map";
|
|
270
|
+
if (value instanceof Set) return "set";
|
|
271
|
+
if (value instanceof ArrayBuffer) return "arraybuffer";
|
|
272
|
+
if (value instanceof DataView) return "dataview";
|
|
273
|
+
if (value instanceof WeakMap) return "weakmap";
|
|
274
|
+
if (value instanceof WeakSet) return "weakset";
|
|
275
|
+
if (ArrayBuffer.isView(value)) return "typedarray";
|
|
276
|
+
return "object";
|
|
277
|
+
};
|
|
278
|
+
const deepEquals = (left, right) => {
|
|
279
|
+
if (left === right) return true;
|
|
280
|
+
if (isEquatable(left)) return left.equals(right);
|
|
281
|
+
const leftType = getType(left);
|
|
282
|
+
if (leftType !== getType(right)) return false;
|
|
283
|
+
switch (leftType) {
|
|
284
|
+
case "null":
|
|
285
|
+
case "undefined":
|
|
286
|
+
case "boolean":
|
|
287
|
+
case "number":
|
|
288
|
+
case "bigint":
|
|
289
|
+
case "string":
|
|
290
|
+
case "symbol":
|
|
291
|
+
case "function": return left === right;
|
|
292
|
+
case "array": return compareArrays(left, right);
|
|
293
|
+
case "date": return compareDates(left, right);
|
|
294
|
+
case "regexp": return compareRegExps(left, right);
|
|
295
|
+
case "error": return compareErrors(left, right);
|
|
296
|
+
case "map": return compareMaps(left, right);
|
|
297
|
+
case "set": return compareSets(left, right);
|
|
298
|
+
case "arraybuffer": return compareArrayBuffers(left, right);
|
|
299
|
+
case "dataview":
|
|
300
|
+
case "weakmap":
|
|
301
|
+
case "weakset": return false;
|
|
302
|
+
case "typedarray": return compareTypedArrays(left, right);
|
|
303
|
+
case "boxed-boolean": return left.valueOf() === right.valueOf();
|
|
304
|
+
case "boxed-number": return left.valueOf() === right.valueOf();
|
|
305
|
+
case "boxed-string": return left.valueOf() === right.valueOf();
|
|
306
|
+
case "object": return compareObjects(left, right);
|
|
307
|
+
default: return false;
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
const isEquatable = (left) => {
|
|
311
|
+
return left !== null && left !== void 0 && typeof left === "object" && "equals" in left && typeof left["equals"] === "function";
|
|
364
312
|
};
|
|
365
313
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
314
|
+
//#endregion
|
|
315
|
+
//#region src/utils/iterators.ts
|
|
316
|
+
const sum = (iterator) => {
|
|
317
|
+
let value, done, sum = 0;
|
|
318
|
+
do {
|
|
319
|
+
({value, done} = iterator.next());
|
|
320
|
+
sum += value || 0;
|
|
321
|
+
} while (!done);
|
|
322
|
+
return sum;
|
|
374
323
|
};
|
|
375
324
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
// src/taskProcessing/taskProcessor.ts
|
|
325
|
+
//#endregion
|
|
326
|
+
//#region src/taskProcessing/taskProcessor.ts
|
|
380
327
|
var TaskProcessor = class {
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
hasItemsToProcess = () => this.queue.findIndex(
|
|
484
|
-
(item) => !item.options?.taskGroupId || !this.activeGroups.has(item.options.taskGroupId)
|
|
485
|
-
) !== -1;
|
|
486
|
-
};
|
|
487
|
-
var DEFAULT_PROMISE_DEADLINE = 2147483647;
|
|
488
|
-
var promiseWithDeadline = (executor, options) => {
|
|
489
|
-
return new Promise((resolve, reject) => {
|
|
490
|
-
let taskStarted = false;
|
|
491
|
-
let timeoutId = null;
|
|
492
|
-
const deadline = options.deadline ?? DEFAULT_PROMISE_DEADLINE;
|
|
493
|
-
timeoutId = setTimeout(() => {
|
|
494
|
-
if (!taskStarted) {
|
|
495
|
-
reject(
|
|
496
|
-
new Error("Task was not started within the maximum waiting time")
|
|
497
|
-
);
|
|
498
|
-
}
|
|
499
|
-
}, deadline);
|
|
500
|
-
timeoutId.unref();
|
|
501
|
-
executor(
|
|
502
|
-
(value) => {
|
|
503
|
-
taskStarted = true;
|
|
504
|
-
if (timeoutId) {
|
|
505
|
-
clearTimeout(timeoutId);
|
|
506
|
-
}
|
|
507
|
-
timeoutId = null;
|
|
508
|
-
resolve(value);
|
|
509
|
-
},
|
|
510
|
-
(reason) => {
|
|
511
|
-
if (timeoutId) {
|
|
512
|
-
clearTimeout(timeoutId);
|
|
513
|
-
}
|
|
514
|
-
timeoutId = null;
|
|
515
|
-
reject(reason);
|
|
516
|
-
}
|
|
517
|
-
);
|
|
518
|
-
});
|
|
328
|
+
queue = [];
|
|
329
|
+
isProcessing = false;
|
|
330
|
+
activeTasks = 0;
|
|
331
|
+
activeGroups = /* @__PURE__ */ new Set();
|
|
332
|
+
options;
|
|
333
|
+
stopped = false;
|
|
334
|
+
constructor(options) {
|
|
335
|
+
this.options = options;
|
|
336
|
+
}
|
|
337
|
+
enqueue(task, options) {
|
|
338
|
+
if (this.stopped) return Promise.reject(new EmmettError("TaskProcessor has been stopped"));
|
|
339
|
+
if (this.queue.length >= this.options.maxQueueSize) return Promise.reject(new EmmettError("Too many pending tasks. Please try again later."));
|
|
340
|
+
return this.schedule(task, options);
|
|
341
|
+
}
|
|
342
|
+
waitForEndOfProcessing() {
|
|
343
|
+
return this.schedule(({ ack }) => Promise.resolve(ack()));
|
|
344
|
+
}
|
|
345
|
+
async stop(options) {
|
|
346
|
+
if (this.stopped) return;
|
|
347
|
+
this.stopped = true;
|
|
348
|
+
this.queue.length = 0;
|
|
349
|
+
this.activeGroups.clear();
|
|
350
|
+
if (!options?.force) await this.waitForEndOfProcessing();
|
|
351
|
+
}
|
|
352
|
+
schedule(task, options) {
|
|
353
|
+
return promiseWithDeadline((resolve, reject) => {
|
|
354
|
+
const taskWithContext = () => {
|
|
355
|
+
return new Promise((resolveTask, failTask) => {
|
|
356
|
+
task({ ack: resolveTask }).then(resolve).catch((err) => {
|
|
357
|
+
failTask(err);
|
|
358
|
+
reject(err);
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
};
|
|
362
|
+
this.queue.push({
|
|
363
|
+
task: taskWithContext,
|
|
364
|
+
options
|
|
365
|
+
});
|
|
366
|
+
if (!this.isProcessing) this.ensureProcessing();
|
|
367
|
+
}, { deadline: this.options.maxTaskIdleTime });
|
|
368
|
+
}
|
|
369
|
+
ensureProcessing() {
|
|
370
|
+
if (this.isProcessing) return;
|
|
371
|
+
this.isProcessing = true;
|
|
372
|
+
this.processQueue();
|
|
373
|
+
}
|
|
374
|
+
processQueue() {
|
|
375
|
+
try {
|
|
376
|
+
while (this.activeTasks < this.options.maxActiveTasks && this.queue.length > 0) {
|
|
377
|
+
const item = this.takeFirstAvailableItem();
|
|
378
|
+
if (item === null) return;
|
|
379
|
+
const groupId = item.options?.taskGroupId;
|
|
380
|
+
if (groupId) this.activeGroups.add(groupId);
|
|
381
|
+
this.activeTasks++;
|
|
382
|
+
this.executeItem(item);
|
|
383
|
+
}
|
|
384
|
+
} catch (error) {
|
|
385
|
+
console.error(error);
|
|
386
|
+
throw error;
|
|
387
|
+
} finally {
|
|
388
|
+
this.isProcessing = false;
|
|
389
|
+
if (this.hasItemsToProcess() && this.activeTasks < this.options.maxActiveTasks) this.ensureProcessing();
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
async executeItem({ task, options }) {
|
|
393
|
+
try {
|
|
394
|
+
await task();
|
|
395
|
+
} finally {
|
|
396
|
+
this.activeTasks--;
|
|
397
|
+
if (options && options.taskGroupId) this.activeGroups.delete(options.taskGroupId);
|
|
398
|
+
this.ensureProcessing();
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
takeFirstAvailableItem = () => {
|
|
402
|
+
const taskIndex = this.queue.findIndex((item) => !item.options?.taskGroupId || !this.activeGroups.has(item.options.taskGroupId));
|
|
403
|
+
if (taskIndex === -1) return null;
|
|
404
|
+
const [item] = this.queue.splice(taskIndex, 1);
|
|
405
|
+
return item ?? null;
|
|
406
|
+
};
|
|
407
|
+
hasItemsToProcess = () => this.queue.findIndex((item) => !item.options?.taskGroupId || !this.activeGroups.has(item.options.taskGroupId)) !== -1;
|
|
408
|
+
};
|
|
409
|
+
const DEFAULT_PROMISE_DEADLINE = 2147483647;
|
|
410
|
+
const promiseWithDeadline = (executor, options) => {
|
|
411
|
+
return new Promise((resolve, reject) => {
|
|
412
|
+
let taskStarted = false;
|
|
413
|
+
let timeoutId = null;
|
|
414
|
+
const deadline = options.deadline ?? DEFAULT_PROMISE_DEADLINE;
|
|
415
|
+
timeoutId = setTimeout(() => {
|
|
416
|
+
if (!taskStarted) reject(/* @__PURE__ */ new Error("Task was not started within the maximum waiting time"));
|
|
417
|
+
}, deadline);
|
|
418
|
+
timeoutId.unref();
|
|
419
|
+
executor((value) => {
|
|
420
|
+
taskStarted = true;
|
|
421
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
422
|
+
timeoutId = null;
|
|
423
|
+
resolve(value);
|
|
424
|
+
}, (reason) => {
|
|
425
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
426
|
+
timeoutId = null;
|
|
427
|
+
reject(reason);
|
|
428
|
+
});
|
|
429
|
+
});
|
|
519
430
|
};
|
|
520
431
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
};
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
return ensureInitialized(retryCount + 1);
|
|
633
|
-
}
|
|
634
|
-
throw error;
|
|
635
|
-
}
|
|
636
|
-
},
|
|
637
|
-
{ taskGroupId: uuid() }
|
|
638
|
-
);
|
|
639
|
-
};
|
|
640
|
-
return {
|
|
641
|
-
ensureInitialized,
|
|
642
|
-
reset: () => {
|
|
643
|
-
initPromise = null;
|
|
644
|
-
},
|
|
645
|
-
stop: (options2) => taskProcessor.stop(options2)
|
|
646
|
-
};
|
|
432
|
+
//#endregion
|
|
433
|
+
//#region src/taskProcessing/executionGuards.ts
|
|
434
|
+
const guardExclusiveAccess = (options) => {
|
|
435
|
+
const taskProcessor = new TaskProcessor({
|
|
436
|
+
maxActiveTasks: 1,
|
|
437
|
+
maxQueueSize: options?.maxQueueSize ?? 1e3
|
|
438
|
+
});
|
|
439
|
+
return {
|
|
440
|
+
execute: (operation) => taskProcessor.enqueue(async ({ ack }) => {
|
|
441
|
+
try {
|
|
442
|
+
return await operation();
|
|
443
|
+
} finally {
|
|
444
|
+
ack();
|
|
445
|
+
}
|
|
446
|
+
}),
|
|
447
|
+
waitForIdle: () => taskProcessor.waitForEndOfProcessing(),
|
|
448
|
+
stop: (options) => taskProcessor.stop(options)
|
|
449
|
+
};
|
|
450
|
+
};
|
|
451
|
+
const guardBoundedAccess = (getResource, options) => {
|
|
452
|
+
let isStopped = false;
|
|
453
|
+
const taskProcessor = new TaskProcessor({
|
|
454
|
+
maxActiveTasks: options.maxResources,
|
|
455
|
+
maxQueueSize: options.maxQueueSize ?? 1e3
|
|
456
|
+
});
|
|
457
|
+
const resourcePool = [];
|
|
458
|
+
const allResources = /* @__PURE__ */ new Set();
|
|
459
|
+
const ackCallbacks = /* @__PURE__ */ new Map();
|
|
460
|
+
const acquire = async () => taskProcessor.enqueue(async ({ ack }) => {
|
|
461
|
+
try {
|
|
462
|
+
let resource;
|
|
463
|
+
if (options.reuseResources) resource = resourcePool.pop();
|
|
464
|
+
if (!resource) {
|
|
465
|
+
resource = await getResource();
|
|
466
|
+
allResources.add(resource);
|
|
467
|
+
}
|
|
468
|
+
ackCallbacks.set(resource, ack);
|
|
469
|
+
return resource;
|
|
470
|
+
} catch (e) {
|
|
471
|
+
ack();
|
|
472
|
+
throw e;
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
const release = (resource) => {
|
|
476
|
+
const ack = ackCallbacks.get(resource);
|
|
477
|
+
if (ack) {
|
|
478
|
+
ackCallbacks.delete(resource);
|
|
479
|
+
if (options.reuseResources) resourcePool.push(resource);
|
|
480
|
+
ack();
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
const execute = async (operation) => {
|
|
484
|
+
const resource = await acquire();
|
|
485
|
+
try {
|
|
486
|
+
return await operation(resource);
|
|
487
|
+
} finally {
|
|
488
|
+
release(resource);
|
|
489
|
+
}
|
|
490
|
+
};
|
|
491
|
+
return {
|
|
492
|
+
acquire,
|
|
493
|
+
release,
|
|
494
|
+
execute,
|
|
495
|
+
waitForIdle: () => taskProcessor.waitForEndOfProcessing(),
|
|
496
|
+
stop: async (stopOptions) => {
|
|
497
|
+
if (isStopped) return;
|
|
498
|
+
isStopped = true;
|
|
499
|
+
if (options?.closeResource) {
|
|
500
|
+
const resources = [...allResources];
|
|
501
|
+
allResources.clear();
|
|
502
|
+
resourcePool.length = 0;
|
|
503
|
+
await Promise.all(resources.map(async (resource) => await options.closeResource(resource)));
|
|
504
|
+
}
|
|
505
|
+
await taskProcessor.stop(stopOptions);
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
};
|
|
509
|
+
const guardInitializedOnce = (initialize, options) => {
|
|
510
|
+
let initPromise = null;
|
|
511
|
+
const taskProcessor = new TaskProcessor({
|
|
512
|
+
maxActiveTasks: 1,
|
|
513
|
+
maxQueueSize: options?.maxQueueSize ?? 1e3
|
|
514
|
+
});
|
|
515
|
+
const ensureInitialized = async (retryCount = 0) => {
|
|
516
|
+
if (initPromise !== null) return initPromise;
|
|
517
|
+
return taskProcessor.enqueue(async ({ ack }) => {
|
|
518
|
+
if (initPromise !== null) {
|
|
519
|
+
ack();
|
|
520
|
+
return initPromise;
|
|
521
|
+
}
|
|
522
|
+
try {
|
|
523
|
+
const promise = initialize();
|
|
524
|
+
initPromise = promise;
|
|
525
|
+
const result = await promise;
|
|
526
|
+
ack();
|
|
527
|
+
return result;
|
|
528
|
+
} catch (error) {
|
|
529
|
+
initPromise = null;
|
|
530
|
+
ack();
|
|
531
|
+
if (retryCount < (options?.maxRetries ?? 3)) return ensureInitialized(retryCount + 1);
|
|
532
|
+
throw error;
|
|
533
|
+
}
|
|
534
|
+
}, { taskGroupId: v7() });
|
|
535
|
+
};
|
|
536
|
+
return {
|
|
537
|
+
ensureInitialized,
|
|
538
|
+
reset: () => {
|
|
539
|
+
initPromise = null;
|
|
540
|
+
},
|
|
541
|
+
stop: (options) => taskProcessor.stop(options)
|
|
542
|
+
};
|
|
647
543
|
};
|
|
648
544
|
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
} finally {
|
|
692
|
-
locks.delete(lockId);
|
|
693
|
-
ack();
|
|
694
|
-
}
|
|
695
|
-
},
|
|
696
|
-
{ taskGroupId: lockId }
|
|
697
|
-
);
|
|
698
|
-
}
|
|
699
|
-
};
|
|
545
|
+
//#endregion
|
|
546
|
+
//#region src/utils/locking/index.ts
|
|
547
|
+
const InProcessLock = () => {
|
|
548
|
+
const taskProcessor = new TaskProcessor({
|
|
549
|
+
maxActiveTasks: Number.MAX_VALUE,
|
|
550
|
+
maxQueueSize: Number.MAX_VALUE
|
|
551
|
+
});
|
|
552
|
+
const locks = /* @__PURE__ */ new Map();
|
|
553
|
+
return {
|
|
554
|
+
async acquire({ lockId }) {
|
|
555
|
+
await new Promise((resolve, reject) => {
|
|
556
|
+
taskProcessor.enqueue(({ ack }) => {
|
|
557
|
+
locks.set(lockId, ack);
|
|
558
|
+
resolve();
|
|
559
|
+
return Promise.resolve();
|
|
560
|
+
}, { taskGroupId: lockId }).catch(reject);
|
|
561
|
+
});
|
|
562
|
+
},
|
|
563
|
+
async tryAcquire({ lockId }) {
|
|
564
|
+
if (locks.has(lockId)) return false;
|
|
565
|
+
await this.acquire({ lockId });
|
|
566
|
+
return true;
|
|
567
|
+
},
|
|
568
|
+
release({ lockId }) {
|
|
569
|
+
const ack = locks.get(lockId);
|
|
570
|
+
if (ack === void 0) return Promise.resolve(true);
|
|
571
|
+
locks.delete(lockId);
|
|
572
|
+
ack();
|
|
573
|
+
return Promise.resolve(true);
|
|
574
|
+
},
|
|
575
|
+
async withAcquire(handle, { lockId }) {
|
|
576
|
+
return taskProcessor.enqueue(async ({ ack }) => {
|
|
577
|
+
locks.set(lockId, ack);
|
|
578
|
+
try {
|
|
579
|
+
return await handle();
|
|
580
|
+
} finally {
|
|
581
|
+
locks.delete(lockId);
|
|
582
|
+
ack();
|
|
583
|
+
}
|
|
584
|
+
}, { taskGroupId: lockId });
|
|
585
|
+
}
|
|
586
|
+
};
|
|
700
587
|
};
|
|
701
588
|
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
};
|
|
589
|
+
//#endregion
|
|
590
|
+
//#region src/utils/numbers/bigint.ts
|
|
591
|
+
const toNormalizedString = (value) => value.toString().padStart(19, "0");
|
|
592
|
+
const bigInt = { toNormalizedString };
|
|
707
593
|
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
594
|
+
//#endregion
|
|
595
|
+
//#region src/utils/promises.ts
|
|
596
|
+
const delay = (ms) => {
|
|
597
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
598
|
+
};
|
|
599
|
+
const asyncAwaiter = () => {
|
|
600
|
+
const result = {};
|
|
601
|
+
(result.reset = () => {
|
|
602
|
+
result.wait = new Promise((res, rej) => {
|
|
603
|
+
result.resolve = res;
|
|
604
|
+
result.reject = rej;
|
|
605
|
+
});
|
|
606
|
+
result.wait.catch(() => {});
|
|
607
|
+
})();
|
|
608
|
+
return result;
|
|
721
609
|
};
|
|
722
610
|
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
};
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
)
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
);
|
|
805
|
-
var JSONReplacers = {
|
|
806
|
-
bigInt: bigIntReplacer,
|
|
807
|
-
date: dateReplacer
|
|
808
|
-
};
|
|
809
|
-
var JSONRevivers = {
|
|
810
|
-
bigInt: bigIntReviver,
|
|
811
|
-
date: dateReviver
|
|
812
|
-
};
|
|
813
|
-
var jsonSerializer = (options) => {
|
|
814
|
-
const defaultReplacer = JSONReplacer(options);
|
|
815
|
-
const defaultReviver = JSONReviver(options);
|
|
816
|
-
return {
|
|
817
|
-
serialize: (object, serializerOptions) => JSON.stringify(
|
|
818
|
-
object,
|
|
819
|
-
serializerOptions ? JSONReplacer(serializerOptions) : defaultReplacer
|
|
820
|
-
),
|
|
821
|
-
deserialize: (payload, deserializerOptions) => JSON.parse(
|
|
822
|
-
payload,
|
|
823
|
-
deserializerOptions ? JSONReviver(deserializerOptions) : defaultReviver
|
|
824
|
-
)
|
|
825
|
-
};
|
|
826
|
-
};
|
|
827
|
-
var JSONSerializer = Object.assign(jsonSerializer(), {
|
|
828
|
-
from: (options) => options?.serialization?.serializer ?? (options?.serialization?.options ? jsonSerializer(options?.serialization?.options) : JSONSerializer)
|
|
829
|
-
});
|
|
830
|
-
var JSONCodec = (options) => {
|
|
831
|
-
const serializer = "serializer" in options && options.serializer ? options.serializer : jsonSerializer(
|
|
832
|
-
"serializerOptions" in options ? options.serializerOptions : void 0
|
|
833
|
-
);
|
|
834
|
-
const upcast = options.upcast ?? ((doc) => doc);
|
|
835
|
-
const downcast = options.downcast ?? ((doc) => doc);
|
|
836
|
-
return {
|
|
837
|
-
decode: (payload, decodeOptions) => {
|
|
838
|
-
const deserialized = decodeOptions ? serializer.deserialize(payload, decodeOptions) : serializer.deserialize(payload);
|
|
839
|
-
return upcast(deserialized);
|
|
840
|
-
},
|
|
841
|
-
encode: (object, encodeOptions) => {
|
|
842
|
-
const downcasted = downcast(object);
|
|
843
|
-
return encodeOptions ? serializer.serialize(downcasted, encodeOptions) : serializer.serialize(downcasted);
|
|
844
|
-
}
|
|
845
|
-
};
|
|
611
|
+
//#endregion
|
|
612
|
+
//#region src/serialization/json/jsonSerializer.ts
|
|
613
|
+
const bigIntReplacer = (_key, value) => {
|
|
614
|
+
return typeof value === "bigint" ? value.toString() : value;
|
|
615
|
+
};
|
|
616
|
+
const dateReplacer = (_key, value) => {
|
|
617
|
+
return value instanceof Date ? value.toISOString() : value;
|
|
618
|
+
};
|
|
619
|
+
const isFirstLetterNumeric = (str) => {
|
|
620
|
+
const c = str.charCodeAt(0);
|
|
621
|
+
return c >= 48 && c <= 57;
|
|
622
|
+
};
|
|
623
|
+
const isFirstLetterNumericOrMinus = (str) => {
|
|
624
|
+
const c = str.charCodeAt(0);
|
|
625
|
+
return c >= 48 && c <= 57 || c === 45;
|
|
626
|
+
};
|
|
627
|
+
const bigIntReviver = (_key, value, context) => {
|
|
628
|
+
if (typeof value === "number" && Number.isInteger(value) && !Number.isSafeInteger(value)) try {
|
|
629
|
+
return BigInt(context?.source ?? value.toString());
|
|
630
|
+
} catch {
|
|
631
|
+
return value;
|
|
632
|
+
}
|
|
633
|
+
if (typeof value === "string" && value.length > 15) {
|
|
634
|
+
if (isFirstLetterNumericOrMinus(value)) {
|
|
635
|
+
const num = Number(value);
|
|
636
|
+
if (Number.isFinite(num) && !Number.isSafeInteger(num)) try {
|
|
637
|
+
return BigInt(value);
|
|
638
|
+
} catch {}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
return value;
|
|
642
|
+
};
|
|
643
|
+
const dateReviver = (_key, value) => {
|
|
644
|
+
if (typeof value === "string" && value.length === 24 && isFirstLetterNumeric(value) && value[10] === "T" && value[23] === "Z") {
|
|
645
|
+
const date = new Date(value);
|
|
646
|
+
if (!isNaN(date.getTime())) return date;
|
|
647
|
+
}
|
|
648
|
+
return value;
|
|
649
|
+
};
|
|
650
|
+
const composeJSONReplacers = (...replacers) => {
|
|
651
|
+
const filteredReplacers = replacers.filter((r) => r !== void 0);
|
|
652
|
+
if (filteredReplacers.length === 0) return void 0;
|
|
653
|
+
return (key, value) => filteredReplacers.reduce((accValue, replacer) => replacer(key, accValue), value);
|
|
654
|
+
};
|
|
655
|
+
const composeJSONRevivers = (...revivers) => {
|
|
656
|
+
const filteredRevivers = revivers.filter((r) => r !== void 0);
|
|
657
|
+
if (filteredRevivers.length === 0) return void 0;
|
|
658
|
+
return (key, value, context) => filteredRevivers.reduce((accValue, reviver) => reviver(key, accValue, context), value);
|
|
659
|
+
};
|
|
660
|
+
const JSONReplacer = (opts) => composeJSONReplacers(opts?.replacer, opts?.failOnBigIntSerialization !== true ? JSONReplacers.bigInt : void 0, opts?.useDefaultDateSerialization !== true ? JSONReplacers.date : void 0);
|
|
661
|
+
const JSONReviver = (opts) => composeJSONRevivers(opts?.reviver, opts?.parseBigInts === true ? JSONRevivers.bigInt : void 0, opts?.parseDates === true ? JSONRevivers.date : void 0);
|
|
662
|
+
const JSONReplacers = {
|
|
663
|
+
bigInt: bigIntReplacer,
|
|
664
|
+
date: dateReplacer
|
|
665
|
+
};
|
|
666
|
+
const JSONRevivers = {
|
|
667
|
+
bigInt: bigIntReviver,
|
|
668
|
+
date: dateReviver
|
|
669
|
+
};
|
|
670
|
+
const jsonSerializer = (options) => {
|
|
671
|
+
const defaultReplacer = JSONReplacer(options);
|
|
672
|
+
const defaultReviver = JSONReviver(options);
|
|
673
|
+
return {
|
|
674
|
+
serialize: (object, serializerOptions) => JSON.stringify(object, serializerOptions ? JSONReplacer(serializerOptions) : defaultReplacer),
|
|
675
|
+
deserialize: (payload, deserializerOptions) => JSON.parse(payload, deserializerOptions ? JSONReviver(deserializerOptions) : defaultReviver)
|
|
676
|
+
};
|
|
677
|
+
};
|
|
678
|
+
const JSONSerializer = Object.assign(jsonSerializer(), { from: (options) => options?.serialization?.serializer ?? (options?.serialization?.options ? jsonSerializer(options?.serialization?.options) : JSONSerializer) });
|
|
679
|
+
const JSONCodec = (options) => {
|
|
680
|
+
const serializer = "serializer" in options && options.serializer ? options.serializer : jsonSerializer("serializerOptions" in options ? options.serializerOptions : void 0);
|
|
681
|
+
const upcast = options.upcast ?? ((doc) => doc);
|
|
682
|
+
const downcast = options.downcast ?? ((doc) => doc);
|
|
683
|
+
return {
|
|
684
|
+
decode: (payload, decodeOptions) => {
|
|
685
|
+
return upcast(decodeOptions ? serializer.deserialize(payload, decodeOptions) : serializer.deserialize(payload));
|
|
686
|
+
},
|
|
687
|
+
encode: (object, encodeOptions) => {
|
|
688
|
+
const downcasted = downcast(object);
|
|
689
|
+
return encodeOptions ? serializer.serialize(downcasted, encodeOptions) : serializer.serialize(downcasted);
|
|
690
|
+
}
|
|
691
|
+
};
|
|
846
692
|
};
|
|
847
693
|
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
}
|
|
867
|
-
throw error;
|
|
868
|
-
}
|
|
869
|
-
},
|
|
870
|
-
opts ?? { retries: 0 }
|
|
871
|
-
);
|
|
694
|
+
//#endregion
|
|
695
|
+
//#region src/utils/retry.ts
|
|
696
|
+
const NoRetries = { retries: 0 };
|
|
697
|
+
const asyncRetry = async (fn, opts) => {
|
|
698
|
+
if (opts === void 0 || opts.retries === 0) return fn();
|
|
699
|
+
return retry(async (bail) => {
|
|
700
|
+
try {
|
|
701
|
+
const result = await fn();
|
|
702
|
+
if (opts?.shouldRetryResult && opts.shouldRetryResult(result)) throw new EmmettError(`Retrying because of result: ${JSONSerializer.serialize(result)}`);
|
|
703
|
+
return result;
|
|
704
|
+
} catch (error) {
|
|
705
|
+
if (opts?.shouldRetryError && !opts.shouldRetryError(error)) {
|
|
706
|
+
bail(error);
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
throw error;
|
|
710
|
+
}
|
|
711
|
+
}, opts ?? { retries: 0 });
|
|
872
712
|
};
|
|
873
713
|
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
714
|
+
//#endregion
|
|
715
|
+
//#region src/utils/shutdown/gracefulShutdown.ts
|
|
716
|
+
/**
|
|
717
|
+
* Registers handlers for OS signals to enable graceful shutdown.
|
|
718
|
+
* Handles SIGTERM and SIGINT by default.
|
|
719
|
+
* Works in Node.js, Bun, and Deno. Safely no-ops in Browser/Cloudflare Workers.
|
|
720
|
+
*
|
|
721
|
+
* @param handler - Function to call when shutdown signal is received
|
|
722
|
+
* @returns Cleanup function to unregister the handlers
|
|
723
|
+
*/
|
|
724
|
+
const onShutdown = (handler) => {
|
|
725
|
+
const signals = ["SIGTERM", "SIGINT"];
|
|
726
|
+
if (typeof process !== "undefined" && typeof process.on === "function") {
|
|
727
|
+
for (const signal of signals) process.on(signal, handler);
|
|
728
|
+
return () => {
|
|
729
|
+
for (const signal of signals) process.off(signal, handler);
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
const deno = globalThis.Deno;
|
|
733
|
+
if (deno && typeof deno.addSignalListener === "function") {
|
|
734
|
+
for (const signal of signals) deno.addSignalListener(signal, handler);
|
|
735
|
+
return () => {
|
|
736
|
+
for (const signal of signals) deno.removeSignalListener(signal, handler);
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
return () => {};
|
|
900
740
|
};
|
|
901
741
|
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
);
|
|
909
|
-
const view = new BigInt64Array(hashBuffer, 0, 1);
|
|
910
|
-
return view[0];
|
|
742
|
+
//#endregion
|
|
743
|
+
//#region src/utils/strings/hashText.ts
|
|
744
|
+
const textEncoder = new TextEncoder();
|
|
745
|
+
const hashText = async (text) => {
|
|
746
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", textEncoder.encode(text));
|
|
747
|
+
return new BigInt64Array(hashBuffer, 0, 1)[0];
|
|
911
748
|
};
|
|
912
749
|
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
if (options.errors?.throwOnOperationFailures)
|
|
933
|
-
operationResult2.assertSuccessful();
|
|
934
|
-
return operationResult2;
|
|
750
|
+
//#endregion
|
|
751
|
+
//#region src/database/utils.ts
|
|
752
|
+
const isGeneralExpectedDocumentVersion = (version) => {
|
|
753
|
+
return version === "DOCUMENT_DOES_NOT_EXIST" || version === "DOCUMENT_EXISTS" || version === "NO_CONCURRENCY_CHECK";
|
|
754
|
+
};
|
|
755
|
+
const expectedVersionValue = (version) => version === void 0 || isGeneralExpectedDocumentVersion(version) ? null : version;
|
|
756
|
+
const operationResult = (result, options) => {
|
|
757
|
+
const operationResult = {
|
|
758
|
+
...result,
|
|
759
|
+
acknowledged: true,
|
|
760
|
+
successful: result.successful,
|
|
761
|
+
assertSuccessful: (errorMessage) => {
|
|
762
|
+
const { successful } = result;
|
|
763
|
+
const { operationName, collectionName } = options;
|
|
764
|
+
if (!successful) throw new ConcurrencyInMemoryDatabaseError(errorMessage ?? `${operationName} on ${collectionName} failed. Expected document state does not match current one! Result: ${JSONSerializer.serialize(result)}!`);
|
|
765
|
+
}
|
|
766
|
+
};
|
|
767
|
+
if (options.errors?.throwOnOperationFailures) operationResult.assertSuccessful();
|
|
768
|
+
return operationResult;
|
|
935
769
|
};
|
|
936
770
|
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
return {
|
|
1143
|
-
...replaceResult,
|
|
1144
|
-
document: {
|
|
1145
|
-
...result,
|
|
1146
|
-
_version: replaceResult.nextExpectedVersion
|
|
1147
|
-
}
|
|
1148
|
-
};
|
|
1149
|
-
}
|
|
1150
|
-
return operationResult(
|
|
1151
|
-
{
|
|
1152
|
-
successful: true,
|
|
1153
|
-
document: existing
|
|
1154
|
-
},
|
|
1155
|
-
{ operationName: "handle", collectionName, errors }
|
|
1156
|
-
);
|
|
1157
|
-
}
|
|
1158
|
-
};
|
|
1159
|
-
return collection;
|
|
1160
|
-
}
|
|
1161
|
-
};
|
|
771
|
+
//#endregion
|
|
772
|
+
//#region src/database/inMemoryDatabase.ts
|
|
773
|
+
const getInMemoryDatabase = () => {
|
|
774
|
+
const storage = /* @__PURE__ */ new Map();
|
|
775
|
+
return { collection: (collectionName, collectionOptions = {}) => {
|
|
776
|
+
const ensureCollectionCreated = () => {
|
|
777
|
+
if (!storage.has(collectionName)) storage.set(collectionName, []);
|
|
778
|
+
};
|
|
779
|
+
const errors = collectionOptions.errors;
|
|
780
|
+
const collection = {
|
|
781
|
+
collectionName,
|
|
782
|
+
insertOne: async (document) => {
|
|
783
|
+
ensureCollectionCreated();
|
|
784
|
+
const _id = document._id ?? v7();
|
|
785
|
+
const _version = document._version ?? 1n;
|
|
786
|
+
if (await collection.findOne((c) => c._id === _id)) return operationResult({
|
|
787
|
+
successful: false,
|
|
788
|
+
insertedId: null,
|
|
789
|
+
nextExpectedVersion: _version
|
|
790
|
+
}, {
|
|
791
|
+
operationName: "insertOne",
|
|
792
|
+
collectionName,
|
|
793
|
+
errors
|
|
794
|
+
});
|
|
795
|
+
const documentsInCollection = storage.get(collectionName);
|
|
796
|
+
const newDocument = {
|
|
797
|
+
...document,
|
|
798
|
+
_id,
|
|
799
|
+
_version
|
|
800
|
+
};
|
|
801
|
+
const newCollection = [...documentsInCollection, newDocument];
|
|
802
|
+
storage.set(collectionName, newCollection);
|
|
803
|
+
return operationResult({
|
|
804
|
+
successful: true,
|
|
805
|
+
insertedId: _id,
|
|
806
|
+
nextExpectedVersion: _version
|
|
807
|
+
}, {
|
|
808
|
+
operationName: "insertOne",
|
|
809
|
+
collectionName,
|
|
810
|
+
errors
|
|
811
|
+
});
|
|
812
|
+
},
|
|
813
|
+
findOne: (predicate) => {
|
|
814
|
+
ensureCollectionCreated();
|
|
815
|
+
const documentsInCollection = storage.get(collectionName);
|
|
816
|
+
const firstOne = (predicate ? documentsInCollection?.filter((doc) => predicate(doc)) : documentsInCollection)?.[0] ?? null;
|
|
817
|
+
return Promise.resolve(firstOne);
|
|
818
|
+
},
|
|
819
|
+
find: (predicate) => {
|
|
820
|
+
ensureCollectionCreated();
|
|
821
|
+
const documentsInCollection = storage.get(collectionName);
|
|
822
|
+
const filteredDocuments = predicate ? documentsInCollection?.filter((doc) => predicate(doc)) : documentsInCollection;
|
|
823
|
+
return Promise.resolve(filteredDocuments);
|
|
824
|
+
},
|
|
825
|
+
deleteOne: (predicate) => {
|
|
826
|
+
ensureCollectionCreated();
|
|
827
|
+
const documentsInCollection = storage.get(collectionName);
|
|
828
|
+
if (predicate) {
|
|
829
|
+
const foundIndex = documentsInCollection.findIndex((doc) => predicate(doc));
|
|
830
|
+
if (foundIndex === -1) return Promise.resolve(operationResult({
|
|
831
|
+
successful: false,
|
|
832
|
+
matchedCount: 0,
|
|
833
|
+
deletedCount: 0
|
|
834
|
+
}, {
|
|
835
|
+
operationName: "deleteOne",
|
|
836
|
+
collectionName,
|
|
837
|
+
errors
|
|
838
|
+
}));
|
|
839
|
+
else {
|
|
840
|
+
const newCollection = documentsInCollection.toSpliced(foundIndex, 1);
|
|
841
|
+
storage.set(collectionName, newCollection);
|
|
842
|
+
return Promise.resolve(operationResult({
|
|
843
|
+
successful: true,
|
|
844
|
+
matchedCount: 1,
|
|
845
|
+
deletedCount: 1
|
|
846
|
+
}, {
|
|
847
|
+
operationName: "deleteOne",
|
|
848
|
+
collectionName,
|
|
849
|
+
errors
|
|
850
|
+
}));
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
const newCollection = documentsInCollection.slice(1);
|
|
854
|
+
storage.set(collectionName, newCollection);
|
|
855
|
+
return Promise.resolve(operationResult({
|
|
856
|
+
successful: true,
|
|
857
|
+
matchedCount: 1,
|
|
858
|
+
deletedCount: 1
|
|
859
|
+
}, {
|
|
860
|
+
operationName: "deleteOne",
|
|
861
|
+
collectionName,
|
|
862
|
+
errors
|
|
863
|
+
}));
|
|
864
|
+
},
|
|
865
|
+
replaceOne: (predicate, document, options) => {
|
|
866
|
+
ensureCollectionCreated();
|
|
867
|
+
const documentsInCollection = storage.get(collectionName);
|
|
868
|
+
const firstIndex = documentsInCollection.findIndex((doc) => predicate(doc));
|
|
869
|
+
if (firstIndex === void 0 || firstIndex === -1) return Promise.resolve(operationResult({
|
|
870
|
+
successful: false,
|
|
871
|
+
matchedCount: 0,
|
|
872
|
+
modifiedCount: 0,
|
|
873
|
+
nextExpectedVersion: 0n
|
|
874
|
+
}, {
|
|
875
|
+
operationName: "replaceOne",
|
|
876
|
+
collectionName,
|
|
877
|
+
errors
|
|
878
|
+
}));
|
|
879
|
+
const existing = documentsInCollection[firstIndex];
|
|
880
|
+
if (typeof options?.expectedVersion === "bigint" && existing._version !== options.expectedVersion) return Promise.resolve(operationResult({
|
|
881
|
+
successful: false,
|
|
882
|
+
matchedCount: 1,
|
|
883
|
+
modifiedCount: 0,
|
|
884
|
+
nextExpectedVersion: existing._version
|
|
885
|
+
}, {
|
|
886
|
+
operationName: "replaceOne",
|
|
887
|
+
collectionName,
|
|
888
|
+
errors
|
|
889
|
+
}));
|
|
890
|
+
const newVersion = existing._version + 1n;
|
|
891
|
+
const newCollection = documentsInCollection.with(firstIndex, {
|
|
892
|
+
_id: existing._id,
|
|
893
|
+
...document,
|
|
894
|
+
_version: newVersion
|
|
895
|
+
});
|
|
896
|
+
storage.set(collectionName, newCollection);
|
|
897
|
+
return Promise.resolve(operationResult({
|
|
898
|
+
successful: true,
|
|
899
|
+
modifiedCount: 1,
|
|
900
|
+
matchedCount: firstIndex,
|
|
901
|
+
nextExpectedVersion: newVersion
|
|
902
|
+
}, {
|
|
903
|
+
operationName: "replaceOne",
|
|
904
|
+
collectionName,
|
|
905
|
+
errors
|
|
906
|
+
}));
|
|
907
|
+
},
|
|
908
|
+
handle: async (id, handle, options) => {
|
|
909
|
+
const { expectedVersion: version, ...operationOptions } = options ?? {};
|
|
910
|
+
ensureCollectionCreated();
|
|
911
|
+
const existing = await collection.findOne(({ _id }) => _id === id);
|
|
912
|
+
const expectedVersion = expectedVersionValue(version);
|
|
913
|
+
if (existing == null && version === "DOCUMENT_EXISTS" || existing == null && expectedVersion != null || existing != null && version === "DOCUMENT_DOES_NOT_EXIST" || existing != null && expectedVersion !== null && existing._version !== expectedVersion) return operationResult({
|
|
914
|
+
successful: false,
|
|
915
|
+
document: existing
|
|
916
|
+
}, {
|
|
917
|
+
operationName: "handle",
|
|
918
|
+
collectionName,
|
|
919
|
+
errors
|
|
920
|
+
});
|
|
921
|
+
const result = handle(existing !== null ? { ...existing } : null);
|
|
922
|
+
if (deepEquals(existing, result)) return operationResult({
|
|
923
|
+
successful: true,
|
|
924
|
+
document: existing
|
|
925
|
+
}, {
|
|
926
|
+
operationName: "handle",
|
|
927
|
+
collectionName,
|
|
928
|
+
errors
|
|
929
|
+
});
|
|
930
|
+
if (!existing && result) {
|
|
931
|
+
const newDoc = {
|
|
932
|
+
...result,
|
|
933
|
+
_id: id
|
|
934
|
+
};
|
|
935
|
+
const insertResult = await collection.insertOne({
|
|
936
|
+
...newDoc,
|
|
937
|
+
_id: id
|
|
938
|
+
});
|
|
939
|
+
return {
|
|
940
|
+
...insertResult,
|
|
941
|
+
document: {
|
|
942
|
+
...newDoc,
|
|
943
|
+
_version: insertResult.nextExpectedVersion
|
|
944
|
+
}
|
|
945
|
+
};
|
|
946
|
+
}
|
|
947
|
+
if (existing && !result) return {
|
|
948
|
+
...await collection.deleteOne(({ _id }) => id === _id),
|
|
949
|
+
document: null
|
|
950
|
+
};
|
|
951
|
+
if (existing && result) {
|
|
952
|
+
const replaceResult = await collection.replaceOne(({ _id }) => id === _id, result, {
|
|
953
|
+
...operationOptions,
|
|
954
|
+
expectedVersion: expectedVersion ?? "DOCUMENT_EXISTS"
|
|
955
|
+
});
|
|
956
|
+
return {
|
|
957
|
+
...replaceResult,
|
|
958
|
+
document: {
|
|
959
|
+
...result,
|
|
960
|
+
_version: replaceResult.nextExpectedVersion
|
|
961
|
+
}
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
return operationResult({
|
|
965
|
+
successful: true,
|
|
966
|
+
document: existing
|
|
967
|
+
}, {
|
|
968
|
+
operationName: "handle",
|
|
969
|
+
collectionName,
|
|
970
|
+
errors
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
};
|
|
974
|
+
return collection;
|
|
975
|
+
} };
|
|
1162
976
|
};
|
|
1163
977
|
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
};
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
};
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
};
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
} = options;
|
|
1332
|
-
return reactor({
|
|
1333
|
-
...rest,
|
|
1334
|
-
type: MessageProcessorType.PROJECTOR,
|
|
1335
|
-
canHandle: projection2.canHandle,
|
|
1336
|
-
processorId,
|
|
1337
|
-
messageOptions: options.projection.eventsOptions,
|
|
1338
|
-
hooks: {
|
|
1339
|
-
onInit: options.hooks?.onInit,
|
|
1340
|
-
onStart: options.truncateOnStart && options.projection.truncate || options.hooks?.onStart ? async (context) => {
|
|
1341
|
-
if (options.truncateOnStart && options.projection.truncate)
|
|
1342
|
-
await options.projection.truncate(context);
|
|
1343
|
-
if (options.hooks?.onStart) await options.hooks?.onStart(context);
|
|
1344
|
-
} : void 0,
|
|
1345
|
-
onClose: options.hooks?.onClose
|
|
1346
|
-
},
|
|
1347
|
-
eachMessage: async (event2, context) => projection2.handle([event2], context)
|
|
1348
|
-
});
|
|
978
|
+
//#endregion
|
|
979
|
+
//#region src/processors/processors.ts
|
|
980
|
+
const getCheckpoint = (message) => {
|
|
981
|
+
return message.metadata.checkpoint;
|
|
982
|
+
};
|
|
983
|
+
const wasMessageHandled = (message, checkpoint) => {
|
|
984
|
+
const messageCheckpoint = getCheckpoint(message);
|
|
985
|
+
return messageCheckpoint !== null && messageCheckpoint !== void 0 && checkpoint !== null && checkpoint !== void 0 && messageCheckpoint <= checkpoint;
|
|
986
|
+
};
|
|
987
|
+
const MessageProcessorType = {
|
|
988
|
+
PROJECTOR: "projector",
|
|
989
|
+
REACTOR: "reactor"
|
|
990
|
+
};
|
|
991
|
+
const MessageProcessor = { result: {
|
|
992
|
+
skip: (options) => ({
|
|
993
|
+
type: "SKIP",
|
|
994
|
+
...options ?? {}
|
|
995
|
+
}),
|
|
996
|
+
stop: (options) => ({
|
|
997
|
+
type: "STOP",
|
|
998
|
+
...options ?? {}
|
|
999
|
+
})
|
|
1000
|
+
} };
|
|
1001
|
+
const defaultProcessingMessageProcessingScope = (handler, partialContext) => handler(partialContext);
|
|
1002
|
+
const bigIntProcessorCheckpoint = (value) => bigInt.toNormalizedString(value);
|
|
1003
|
+
const parseBigIntProcessorCheckpoint = (value) => BigInt(value);
|
|
1004
|
+
const defaultProcessorVersion = 1;
|
|
1005
|
+
const defaultProcessorPartition = defaultTag;
|
|
1006
|
+
const getProcessorInstanceId = (processorId) => `${processorId}:${v7()}`;
|
|
1007
|
+
const getProjectorId = (options) => `emt:processor:projector:${options.projectionName}`;
|
|
1008
|
+
const reactor = (options) => {
|
|
1009
|
+
const { checkpoints, processorId, processorInstanceId: instanceId = getProcessorInstanceId(processorId), type = MessageProcessorType.REACTOR, version = 1, partition = defaultProcessorPartition, hooks = {}, processingScope = defaultProcessingMessageProcessingScope, startFrom, canHandle, stopAfter } = options;
|
|
1010
|
+
const isCustomBatch = "eachBatch" in options && !!options.eachBatch;
|
|
1011
|
+
const eachBatch = isCustomBatch ? options.eachBatch : async (messages, context) => {
|
|
1012
|
+
let result = void 0;
|
|
1013
|
+
for (let i = 0; i < messages.length; i++) {
|
|
1014
|
+
const message = messages[i];
|
|
1015
|
+
const messageProcessingResult = await options.eachMessage(message, context);
|
|
1016
|
+
if (messageProcessingResult && messageProcessingResult.type === "STOP") {
|
|
1017
|
+
result = {
|
|
1018
|
+
...messageProcessingResult,
|
|
1019
|
+
lastSuccessfulMessage: messageProcessingResult.error ? messages[i - 1] : message
|
|
1020
|
+
};
|
|
1021
|
+
break;
|
|
1022
|
+
}
|
|
1023
|
+
if (stopAfter && stopAfter(message)) {
|
|
1024
|
+
result = {
|
|
1025
|
+
type: "STOP",
|
|
1026
|
+
reason: "Stop condition reached",
|
|
1027
|
+
lastSuccessfulMessage: message
|
|
1028
|
+
};
|
|
1029
|
+
break;
|
|
1030
|
+
}
|
|
1031
|
+
if (messageProcessingResult && messageProcessingResult.type === "SKIP") {
|
|
1032
|
+
result = {
|
|
1033
|
+
...messageProcessingResult,
|
|
1034
|
+
lastSuccessfulMessage: message
|
|
1035
|
+
};
|
|
1036
|
+
continue;
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
return result;
|
|
1040
|
+
};
|
|
1041
|
+
let isInitiated = false;
|
|
1042
|
+
let isActive = false;
|
|
1043
|
+
let lastCheckpoint = null;
|
|
1044
|
+
let closeSignal = null;
|
|
1045
|
+
const init = async (initOptions) => {
|
|
1046
|
+
if (isInitiated) return;
|
|
1047
|
+
if (hooks.onInit === void 0) {
|
|
1048
|
+
isInitiated = true;
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
1051
|
+
return await processingScope(async (context) => {
|
|
1052
|
+
await hooks.onInit(context);
|
|
1053
|
+
isInitiated = true;
|
|
1054
|
+
}, initOptions);
|
|
1055
|
+
};
|
|
1056
|
+
const close = async (closeOptions) => {
|
|
1057
|
+
isActive = false;
|
|
1058
|
+
if (closeSignal) {
|
|
1059
|
+
closeSignal();
|
|
1060
|
+
closeSignal = null;
|
|
1061
|
+
}
|
|
1062
|
+
if (hooks.onClose) await processingScope(hooks.onClose, closeOptions);
|
|
1063
|
+
};
|
|
1064
|
+
return {
|
|
1065
|
+
id: processorId,
|
|
1066
|
+
instanceId,
|
|
1067
|
+
type,
|
|
1068
|
+
canHandle,
|
|
1069
|
+
init,
|
|
1070
|
+
start: async (startOptions) => {
|
|
1071
|
+
if (isActive) return;
|
|
1072
|
+
await init(startOptions);
|
|
1073
|
+
isActive = true;
|
|
1074
|
+
closeSignal = onShutdown(() => close(startOptions));
|
|
1075
|
+
if (lastCheckpoint !== null) return { lastCheckpoint };
|
|
1076
|
+
return await processingScope(async (context) => {
|
|
1077
|
+
if (hooks.onStart) await hooks.onStart(context);
|
|
1078
|
+
if (startFrom && startFrom !== "CURRENT") return startFrom;
|
|
1079
|
+
if (checkpoints) lastCheckpoint = (await checkpoints?.read({
|
|
1080
|
+
processorId,
|
|
1081
|
+
partition
|
|
1082
|
+
}, {
|
|
1083
|
+
...startOptions,
|
|
1084
|
+
...context
|
|
1085
|
+
})).lastCheckpoint;
|
|
1086
|
+
if (lastCheckpoint === null) return "BEGINNING";
|
|
1087
|
+
return { lastCheckpoint };
|
|
1088
|
+
}, startOptions);
|
|
1089
|
+
},
|
|
1090
|
+
close,
|
|
1091
|
+
get isActive() {
|
|
1092
|
+
return isActive;
|
|
1093
|
+
},
|
|
1094
|
+
handle: async (messages, partialContext) => {
|
|
1095
|
+
if (!isActive) return Promise.resolve();
|
|
1096
|
+
return await processingScope(async (context) => {
|
|
1097
|
+
const messagesAboveCheckpoint = messages.filter((message) => !wasMessageHandled(message, lastCheckpoint));
|
|
1098
|
+
const upcastedMessages = messagesAboveCheckpoint.map((message) => upcastRecordedMessage(message, options.messageOptions?.schema?.versioning)).filter((upcasted) => !canHandle || canHandle.includes(upcasted.type));
|
|
1099
|
+
const stopMessageIndex = isCustomBatch && stopAfter ? upcastedMessages.findIndex(stopAfter) : -1;
|
|
1100
|
+
const unhandledMessages = stopMessageIndex !== -1 ? upcastedMessages.slice(0, stopMessageIndex + 1) : upcastedMessages;
|
|
1101
|
+
const batchResult = await eachBatch(unhandledMessages, context);
|
|
1102
|
+
const messageProcessingResult = batchResult?.type === "STOP" ? batchResult : stopMessageIndex !== -1 ? {
|
|
1103
|
+
type: "STOP",
|
|
1104
|
+
reason: "Stop condition reached",
|
|
1105
|
+
lastSuccessfulMessage: unhandledMessages[stopMessageIndex]
|
|
1106
|
+
} : batchResult;
|
|
1107
|
+
const isStop = messageProcessingResult && messageProcessingResult.type === "STOP";
|
|
1108
|
+
const checkpointMessage = messageProcessingResult?.type === "STOP" ? messageProcessingResult.lastSuccessfulMessage : messagesAboveCheckpoint[messagesAboveCheckpoint.length - 1];
|
|
1109
|
+
if (checkpointMessage && checkpoints) {
|
|
1110
|
+
const storeCheckpointResult = await checkpoints.store({
|
|
1111
|
+
processorId,
|
|
1112
|
+
version,
|
|
1113
|
+
message: checkpointMessage,
|
|
1114
|
+
lastCheckpoint,
|
|
1115
|
+
partition
|
|
1116
|
+
}, context);
|
|
1117
|
+
if (storeCheckpointResult.success) lastCheckpoint = storeCheckpointResult.newCheckpoint;
|
|
1118
|
+
}
|
|
1119
|
+
if (isStop) {
|
|
1120
|
+
isActive = false;
|
|
1121
|
+
return messageProcessingResult;
|
|
1122
|
+
}
|
|
1123
|
+
}, partialContext);
|
|
1124
|
+
}
|
|
1125
|
+
};
|
|
1126
|
+
};
|
|
1127
|
+
const projector = (options) => {
|
|
1128
|
+
const { projection, processorId = getProjectorId({ projectionName: projection.name ?? "unknown" }), ...rest } = options;
|
|
1129
|
+
return reactor({
|
|
1130
|
+
...rest,
|
|
1131
|
+
type: MessageProcessorType.PROJECTOR,
|
|
1132
|
+
canHandle: projection.canHandle,
|
|
1133
|
+
processorId,
|
|
1134
|
+
messageOptions: options.projection.eventsOptions,
|
|
1135
|
+
hooks: {
|
|
1136
|
+
onInit: options.hooks?.onInit,
|
|
1137
|
+
onStart: options.truncateOnStart && options.projection.truncate || options.hooks?.onStart ? async (context) => {
|
|
1138
|
+
if (options.truncateOnStart && options.projection.truncate) await options.projection.truncate(context);
|
|
1139
|
+
if (options.hooks?.onStart) await options.hooks?.onStart(context);
|
|
1140
|
+
} : void 0,
|
|
1141
|
+
onClose: options.hooks?.onClose
|
|
1142
|
+
},
|
|
1143
|
+
eachBatch: async (events, context) => projection.handle(events, context)
|
|
1144
|
+
});
|
|
1349
1145
|
};
|
|
1350
1146
|
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
};
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
processorId: options.processorId
|
|
1430
|
-
}),
|
|
1431
|
-
checkpoints: inMemoryCheckpointer()
|
|
1432
|
-
});
|
|
1433
|
-
return Object.assign(processor, { database });
|
|
1147
|
+
//#endregion
|
|
1148
|
+
//#region src/processors/inMemoryProcessors.ts
|
|
1149
|
+
const inMemoryCheckpointer = () => {
|
|
1150
|
+
return {
|
|
1151
|
+
read: async ({ processorId }, { database }) => {
|
|
1152
|
+
const checkpoint = await database.collection("emt_processor_checkpoints").findOne((d) => d._id === processorId);
|
|
1153
|
+
return Promise.resolve({ lastCheckpoint: checkpoint?.lastCheckpoint ?? null });
|
|
1154
|
+
},
|
|
1155
|
+
store: async (context, { database }) => {
|
|
1156
|
+
const { message, processorId, lastCheckpoint } = context;
|
|
1157
|
+
const checkpoints = database.collection("emt_processor_checkpoints");
|
|
1158
|
+
const currentPosition = (await checkpoints.findOne((d) => d._id === processorId))?.lastCheckpoint ?? null;
|
|
1159
|
+
const newCheckpoint = getCheckpoint(message);
|
|
1160
|
+
if (currentPosition && (currentPosition === newCheckpoint || currentPosition !== lastCheckpoint)) return {
|
|
1161
|
+
success: false,
|
|
1162
|
+
reason: currentPosition === newCheckpoint ? "IGNORED" : newCheckpoint !== null && currentPosition > newCheckpoint ? "CURRENT_AHEAD" : "MISMATCH"
|
|
1163
|
+
};
|
|
1164
|
+
await checkpoints.handle(processorId, (existing) => ({
|
|
1165
|
+
...existing ?? {},
|
|
1166
|
+
_id: processorId,
|
|
1167
|
+
lastCheckpoint: newCheckpoint
|
|
1168
|
+
}));
|
|
1169
|
+
return {
|
|
1170
|
+
success: true,
|
|
1171
|
+
newCheckpoint
|
|
1172
|
+
};
|
|
1173
|
+
}
|
|
1174
|
+
};
|
|
1175
|
+
};
|
|
1176
|
+
const inMemoryProcessingScope = (options) => {
|
|
1177
|
+
const processorDatabase = options.database;
|
|
1178
|
+
const processingScope = (handler, partialContext) => {
|
|
1179
|
+
const database = processorDatabase ?? partialContext?.database;
|
|
1180
|
+
if (!database) throw new EmmettError(`InMemory processor '${options.processorId}' is missing database. Ensure that you passed it through options`);
|
|
1181
|
+
return handler({
|
|
1182
|
+
...partialContext,
|
|
1183
|
+
database
|
|
1184
|
+
});
|
|
1185
|
+
};
|
|
1186
|
+
return processingScope;
|
|
1187
|
+
};
|
|
1188
|
+
const inMemoryProjector = (options) => {
|
|
1189
|
+
const database = options.connectionOptions?.database ?? getInMemoryDatabase();
|
|
1190
|
+
const hooks = {
|
|
1191
|
+
onInit: options.hooks?.onInit,
|
|
1192
|
+
onStart: options.hooks?.onStart,
|
|
1193
|
+
onClose: options.hooks?.onClose ? async (context) => {
|
|
1194
|
+
if (options.hooks?.onClose) await options.hooks?.onClose(context);
|
|
1195
|
+
} : void 0
|
|
1196
|
+
};
|
|
1197
|
+
const processor = projector({
|
|
1198
|
+
...options,
|
|
1199
|
+
hooks,
|
|
1200
|
+
processingScope: inMemoryProcessingScope({
|
|
1201
|
+
database,
|
|
1202
|
+
processorId: options.processorId ?? `projection:${options.projection.name}`
|
|
1203
|
+
}),
|
|
1204
|
+
checkpoints: inMemoryCheckpointer()
|
|
1205
|
+
});
|
|
1206
|
+
return Object.assign(processor, { database });
|
|
1207
|
+
};
|
|
1208
|
+
const inMemoryReactor = (options) => {
|
|
1209
|
+
const database = options.connectionOptions?.database ?? getInMemoryDatabase();
|
|
1210
|
+
const hooks = {
|
|
1211
|
+
onInit: options.hooks?.onInit,
|
|
1212
|
+
onStart: options.hooks?.onStart,
|
|
1213
|
+
onClose: options.hooks?.onClose
|
|
1214
|
+
};
|
|
1215
|
+
const processor = reactor({
|
|
1216
|
+
...options,
|
|
1217
|
+
hooks,
|
|
1218
|
+
processingScope: inMemoryProcessingScope({
|
|
1219
|
+
database,
|
|
1220
|
+
processorId: options.processorId
|
|
1221
|
+
}),
|
|
1222
|
+
checkpoints: inMemoryCheckpointer()
|
|
1223
|
+
});
|
|
1224
|
+
return Object.assign(processor, { database });
|
|
1434
1225
|
};
|
|
1435
1226
|
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
};
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
}) => ({
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
...context,
|
|
1472
|
-
database: context.database
|
|
1473
|
-
});
|
|
1474
|
-
} : void 0
|
|
1227
|
+
//#endregion
|
|
1228
|
+
//#region src/eventStore/projections/inMemory/inMemoryProjection.ts
|
|
1229
|
+
const DATABASE_REQUIRED_ERROR_MESSAGE = "Database is required in context for InMemory projections";
|
|
1230
|
+
/**
|
|
1231
|
+
* Handles projections for the InMemoryEventStore
|
|
1232
|
+
* Similar to the PostgreSQL implementation, this processes events through projections
|
|
1233
|
+
*/
|
|
1234
|
+
const handleInMemoryProjections = async (options) => {
|
|
1235
|
+
const { projections, events, database, eventStore } = options;
|
|
1236
|
+
const eventTypes = events.map((e) => e.type);
|
|
1237
|
+
const relevantProjections = projections.filter((p) => p.canHandle.some((type) => eventTypes.includes(type)));
|
|
1238
|
+
for (const projection of relevantProjections) await projection.handle(events, {
|
|
1239
|
+
eventStore,
|
|
1240
|
+
database
|
|
1241
|
+
});
|
|
1242
|
+
};
|
|
1243
|
+
/**
|
|
1244
|
+
* Creates an InMemory projection
|
|
1245
|
+
*/
|
|
1246
|
+
const inMemoryProjection = ({ truncate, handle, canHandle }) => ({
|
|
1247
|
+
canHandle,
|
|
1248
|
+
handle: async (events, context) => {
|
|
1249
|
+
if (!context.database) throw new Error(DATABASE_REQUIRED_ERROR_MESSAGE);
|
|
1250
|
+
await handle(events, {
|
|
1251
|
+
...context,
|
|
1252
|
+
database: context.database
|
|
1253
|
+
});
|
|
1254
|
+
},
|
|
1255
|
+
truncate: truncate ? (context) => {
|
|
1256
|
+
if (!context.database) throw new Error(DATABASE_REQUIRED_ERROR_MESSAGE);
|
|
1257
|
+
return truncate({
|
|
1258
|
+
...context,
|
|
1259
|
+
database: context.database
|
|
1260
|
+
});
|
|
1261
|
+
} : void 0
|
|
1475
1262
|
});
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
...options,
|
|
1509
|
-
getDocumentId: options.getDocumentId ?? ((event2) => event2.metadata.streamName)
|
|
1510
|
-
});
|
|
1263
|
+
/**
|
|
1264
|
+
* Creates a projection that handles events across multiple streams
|
|
1265
|
+
*/
|
|
1266
|
+
const inMemoryMultiStreamProjection = (options) => {
|
|
1267
|
+
const { collectionName, getDocumentId, canHandle } = options;
|
|
1268
|
+
return inMemoryProjection({
|
|
1269
|
+
handle: async (events, { database }) => {
|
|
1270
|
+
const collection = database.collection(collectionName);
|
|
1271
|
+
for (const event of events) await collection.handle(getDocumentId(event), (document) => {
|
|
1272
|
+
if ("initialState" in options) return options.evolve(document ?? options.initialState(), event);
|
|
1273
|
+
else return options.evolve(document, event);
|
|
1274
|
+
});
|
|
1275
|
+
},
|
|
1276
|
+
canHandle,
|
|
1277
|
+
truncate: async ({ database }) => {
|
|
1278
|
+
const collection = database.collection(collectionName);
|
|
1279
|
+
const documents = await collection.find();
|
|
1280
|
+
for (const doc of documents) if (doc && "_id" in doc) {
|
|
1281
|
+
const id = doc._id;
|
|
1282
|
+
await collection.deleteOne((d) => d._id === id);
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
});
|
|
1286
|
+
};
|
|
1287
|
+
/**
|
|
1288
|
+
* Creates a projection that handles events from a single stream
|
|
1289
|
+
*/
|
|
1290
|
+
const inMemorySingleStreamProjection = (options) => {
|
|
1291
|
+
return inMemoryMultiStreamProjection({
|
|
1292
|
+
...options,
|
|
1293
|
+
getDocumentId: options.getDocumentId ?? ((event) => event.metadata.streamName)
|
|
1294
|
+
});
|
|
1511
1295
|
};
|
|
1512
1296
|
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
// src/testing/assertions.ts
|
|
1297
|
+
//#endregion
|
|
1298
|
+
//#region src/testing/assertions.ts
|
|
1517
1299
|
var AssertionError = class extends Error {
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
};
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
};
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
await promise;
|
|
1599
|
-
throw new AssertionError("Function didn't throw expected error");
|
|
1600
|
-
} catch (error) {
|
|
1601
|
-
if (!errorCheck) return;
|
|
1602
|
-
if (errorCheck instanceof Error) assertDeepEqual(error, errorCheck);
|
|
1603
|
-
else assertTrue(errorCheck(error));
|
|
1604
|
-
}
|
|
1605
|
-
};
|
|
1606
|
-
var assertMatches = (actual, expected, message2) => {
|
|
1607
|
-
if (!isSubset(actual, expected))
|
|
1608
|
-
throw new AssertionError(
|
|
1609
|
-
message2 ?? `subObj:
|
|
1610
|
-
${JSONSerializer.serialize(expected)}
|
|
1611
|
-
is not subset of
|
|
1612
|
-
${JSONSerializer.serialize(actual)}`
|
|
1613
|
-
);
|
|
1614
|
-
};
|
|
1615
|
-
var assertDeepEqual = (actual, expected, message2) => {
|
|
1616
|
-
if (!deepEquals(actual, expected))
|
|
1617
|
-
throw new AssertionError(
|
|
1618
|
-
message2 ?? `subObj:
|
|
1619
|
-
${JSONSerializer.serialize(expected)}
|
|
1620
|
-
is not equal to
|
|
1621
|
-
${JSONSerializer.serialize(actual)}`
|
|
1622
|
-
);
|
|
1623
|
-
};
|
|
1624
|
-
var assertNotDeepEqual = (actual, expected, message2) => {
|
|
1625
|
-
if (deepEquals(actual, expected))
|
|
1626
|
-
throw new AssertionError(
|
|
1627
|
-
message2 ?? `subObj:
|
|
1628
|
-
${JSONSerializer.serialize(expected)}
|
|
1629
|
-
is equals to
|
|
1630
|
-
${JSONSerializer.serialize(actual)}`
|
|
1631
|
-
);
|
|
1632
|
-
};
|
|
1633
|
-
var assertThat = (item) => {
|
|
1634
|
-
return {
|
|
1635
|
-
isEqualTo: (other) => assertTrue(deepEquals(item, other))
|
|
1636
|
-
};
|
|
1637
|
-
};
|
|
1638
|
-
var assertDefined = (value, message2) => {
|
|
1639
|
-
assertOk(value, message2 instanceof Error ? message2.message : message2);
|
|
1640
|
-
};
|
|
1641
|
-
function assertFalse(condition, message2) {
|
|
1642
|
-
if (condition !== false)
|
|
1643
|
-
throw new AssertionError(message2 ?? `Condition is true`);
|
|
1300
|
+
constructor(message) {
|
|
1301
|
+
super(message);
|
|
1302
|
+
}
|
|
1303
|
+
};
|
|
1304
|
+
const isSubset = (superObj, subObj) => {
|
|
1305
|
+
const sup = superObj;
|
|
1306
|
+
const sub = subObj;
|
|
1307
|
+
assertOk(sup);
|
|
1308
|
+
assertOk(sub);
|
|
1309
|
+
return Object.keys(sub).every((ele) => {
|
|
1310
|
+
if (sub[ele] !== null && typeof sub[ele] == "object") return isSubset(sup[ele], sub[ele]);
|
|
1311
|
+
return sub[ele] === sup[ele];
|
|
1312
|
+
});
|
|
1313
|
+
};
|
|
1314
|
+
const assertFails = (message) => {
|
|
1315
|
+
throw new AssertionError(message ?? "That should not ever happened, right?");
|
|
1316
|
+
};
|
|
1317
|
+
const assertThrowsAsync = async (fun, errorCheck) => {
|
|
1318
|
+
try {
|
|
1319
|
+
await fun();
|
|
1320
|
+
} catch (error) {
|
|
1321
|
+
const typedError = error;
|
|
1322
|
+
if (typedError instanceof AssertionError || !errorCheck) {
|
|
1323
|
+
assertFalse(typedError instanceof AssertionError, "Function didn't throw expected error");
|
|
1324
|
+
return typedError;
|
|
1325
|
+
}
|
|
1326
|
+
assertTrue(errorCheck(typedError), `Error doesn't match the expected condition: ${JSONSerializer.serialize(error)}`);
|
|
1327
|
+
return typedError;
|
|
1328
|
+
}
|
|
1329
|
+
throw new AssertionError("Function didn't throw expected error");
|
|
1330
|
+
};
|
|
1331
|
+
const assertThrows = (fun, errorCheck) => {
|
|
1332
|
+
try {
|
|
1333
|
+
fun();
|
|
1334
|
+
} catch (error) {
|
|
1335
|
+
const typedError = error;
|
|
1336
|
+
if (errorCheck) assertTrue(errorCheck(typedError), `Error doesn't match the expected condition: ${JSONSerializer.serialize(error)}`);
|
|
1337
|
+
else if (typedError instanceof AssertionError) assertFalse(typedError instanceof AssertionError, "Function didn't throw expected error");
|
|
1338
|
+
return typedError;
|
|
1339
|
+
}
|
|
1340
|
+
throw new AssertionError("Function didn't throw expected error");
|
|
1341
|
+
};
|
|
1342
|
+
const assertDoesNotThrow = (fun, errorCheck) => {
|
|
1343
|
+
try {
|
|
1344
|
+
fun();
|
|
1345
|
+
return null;
|
|
1346
|
+
} catch (error) {
|
|
1347
|
+
const typedError = error;
|
|
1348
|
+
if (errorCheck) assertFalse(errorCheck(typedError), `Error matching the expected condition was thrown!: ${JSONSerializer.serialize(error)}`);
|
|
1349
|
+
else assertFails(`Function threw an error: ${JSONSerializer.serialize(error)}`);
|
|
1350
|
+
return typedError;
|
|
1351
|
+
}
|
|
1352
|
+
};
|
|
1353
|
+
const assertRejects = async (promise, errorCheck) => {
|
|
1354
|
+
try {
|
|
1355
|
+
await promise;
|
|
1356
|
+
throw new AssertionError("Function didn't throw expected error");
|
|
1357
|
+
} catch (error) {
|
|
1358
|
+
if (!errorCheck) return;
|
|
1359
|
+
if (errorCheck instanceof Error) assertDeepEqual(error, errorCheck);
|
|
1360
|
+
else assertTrue(errorCheck(error));
|
|
1361
|
+
}
|
|
1362
|
+
};
|
|
1363
|
+
const assertMatches = (actual, expected, message) => {
|
|
1364
|
+
if (!isSubset(actual, expected)) throw new AssertionError(message ?? `subObj:\n${JSONSerializer.serialize(expected)}\nis not subset of\n${JSONSerializer.serialize(actual)}`);
|
|
1365
|
+
};
|
|
1366
|
+
const assertDeepEqual = (actual, expected, message) => {
|
|
1367
|
+
if (!deepEquals(actual, expected)) throw new AssertionError(message ?? `subObj:\n${JSONSerializer.serialize(expected)}\nis not equal to\n${JSONSerializer.serialize(actual)}`);
|
|
1368
|
+
};
|
|
1369
|
+
const assertNotDeepEqual = (actual, expected, message) => {
|
|
1370
|
+
if (deepEquals(actual, expected)) throw new AssertionError(message ?? `subObj:\n${JSONSerializer.serialize(expected)}\nis equals to\n${JSONSerializer.serialize(actual)}`);
|
|
1371
|
+
};
|
|
1372
|
+
const assertThat = (item) => {
|
|
1373
|
+
return { isEqualTo: (other) => assertTrue(deepEquals(item, other)) };
|
|
1374
|
+
};
|
|
1375
|
+
const assertDefined = (value, message) => {
|
|
1376
|
+
assertOk(value, message instanceof Error ? message.message : message);
|
|
1377
|
+
};
|
|
1378
|
+
function assertFalse(condition, message) {
|
|
1379
|
+
if (condition !== false) throw new AssertionError(message ?? `Condition is true`);
|
|
1644
1380
|
}
|
|
1645
|
-
function assertTrue(condition,
|
|
1646
|
-
|
|
1647
|
-
throw new AssertionError(message2 ?? `Condition is false`);
|
|
1381
|
+
function assertTrue(condition, message) {
|
|
1382
|
+
if (condition !== true) throw new AssertionError(message ?? `Condition is false`);
|
|
1648
1383
|
}
|
|
1649
|
-
function assertOk(obj,
|
|
1650
|
-
|
|
1384
|
+
function assertOk(obj, message) {
|
|
1385
|
+
if (!obj) throw new AssertionError(message ?? `Condition is not truthy`);
|
|
1651
1386
|
}
|
|
1652
|
-
function assertEqual(expected, actual,
|
|
1653
|
-
|
|
1654
|
-
throw new AssertionError(
|
|
1655
|
-
`${message2 ?? "Objects are not equal"}:
|
|
1656
|
-
Expected: ${JSONSerializer.serialize(expected)}
|
|
1657
|
-
Actual: ${JSONSerializer.serialize(actual)}`
|
|
1658
|
-
);
|
|
1387
|
+
function assertEqual(expected, actual, message) {
|
|
1388
|
+
if (expected !== actual) throw new AssertionError(`${message ?? "Objects are not equal"}:\nExpected: ${JSONSerializer.serialize(expected)}\nActual: ${JSONSerializer.serialize(actual)}`);
|
|
1659
1389
|
}
|
|
1660
|
-
function assertNotEqual(obj, other,
|
|
1661
|
-
|
|
1662
|
-
throw new AssertionError(
|
|
1663
|
-
message2 ?? `Objects are equal: ${JSONSerializer.serialize(obj)}`
|
|
1664
|
-
);
|
|
1390
|
+
function assertNotEqual(obj, other, message) {
|
|
1391
|
+
if (obj === other) throw new AssertionError(message ?? `Objects are equal: ${JSONSerializer.serialize(obj)}`);
|
|
1665
1392
|
}
|
|
1666
1393
|
function assertIsNotNull(result) {
|
|
1667
|
-
|
|
1668
|
-
|
|
1394
|
+
assertNotEqual(result, null);
|
|
1395
|
+
assertOk(result);
|
|
1669
1396
|
}
|
|
1670
1397
|
function assertIsNull(result) {
|
|
1671
|
-
|
|
1398
|
+
assertEqual(result, null);
|
|
1672
1399
|
}
|
|
1673
|
-
|
|
1674
|
-
|
|
1400
|
+
const argValue = (value) => (arg) => deepEquals(arg, value);
|
|
1401
|
+
const argMatches = (matches) => (arg) => matches(arg);
|
|
1675
1402
|
function verifyThat(fn) {
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
fn.mock?.calls.length !== void 0 && fn.mock.calls.length >= 1
|
|
1701
|
-
);
|
|
1702
|
-
assertTrue(
|
|
1703
|
-
fn.mock?.calls.length !== void 0 && fn.mock.calls.length >= 1 && fn.mock.calls.some(
|
|
1704
|
-
(call) => call.arguments && call.arguments.length >= matches.length && matches.every((match, index) => match(call.arguments[index]))
|
|
1705
|
-
)
|
|
1706
|
-
);
|
|
1707
|
-
},
|
|
1708
|
-
notCalledWithArgumentMatching: (...matches) => {
|
|
1709
|
-
assertFalse(
|
|
1710
|
-
fn.mock?.calls.length !== void 0 && fn.mock.calls.length >= 1 && fn.mock.calls[0].arguments && fn.mock.calls[0].arguments.length >= matches.length && matches.every(
|
|
1711
|
-
(match, index) => match(fn.mock.calls[0].arguments[index])
|
|
1712
|
-
)
|
|
1713
|
-
);
|
|
1714
|
-
}
|
|
1715
|
-
};
|
|
1403
|
+
return {
|
|
1404
|
+
calledTimes: (times) => {
|
|
1405
|
+
assertEqual(fn.mock?.calls?.length, times);
|
|
1406
|
+
},
|
|
1407
|
+
notCalled: () => {
|
|
1408
|
+
assertEqual(fn?.mock?.calls?.length, 0);
|
|
1409
|
+
},
|
|
1410
|
+
called: () => {
|
|
1411
|
+
assertTrue(fn.mock?.calls.length !== void 0 && fn.mock.calls.length > 0);
|
|
1412
|
+
},
|
|
1413
|
+
calledWith: (...args) => {
|
|
1414
|
+
assertTrue(fn.mock?.calls.length !== void 0 && fn.mock.calls.length >= 1 && fn.mock.calls.some((call) => deepEquals(call.arguments, args)));
|
|
1415
|
+
},
|
|
1416
|
+
calledOnceWith: (...args) => {
|
|
1417
|
+
assertTrue(fn.mock?.calls.length !== void 0 && fn.mock.calls.length === 1 && fn.mock.calls.some((call) => deepEquals(call.arguments, args)));
|
|
1418
|
+
},
|
|
1419
|
+
calledWithArgumentMatching: (...matches) => {
|
|
1420
|
+
assertTrue(fn.mock?.calls.length !== void 0 && fn.mock.calls.length >= 1);
|
|
1421
|
+
assertTrue(fn.mock?.calls.length !== void 0 && fn.mock.calls.length >= 1 && fn.mock.calls.some((call) => call.arguments && call.arguments.length >= matches.length && matches.every((match, index) => match(call.arguments[index]))));
|
|
1422
|
+
},
|
|
1423
|
+
notCalledWithArgumentMatching: (...matches) => {
|
|
1424
|
+
assertFalse(fn.mock?.calls.length !== void 0 && fn.mock.calls.length >= 1 && fn.mock.calls[0].arguments && fn.mock.calls[0].arguments.length >= matches.length && matches.every((match, index) => match(fn.mock.calls[0].arguments[index])));
|
|
1425
|
+
}
|
|
1426
|
+
};
|
|
1716
1427
|
}
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
},
|
|
1768
|
-
anyMatches: (matches) => {
|
|
1769
|
-
assertTrue(array.some(matches));
|
|
1770
|
-
},
|
|
1771
|
-
allMatchAsync: async (matches) => {
|
|
1772
|
-
for (const item of array) {
|
|
1773
|
-
assertTrue(await matches(item));
|
|
1774
|
-
}
|
|
1775
|
-
}
|
|
1776
|
-
};
|
|
1428
|
+
const assertThatArray = (array) => {
|
|
1429
|
+
return {
|
|
1430
|
+
isEmpty: () => assertEqual(array.length, 0, `Array is not empty ${JSONSerializer.serialize(array)}`),
|
|
1431
|
+
isNotEmpty: () => assertNotEqual(array.length, 0, `Array is empty`),
|
|
1432
|
+
hasSize: (length) => assertEqual(array.length, length),
|
|
1433
|
+
containsElements: (other) => {
|
|
1434
|
+
assertTrue(other.every((ts) => array.some((o) => deepEquals(ts, o))));
|
|
1435
|
+
},
|
|
1436
|
+
containsElementsMatching: (other) => {
|
|
1437
|
+
assertTrue(other.every((ts) => array.some((o) => isSubset(o, ts))));
|
|
1438
|
+
},
|
|
1439
|
+
containsOnlyElementsMatching: (other) => {
|
|
1440
|
+
assertEqual(array.length, other.length, `Arrays lengths don't match`);
|
|
1441
|
+
assertTrue(other.every((ts) => array.some((o) => isSubset(o, ts))));
|
|
1442
|
+
},
|
|
1443
|
+
containsExactlyInAnyOrder: (other) => {
|
|
1444
|
+
assertEqual(array.length, other.length);
|
|
1445
|
+
assertTrue(array.every((ts) => other.some((o) => deepEquals(ts, o))));
|
|
1446
|
+
},
|
|
1447
|
+
containsExactlyInAnyOrderElementsOf: (other) => {
|
|
1448
|
+
assertEqual(array.length, other.length);
|
|
1449
|
+
assertTrue(array.every((ts) => other.some((o) => deepEquals(ts, o))));
|
|
1450
|
+
},
|
|
1451
|
+
containsExactlyElementsOf: (other) => {
|
|
1452
|
+
assertEqual(array.length, other.length);
|
|
1453
|
+
for (let i = 0; i < array.length; i++) assertTrue(deepEquals(array[i], other[i]));
|
|
1454
|
+
},
|
|
1455
|
+
containsExactly: (elem) => {
|
|
1456
|
+
assertEqual(array.length, 1);
|
|
1457
|
+
assertTrue(deepEquals(array[0], elem));
|
|
1458
|
+
},
|
|
1459
|
+
contains: (elem) => {
|
|
1460
|
+
assertTrue(array.some((a) => deepEquals(a, elem)));
|
|
1461
|
+
},
|
|
1462
|
+
containsOnlyOnceElementsOf: (other) => {
|
|
1463
|
+
assertTrue(other.map((o) => array.filter((a) => deepEquals(a, o)).length).filter((a) => a === 1).length === other.length);
|
|
1464
|
+
},
|
|
1465
|
+
containsAnyOf: (other) => {
|
|
1466
|
+
assertTrue(array.some((a) => other.some((o) => deepEquals(a, o))));
|
|
1467
|
+
},
|
|
1468
|
+
allMatch: (matches) => {
|
|
1469
|
+
assertTrue(array.every(matches));
|
|
1470
|
+
},
|
|
1471
|
+
anyMatches: (matches) => {
|
|
1472
|
+
assertTrue(array.some(matches));
|
|
1473
|
+
},
|
|
1474
|
+
allMatchAsync: async (matches) => {
|
|
1475
|
+
for (const item of array) assertTrue(await matches(item));
|
|
1476
|
+
}
|
|
1477
|
+
};
|
|
1777
1478
|
};
|
|
1778
1479
|
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
};
|
|
1480
|
+
//#endregion
|
|
1481
|
+
//#region src/testing/deciderSpecification.ts
|
|
1482
|
+
const DeciderSpecification = { for: deciderSpecificationFor };
|
|
1783
1483
|
function deciderSpecificationFor(decider) {
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
"Handler did not fail as expected"
|
|
1822
|
-
);
|
|
1823
|
-
}).catch((error) => {
|
|
1824
|
-
thenThrowsErrorHandler(error, args);
|
|
1825
|
-
});
|
|
1826
|
-
}
|
|
1827
|
-
throw new AssertionError("Handler did not fail as expected");
|
|
1828
|
-
} catch (error) {
|
|
1829
|
-
thenThrowsErrorHandler(error, args);
|
|
1830
|
-
}
|
|
1831
|
-
}
|
|
1832
|
-
};
|
|
1833
|
-
}
|
|
1834
|
-
};
|
|
1835
|
-
};
|
|
1836
|
-
}
|
|
1484
|
+
return (givenEvents) => {
|
|
1485
|
+
return { when: (command) => {
|
|
1486
|
+
const handle = () => {
|
|
1487
|
+
const currentState = (Array.isArray(givenEvents) ? givenEvents : [givenEvents]).reduce(decider.evolve, decider.initialState());
|
|
1488
|
+
return decider.decide(command, currentState);
|
|
1489
|
+
};
|
|
1490
|
+
return {
|
|
1491
|
+
then: (expectedEvents) => {
|
|
1492
|
+
const resultEvents = handle();
|
|
1493
|
+
if (resultEvents instanceof Promise) return resultEvents.then((events) => {
|
|
1494
|
+
thenHandler$1(events, expectedEvents);
|
|
1495
|
+
});
|
|
1496
|
+
thenHandler$1(resultEvents, expectedEvents);
|
|
1497
|
+
},
|
|
1498
|
+
thenNothingHappened: () => {
|
|
1499
|
+
const resultEvents = handle();
|
|
1500
|
+
if (resultEvents instanceof Promise) return resultEvents.then((events) => {
|
|
1501
|
+
thenNothingHappensHandler$1(events);
|
|
1502
|
+
});
|
|
1503
|
+
thenNothingHappensHandler$1(resultEvents);
|
|
1504
|
+
},
|
|
1505
|
+
thenThrows: (...args) => {
|
|
1506
|
+
try {
|
|
1507
|
+
const result = handle();
|
|
1508
|
+
if (result instanceof Promise) return result.then(() => {
|
|
1509
|
+
throw new AssertionError("Handler did not fail as expected");
|
|
1510
|
+
}).catch((error) => {
|
|
1511
|
+
thenThrowsErrorHandler$1(error, args);
|
|
1512
|
+
});
|
|
1513
|
+
throw new AssertionError("Handler did not fail as expected");
|
|
1514
|
+
} catch (error) {
|
|
1515
|
+
thenThrowsErrorHandler$1(error, args);
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
};
|
|
1519
|
+
} };
|
|
1520
|
+
};
|
|
1837
1521
|
}
|
|
1838
|
-
function thenHandler(events, expectedEvents) {
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
expectedEventsArray
|
|
1843
|
-
);
|
|
1522
|
+
function thenHandler$1(events, expectedEvents) {
|
|
1523
|
+
const resultEventsArray = Array.isArray(events) ? events : [events];
|
|
1524
|
+
const expectedEventsArray = Array.isArray(expectedEvents) ? expectedEvents : [expectedEvents];
|
|
1525
|
+
assertThatArray(resultEventsArray).containsOnlyElementsMatching(expectedEventsArray);
|
|
1844
1526
|
}
|
|
1845
|
-
function thenNothingHappensHandler(events) {
|
|
1846
|
-
|
|
1847
|
-
assertThatArray(resultEventsArray).isEmpty();
|
|
1527
|
+
function thenNothingHappensHandler$1(events) {
|
|
1528
|
+
assertThatArray(Array.isArray(events) ? events : [events]).isEmpty();
|
|
1848
1529
|
}
|
|
1849
|
-
function thenThrowsErrorHandler(error, args) {
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
}
|
|
1859
|
-
assertTrue(
|
|
1860
|
-
error instanceof args[0],
|
|
1861
|
-
`Caught error is not an instance of the expected type: ${error?.toString()}`
|
|
1862
|
-
);
|
|
1863
|
-
if (args[1]) {
|
|
1864
|
-
assertTrue(
|
|
1865
|
-
args[1](error),
|
|
1866
|
-
`Error didn't match the error condition: ${error?.toString()}`
|
|
1867
|
-
);
|
|
1868
|
-
}
|
|
1530
|
+
function thenThrowsErrorHandler$1(error, args) {
|
|
1531
|
+
if (error instanceof AssertionError) throw error;
|
|
1532
|
+
if (args.length === 0) return;
|
|
1533
|
+
if (!isErrorConstructor(args[0])) {
|
|
1534
|
+
assertTrue(args[0](error), `Error didn't match the error condition: ${error?.toString()}`);
|
|
1535
|
+
return;
|
|
1536
|
+
}
|
|
1537
|
+
assertTrue(error instanceof args[0], `Caught error is not an instance of the expected type: ${error?.toString()}`);
|
|
1538
|
+
if (args[1]) assertTrue(args[1](error), `Error didn't match the error condition: ${error?.toString()}`);
|
|
1869
1539
|
}
|
|
1870
1540
|
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
};
|
|
1541
|
+
//#endregion
|
|
1542
|
+
//#region src/testing/workflowSpecification.ts
|
|
1543
|
+
const WorkflowSpecification = { for: workflowSpecificationFor };
|
|
1875
1544
|
function workflowSpecificationFor(workflow) {
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
} catch (error) {
|
|
1901
|
-
thenThrowsErrorHandler2(error, args);
|
|
1902
|
-
}
|
|
1903
|
-
}
|
|
1904
|
-
};
|
|
1905
|
-
}
|
|
1906
|
-
};
|
|
1907
|
-
};
|
|
1545
|
+
return (givenEvents) => {
|
|
1546
|
+
return { when: (input) => {
|
|
1547
|
+
const handle = () => {
|
|
1548
|
+
const currentState = (Array.isArray(givenEvents) ? givenEvents : [givenEvents]).reduce(workflow.evolve, workflow.initialState());
|
|
1549
|
+
return workflow.decide(input, currentState);
|
|
1550
|
+
};
|
|
1551
|
+
return {
|
|
1552
|
+
then: (expectedOutput) => {
|
|
1553
|
+
thenHandler(handle(), expectedOutput);
|
|
1554
|
+
},
|
|
1555
|
+
thenNothingHappened: () => {
|
|
1556
|
+
thenNothingHappensHandler(handle());
|
|
1557
|
+
},
|
|
1558
|
+
thenThrows: (...args) => {
|
|
1559
|
+
try {
|
|
1560
|
+
handle();
|
|
1561
|
+
throw new AssertionError("Handler did not fail as expected");
|
|
1562
|
+
} catch (error) {
|
|
1563
|
+
thenThrowsErrorHandler(error, args);
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
};
|
|
1567
|
+
} };
|
|
1568
|
+
};
|
|
1908
1569
|
}
|
|
1909
|
-
function
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1570
|
+
function thenHandler(result, expectedOutput) {
|
|
1571
|
+
const resultArray = Array.isArray(result) ? result : [result];
|
|
1572
|
+
const expectedArray = Array.isArray(expectedOutput) ? expectedOutput : [expectedOutput];
|
|
1573
|
+
assertThatArray(resultArray).containsOnlyElementsMatching(expectedArray);
|
|
1913
1574
|
}
|
|
1914
|
-
function
|
|
1915
|
-
|
|
1916
|
-
assertThatArray(resultArray).isEmpty();
|
|
1575
|
+
function thenNothingHappensHandler(result) {
|
|
1576
|
+
assertThatArray(Array.isArray(result) ? result : [result]).isEmpty();
|
|
1917
1577
|
}
|
|
1918
|
-
function
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
}
|
|
1928
|
-
assertTrue(
|
|
1929
|
-
error instanceof args[0],
|
|
1930
|
-
`Caught error is not an instance of the expected type: ${error?.toString()}`
|
|
1931
|
-
);
|
|
1932
|
-
if (args[1]) {
|
|
1933
|
-
assertTrue(
|
|
1934
|
-
args[1](error),
|
|
1935
|
-
`Error didn't match the error condition: ${error?.toString()}`
|
|
1936
|
-
);
|
|
1937
|
-
}
|
|
1578
|
+
function thenThrowsErrorHandler(error, args) {
|
|
1579
|
+
if (error instanceof AssertionError) throw error;
|
|
1580
|
+
if (args.length === 0) return;
|
|
1581
|
+
if (!isErrorConstructor(args[0])) {
|
|
1582
|
+
assertTrue(args[0](error), `Error didn't match the error condition: ${error?.toString()}`);
|
|
1583
|
+
return;
|
|
1584
|
+
}
|
|
1585
|
+
assertTrue(error instanceof args[0], `Caught error is not an instance of the expected type: ${error?.toString()}`);
|
|
1586
|
+
if (args[1]) assertTrue(args[1](error), `Error didn't match the error condition: ${error?.toString()}`);
|
|
1938
1587
|
}
|
|
1939
1588
|
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
[...currentStream[1], ...events]
|
|
1964
|
-
]);
|
|
1965
|
-
return result;
|
|
1966
|
-
},
|
|
1967
|
-
appendedEvents,
|
|
1968
|
-
setup: async (streamName, events) => {
|
|
1969
|
-
return eventStore.appendToStream(streamName, events);
|
|
1970
|
-
}
|
|
1971
|
-
// streamEvents: (): ReadableStream<
|
|
1972
|
-
// // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
|
|
1973
|
-
// ReadEvent<Event, ReadEventMetadataType> | GlobalSubscriptionEvent
|
|
1974
|
-
// > => {
|
|
1975
|
-
// return eventStore.streamEvents();
|
|
1976
|
-
// },
|
|
1977
|
-
};
|
|
1978
|
-
return wrapped;
|
|
1589
|
+
//#endregion
|
|
1590
|
+
//#region src/testing/wrapEventStore.ts
|
|
1591
|
+
const WrapEventStore = (eventStore) => {
|
|
1592
|
+
const appendedEvents = /* @__PURE__ */ new Map();
|
|
1593
|
+
return {
|
|
1594
|
+
...eventStore,
|
|
1595
|
+
aggregateStream(streamName, options) {
|
|
1596
|
+
return eventStore.aggregateStream(streamName, options);
|
|
1597
|
+
},
|
|
1598
|
+
async readStream(streamName, options) {
|
|
1599
|
+
return await eventStore.readStream(streamName, options);
|
|
1600
|
+
},
|
|
1601
|
+
appendToStream: async (streamName, events, options) => {
|
|
1602
|
+
const result = await eventStore.appendToStream(streamName, events, options);
|
|
1603
|
+
const currentStream = appendedEvents.get(streamName) ?? [streamName, []];
|
|
1604
|
+
appendedEvents.set(streamName, [streamName, [...currentStream[1], ...events]]);
|
|
1605
|
+
return result;
|
|
1606
|
+
},
|
|
1607
|
+
appendedEvents,
|
|
1608
|
+
setup: async (streamName, events) => {
|
|
1609
|
+
return eventStore.appendToStream(streamName, events);
|
|
1610
|
+
}
|
|
1611
|
+
};
|
|
1979
1612
|
};
|
|
1980
1613
|
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
}
|
|
2081
|
-
}
|
|
2082
|
-
}
|
|
2083
|
-
};
|
|
2084
|
-
}
|
|
2085
|
-
};
|
|
2086
|
-
};
|
|
2087
|
-
}
|
|
2088
|
-
};
|
|
2089
|
-
var eventInStream = (streamName, event2) => {
|
|
2090
|
-
return {
|
|
2091
|
-
...event2,
|
|
2092
|
-
metadata: {
|
|
2093
|
-
...event2.metadata ?? {},
|
|
2094
|
-
streamName: event2.metadata?.streamName ?? streamName
|
|
2095
|
-
}
|
|
2096
|
-
};
|
|
2097
|
-
};
|
|
2098
|
-
var eventsInStream = (streamName, events) => {
|
|
2099
|
-
return events.map((e) => eventInStream(streamName, e));
|
|
2100
|
-
};
|
|
2101
|
-
var newEventsInStream = eventsInStream;
|
|
1614
|
+
//#endregion
|
|
1615
|
+
//#region src/eventStore/projections/inMemory/inMemoryProjectionSpec.ts
|
|
1616
|
+
const InMemoryProjectionSpec = { for: (options) => {
|
|
1617
|
+
const { projection } = options;
|
|
1618
|
+
return (givenEvents) => {
|
|
1619
|
+
return { when: (events, options) => {
|
|
1620
|
+
const allEvents = [];
|
|
1621
|
+
const run = async (database) => {
|
|
1622
|
+
let globalPosition = 0n;
|
|
1623
|
+
const numberOfTimes = options?.numberOfTimes ?? 1;
|
|
1624
|
+
for (const event of [...givenEvents, ...Array.from({ length: numberOfTimes }).flatMap(() => events)]) {
|
|
1625
|
+
const metadata = {
|
|
1626
|
+
checkpoint: bigIntProcessorCheckpoint(++globalPosition),
|
|
1627
|
+
globalPosition,
|
|
1628
|
+
streamPosition: globalPosition,
|
|
1629
|
+
streamName: event.metadata?.streamName ?? `test-${v4()}`,
|
|
1630
|
+
messageId: v4()
|
|
1631
|
+
};
|
|
1632
|
+
allEvents.push({
|
|
1633
|
+
...event,
|
|
1634
|
+
kind: "Event",
|
|
1635
|
+
metadata: {
|
|
1636
|
+
...metadata,
|
|
1637
|
+
..."metadata" in event ? event.metadata ?? {} : {}
|
|
1638
|
+
}
|
|
1639
|
+
});
|
|
1640
|
+
}
|
|
1641
|
+
await handleInMemoryProjections({
|
|
1642
|
+
events: allEvents,
|
|
1643
|
+
projections: [projection],
|
|
1644
|
+
database,
|
|
1645
|
+
eventStore: {
|
|
1646
|
+
database,
|
|
1647
|
+
aggregateStream: async () => {
|
|
1648
|
+
return Promise.resolve({
|
|
1649
|
+
state: {},
|
|
1650
|
+
currentStreamVersion: 0n,
|
|
1651
|
+
streamExists: false
|
|
1652
|
+
});
|
|
1653
|
+
},
|
|
1654
|
+
readStream: async () => {
|
|
1655
|
+
return Promise.resolve({
|
|
1656
|
+
events: [],
|
|
1657
|
+
currentStreamVersion: 0n,
|
|
1658
|
+
streamExists: false
|
|
1659
|
+
});
|
|
1660
|
+
},
|
|
1661
|
+
appendToStream: async () => {
|
|
1662
|
+
return Promise.resolve({
|
|
1663
|
+
nextExpectedStreamVersion: 0n,
|
|
1664
|
+
createdNewStream: false
|
|
1665
|
+
});
|
|
1666
|
+
},
|
|
1667
|
+
streamExists: async () => {
|
|
1668
|
+
return Promise.resolve(false);
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
});
|
|
1672
|
+
};
|
|
1673
|
+
return {
|
|
1674
|
+
then: async (assertFn, message) => {
|
|
1675
|
+
const database = getInMemoryDatabase();
|
|
1676
|
+
await run(database);
|
|
1677
|
+
const succeeded = await assertFn({ database });
|
|
1678
|
+
if (succeeded !== void 0 && succeeded === false) assertFails(message ?? "Projection specification didn't match the criteria");
|
|
1679
|
+
},
|
|
1680
|
+
thenThrows: async (...args) => {
|
|
1681
|
+
const database = getInMemoryDatabase();
|
|
1682
|
+
try {
|
|
1683
|
+
await run(database);
|
|
1684
|
+
throw new AssertionError("Handler did not fail as expected");
|
|
1685
|
+
} catch (error) {
|
|
1686
|
+
if (error instanceof AssertionError) throw error;
|
|
1687
|
+
if (args.length === 0) return;
|
|
1688
|
+
if (!isErrorConstructor(args[0])) {
|
|
1689
|
+
assertTrue(args[0](error), `Error didn't match the error condition: ${error?.toString()}`);
|
|
1690
|
+
return;
|
|
1691
|
+
}
|
|
1692
|
+
assertTrue(error instanceof args[0], `Caught error is not an instance of the expected type: ${error?.toString()}`);
|
|
1693
|
+
if (args[1]) assertTrue(args[1](error), `Error didn't match the error condition: ${error?.toString()}`);
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
};
|
|
1697
|
+
} };
|
|
1698
|
+
};
|
|
1699
|
+
} };
|
|
1700
|
+
const eventInStream = (streamName, event) => {
|
|
1701
|
+
return {
|
|
1702
|
+
...event,
|
|
1703
|
+
metadata: {
|
|
1704
|
+
...event.metadata ?? {},
|
|
1705
|
+
streamName: event.metadata?.streamName ?? streamName
|
|
1706
|
+
}
|
|
1707
|
+
};
|
|
1708
|
+
};
|
|
1709
|
+
const eventsInStream = (streamName, events) => {
|
|
1710
|
+
return events.map((e) => eventInStream(streamName, e));
|
|
1711
|
+
};
|
|
1712
|
+
const newEventsInStream = eventsInStream;
|
|
2102
1713
|
function documentExists(expected, options) {
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
}
|
|
2121
|
-
}
|
|
2122
|
-
return Promise.resolve(true);
|
|
2123
|
-
};
|
|
1714
|
+
return async ({ database }) => {
|
|
1715
|
+
const document = await database.collection(options.inCollection).findOne((doc) => {
|
|
1716
|
+
return ("_id" in doc ? doc._id : void 0) === options.withId;
|
|
1717
|
+
});
|
|
1718
|
+
if (!document) {
|
|
1719
|
+
assertFails(`Document with ID ${options.withId} does not exist in collection ${options.inCollection}`);
|
|
1720
|
+
return Promise.resolve(false);
|
|
1721
|
+
}
|
|
1722
|
+
for (const [key, value] of Object.entries(expected)) {
|
|
1723
|
+
const propKey = key;
|
|
1724
|
+
if (!(key in document) || JSONSerializer.serialize(document[propKey]) !== JSONSerializer.serialize(value)) {
|
|
1725
|
+
assertFails(`Property ${key} doesn't match the expected value`);
|
|
1726
|
+
return Promise.resolve(false);
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
return Promise.resolve(true);
|
|
1730
|
+
};
|
|
2124
1731
|
}
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
inCollection: collectionName,
|
|
2130
|
-
withId: id
|
|
2131
|
-
})
|
|
2132
|
-
})
|
|
2133
|
-
})
|
|
2134
|
-
};
|
|
1732
|
+
const expectInMemoryDocuments = { fromCollection: (collectionName) => ({ withId: (id) => ({ toBeEqual: (expected) => documentExists(expected, {
|
|
1733
|
+
inCollection: collectionName,
|
|
1734
|
+
withId: id
|
|
1735
|
+
}) }) }) };
|
|
2135
1736
|
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
};
|
|
2154
|
-
};
|
|
2155
|
-
var downcastRecordedMessages = (recordedMessages, options) => {
|
|
2156
|
-
if (!options?.downcast)
|
|
2157
|
-
return recordedMessages;
|
|
2158
|
-
return recordedMessages.map(
|
|
2159
|
-
(recordedMessage) => downcastRecordedMessage(recordedMessage, options)
|
|
2160
|
-
);
|
|
1737
|
+
//#endregion
|
|
1738
|
+
//#region src/eventStore/versioning/downcasting.ts
|
|
1739
|
+
const downcastRecordedMessage = (recordedMessage, options) => {
|
|
1740
|
+
if (!options?.downcast) return recordedMessage;
|
|
1741
|
+
const downcasted = options.downcast(recordedMessage);
|
|
1742
|
+
return {
|
|
1743
|
+
...recordedMessage,
|
|
1744
|
+
data: downcasted.data,
|
|
1745
|
+
..."metadata" in recordedMessage || "metadata" in downcasted ? { metadata: {
|
|
1746
|
+
..."metadata" in recordedMessage ? recordedMessage.metadata : {},
|
|
1747
|
+
..."metadata" in downcasted ? downcasted.metadata : {}
|
|
1748
|
+
} } : {}
|
|
1749
|
+
};
|
|
1750
|
+
};
|
|
1751
|
+
const downcastRecordedMessages = (recordedMessages, options) => {
|
|
1752
|
+
if (!options?.downcast) return recordedMessages;
|
|
1753
|
+
return recordedMessages.map((recordedMessage) => downcastRecordedMessage(recordedMessage, options));
|
|
2161
1754
|
};
|
|
2162
1755
|
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
};
|
|
2181
|
-
};
|
|
2182
|
-
var upcastRecordedMessages = (recordedMessages, options) => {
|
|
2183
|
-
if (!options?.upcast)
|
|
2184
|
-
return recordedMessages;
|
|
2185
|
-
return recordedMessages.map(
|
|
2186
|
-
(recordedMessage) => upcastRecordedMessage(recordedMessage, options)
|
|
2187
|
-
);
|
|
1756
|
+
//#endregion
|
|
1757
|
+
//#region src/eventStore/versioning/upcasting.ts
|
|
1758
|
+
const upcastRecordedMessage = (recordedMessage, options) => {
|
|
1759
|
+
if (!options?.upcast) return recordedMessage;
|
|
1760
|
+
const upcasted = options.upcast(recordedMessage);
|
|
1761
|
+
return {
|
|
1762
|
+
...recordedMessage,
|
|
1763
|
+
data: upcasted.data,
|
|
1764
|
+
..."metadata" in recordedMessage || "metadata" in upcasted ? { metadata: {
|
|
1765
|
+
..."metadata" in recordedMessage ? recordedMessage.metadata : {},
|
|
1766
|
+
..."metadata" in upcasted ? upcasted.metadata : {}
|
|
1767
|
+
} } : {}
|
|
1768
|
+
};
|
|
1769
|
+
};
|
|
1770
|
+
const upcastRecordedMessages = (recordedMessages, options) => {
|
|
1771
|
+
if (!options?.upcast) return recordedMessages;
|
|
1772
|
+
return recordedMessages.map((recordedMessage) => upcastRecordedMessage(recordedMessage, options));
|
|
2188
1773
|
};
|
|
2189
1774
|
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
...downcastRecordedMessages(newEvents, options?.schema?.versioning)
|
|
2270
|
-
]);
|
|
2271
|
-
if (inlineProjections2.length > 0) {
|
|
2272
|
-
await handleInMemoryProjections({
|
|
2273
|
-
projections: inlineProjections2,
|
|
2274
|
-
events: newEvents,
|
|
2275
|
-
database: eventStore.database,
|
|
2276
|
-
eventStore
|
|
2277
|
-
});
|
|
2278
|
-
}
|
|
2279
|
-
const result = {
|
|
2280
|
-
nextExpectedStreamVersion: positionOfLastEventInTheStream,
|
|
2281
|
-
createdNewStream: currentStreamVersion === InMemoryEventStoreDefaultStreamVersion
|
|
2282
|
-
};
|
|
2283
|
-
await tryPublishMessagesAfterCommit(
|
|
2284
|
-
newEvents,
|
|
2285
|
-
eventStoreOptions?.hooks
|
|
2286
|
-
);
|
|
2287
|
-
return result;
|
|
2288
|
-
},
|
|
2289
|
-
streamExists: (streamName) => {
|
|
2290
|
-
const events = streams.get(streamName);
|
|
2291
|
-
return Promise.resolve(events !== void 0 && events.length > 0);
|
|
2292
|
-
}
|
|
2293
|
-
};
|
|
2294
|
-
return eventStore;
|
|
1775
|
+
//#endregion
|
|
1776
|
+
//#region src/eventStore/inMemoryEventStore.ts
|
|
1777
|
+
const InMemoryEventStoreDefaultStreamVersion = 0n;
|
|
1778
|
+
const getInMemoryEventStore = (eventStoreOptions) => {
|
|
1779
|
+
const streams = /* @__PURE__ */ new Map();
|
|
1780
|
+
const getAllEventsCount = () => {
|
|
1781
|
+
return Array.from(streams.values()).map((s) => s.length).reduce((p, c) => p + c, 0);
|
|
1782
|
+
};
|
|
1783
|
+
const database = eventStoreOptions?.database || getInMemoryDatabase();
|
|
1784
|
+
const inlineProjections = (eventStoreOptions?.projections ?? []).filter(({ type }) => type === "inline").map(({ projection }) => projection);
|
|
1785
|
+
const eventStore = {
|
|
1786
|
+
database,
|
|
1787
|
+
async aggregateStream(streamName, options) {
|
|
1788
|
+
const { evolve, initialState, read } = options;
|
|
1789
|
+
const result = await this.readStream(streamName, read);
|
|
1790
|
+
const events = result?.events ?? [];
|
|
1791
|
+
const state = events.reduce((s, e) => evolve(s, e), initialState());
|
|
1792
|
+
return {
|
|
1793
|
+
currentStreamVersion: BigInt(events.length),
|
|
1794
|
+
state,
|
|
1795
|
+
streamExists: result.streamExists
|
|
1796
|
+
};
|
|
1797
|
+
},
|
|
1798
|
+
readStream: (streamName, readOptions) => {
|
|
1799
|
+
const events = streams.get(streamName);
|
|
1800
|
+
const currentStreamVersion = events ? BigInt(events.length) : InMemoryEventStoreDefaultStreamVersion;
|
|
1801
|
+
assertExpectedVersionMatchesCurrent(currentStreamVersion, readOptions?.expectedStreamVersion, InMemoryEventStoreDefaultStreamVersion);
|
|
1802
|
+
const from = Number(readOptions?.from ?? 0);
|
|
1803
|
+
const to = Number(readOptions?.to ?? (readOptions?.maxCount ? (readOptions.from ?? 0n) + readOptions.maxCount : events?.length ?? 1));
|
|
1804
|
+
const result = {
|
|
1805
|
+
currentStreamVersion,
|
|
1806
|
+
events: events !== void 0 && events.length > 0 ? upcastRecordedMessages(events.slice(from, to), readOptions?.schema?.versioning) : [],
|
|
1807
|
+
streamExists: events !== void 0 && events.length > 0
|
|
1808
|
+
};
|
|
1809
|
+
return Promise.resolve(result);
|
|
1810
|
+
},
|
|
1811
|
+
appendToStream: async (streamName, events, options) => {
|
|
1812
|
+
const currentEvents = streams.get(streamName) ?? [];
|
|
1813
|
+
const currentStreamVersion = currentEvents.length > 0 ? BigInt(currentEvents.length) : InMemoryEventStoreDefaultStreamVersion;
|
|
1814
|
+
assertExpectedVersionMatchesCurrent(currentStreamVersion, options?.expectedStreamVersion, InMemoryEventStoreDefaultStreamVersion);
|
|
1815
|
+
const newEvents = events.map((event, index) => {
|
|
1816
|
+
const globalPosition = BigInt(getAllEventsCount() + index + 1);
|
|
1817
|
+
const metadata = {
|
|
1818
|
+
streamName,
|
|
1819
|
+
messageId: v4(),
|
|
1820
|
+
streamPosition: BigInt(currentEvents.length + index + 1),
|
|
1821
|
+
globalPosition,
|
|
1822
|
+
checkpoint: bigIntProcessorCheckpoint(globalPosition)
|
|
1823
|
+
};
|
|
1824
|
+
return {
|
|
1825
|
+
...event,
|
|
1826
|
+
kind: event.kind ?? "Event",
|
|
1827
|
+
metadata: {
|
|
1828
|
+
..."metadata" in event ? event.metadata ?? {} : {},
|
|
1829
|
+
...metadata
|
|
1830
|
+
}
|
|
1831
|
+
};
|
|
1832
|
+
});
|
|
1833
|
+
const positionOfLastEventInTheStream = BigInt(newEvents.slice(-1)[0].metadata.streamPosition);
|
|
1834
|
+
streams.set(streamName, [...currentEvents, ...downcastRecordedMessages(newEvents, options?.schema?.versioning)]);
|
|
1835
|
+
if (inlineProjections.length > 0) await handleInMemoryProjections({
|
|
1836
|
+
projections: inlineProjections,
|
|
1837
|
+
events: newEvents,
|
|
1838
|
+
database: eventStore.database,
|
|
1839
|
+
eventStore
|
|
1840
|
+
});
|
|
1841
|
+
const result = {
|
|
1842
|
+
nextExpectedStreamVersion: positionOfLastEventInTheStream,
|
|
1843
|
+
createdNewStream: currentStreamVersion === InMemoryEventStoreDefaultStreamVersion
|
|
1844
|
+
};
|
|
1845
|
+
await tryPublishMessagesAfterCommit(newEvents, eventStoreOptions?.hooks);
|
|
1846
|
+
return result;
|
|
1847
|
+
},
|
|
1848
|
+
streamExists: (streamName) => {
|
|
1849
|
+
const events = streams.get(streamName);
|
|
1850
|
+
return Promise.resolve(events !== void 0 && events.length > 0);
|
|
1851
|
+
}
|
|
1852
|
+
};
|
|
1853
|
+
return eventStore;
|
|
2295
1854
|
};
|
|
2296
1855
|
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
};
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
};
|
|
2360
|
-
}
|
|
2361
|
-
const expectedStreamVersion = handleOptions?.expectedStreamVersion ?? (aggregationResult.streamExists ? currentStreamVersion : STREAM_DOES_NOT_EXIST);
|
|
2362
|
-
const appendResult = await eventStore.appendToStream(
|
|
2363
|
-
streamName,
|
|
2364
|
-
eventsToAppend,
|
|
2365
|
-
{
|
|
2366
|
-
...handleOptions,
|
|
2367
|
-
expectedStreamVersion
|
|
2368
|
-
}
|
|
2369
|
-
);
|
|
2370
|
-
return {
|
|
2371
|
-
...appendResult,
|
|
2372
|
-
newEvents: eventsToAppend,
|
|
2373
|
-
newState: state
|
|
2374
|
-
};
|
|
2375
|
-
});
|
|
2376
|
-
return result;
|
|
2377
|
-
},
|
|
2378
|
-
fromCommandHandlerRetryOptions(
|
|
2379
|
-
handleOptions && "retry" in handleOptions ? handleOptions.retry : options.retry
|
|
2380
|
-
)
|
|
2381
|
-
);
|
|
2382
|
-
var withSession = (eventStore, callback) => {
|
|
2383
|
-
const sessionFactory = canCreateEventStoreSession(eventStore) ? eventStore : nulloSessionFactory(eventStore);
|
|
2384
|
-
return sessionFactory.withSession(callback);
|
|
1856
|
+
//#endregion
|
|
1857
|
+
//#region src/commandHandling/handleCommand.ts
|
|
1858
|
+
const CommandHandlerStreamVersionConflictRetryOptions = {
|
|
1859
|
+
retries: 3,
|
|
1860
|
+
minTimeout: 100,
|
|
1861
|
+
factor: 1.5,
|
|
1862
|
+
shouldRetryError: isExpectedVersionConflictError
|
|
1863
|
+
};
|
|
1864
|
+
const fromCommandHandlerRetryOptions = (retryOptions) => {
|
|
1865
|
+
if (retryOptions === void 0) return NoRetries;
|
|
1866
|
+
if ("onVersionConflict" in retryOptions) if (typeof retryOptions.onVersionConflict === "boolean") return CommandHandlerStreamVersionConflictRetryOptions;
|
|
1867
|
+
else if (typeof retryOptions.onVersionConflict === "number") return {
|
|
1868
|
+
...CommandHandlerStreamVersionConflictRetryOptions,
|
|
1869
|
+
retries: retryOptions.onVersionConflict
|
|
1870
|
+
};
|
|
1871
|
+
else return retryOptions.onVersionConflict;
|
|
1872
|
+
return retryOptions;
|
|
1873
|
+
};
|
|
1874
|
+
const CommandHandler = (options) => async (store, id, handle, handleOptions) => asyncRetry(async () => {
|
|
1875
|
+
return await withSession$1(store, async ({ eventStore }) => {
|
|
1876
|
+
const { evolve, initialState } = options;
|
|
1877
|
+
const streamName = (options.mapToStreamId ?? ((id) => id))(id);
|
|
1878
|
+
const aggregationResult = await eventStore.aggregateStream(streamName, {
|
|
1879
|
+
evolve,
|
|
1880
|
+
initialState,
|
|
1881
|
+
read: {
|
|
1882
|
+
schema: options.schema,
|
|
1883
|
+
...handleOptions,
|
|
1884
|
+
serialization: options.serialization,
|
|
1885
|
+
expectedStreamVersion: handleOptions?.expectedStreamVersion ?? "NO_CONCURRENCY_CHECK"
|
|
1886
|
+
}
|
|
1887
|
+
});
|
|
1888
|
+
const { currentStreamVersion, streamExists: _streamExists, ...restOfAggregationResult } = aggregationResult;
|
|
1889
|
+
let state = aggregationResult.state;
|
|
1890
|
+
const handlers = Array.isArray(handle) ? handle : [handle];
|
|
1891
|
+
let eventsToAppend = [];
|
|
1892
|
+
for (const handler of handlers) {
|
|
1893
|
+
const result = await handler(state);
|
|
1894
|
+
const newEvents = Array.isArray(result) ? result : [result];
|
|
1895
|
+
if (newEvents.length > 0) state = newEvents.reduce(evolve, state);
|
|
1896
|
+
eventsToAppend = [...eventsToAppend, ...newEvents];
|
|
1897
|
+
}
|
|
1898
|
+
if (eventsToAppend.length === 0) return {
|
|
1899
|
+
...restOfAggregationResult,
|
|
1900
|
+
newEvents: [],
|
|
1901
|
+
newState: state,
|
|
1902
|
+
nextExpectedStreamVersion: currentStreamVersion,
|
|
1903
|
+
createdNewStream: false
|
|
1904
|
+
};
|
|
1905
|
+
const expectedStreamVersion = handleOptions?.expectedStreamVersion ?? (aggregationResult.streamExists ? currentStreamVersion : "STREAM_DOES_NOT_EXIST");
|
|
1906
|
+
return {
|
|
1907
|
+
...await eventStore.appendToStream(streamName, eventsToAppend, {
|
|
1908
|
+
...handleOptions,
|
|
1909
|
+
expectedStreamVersion
|
|
1910
|
+
}),
|
|
1911
|
+
newEvents: eventsToAppend,
|
|
1912
|
+
newState: state
|
|
1913
|
+
};
|
|
1914
|
+
});
|
|
1915
|
+
}, fromCommandHandlerRetryOptions(handleOptions && "retry" in handleOptions ? handleOptions.retry : options.retry));
|
|
1916
|
+
const withSession$1 = (eventStore, callback) => {
|
|
1917
|
+
return (canCreateEventStoreSession(eventStore) ? eventStore : nulloSessionFactory(eventStore)).withSession(callback);
|
|
2385
1918
|
};
|
|
2386
1919
|
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
return CommandHandler(rest)(
|
|
2394
|
-
eventStore,
|
|
2395
|
-
id,
|
|
2396
|
-
deciders,
|
|
2397
|
-
handleOptions
|
|
2398
|
-
);
|
|
1920
|
+
//#endregion
|
|
1921
|
+
//#region src/commandHandling/handleCommandWithDecider.ts
|
|
1922
|
+
const DeciderCommandHandler = (options) => async (eventStore, id, commands, handleOptions) => {
|
|
1923
|
+
const { decide, ...rest } = options;
|
|
1924
|
+
const deciders = (Array.isArray(commands) ? commands : [commands]).map((command) => (state) => decide(command, state));
|
|
1925
|
+
return CommandHandler(rest)(eventStore, id, deciders, handleOptions);
|
|
2399
1926
|
};
|
|
2400
1927
|
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
for (const eventType of eventTypes) {
|
|
2441
|
-
if (!allHandlers.has(eventType)) allHandlers.set(eventType, []);
|
|
2442
|
-
allHandlers.set(eventType, [
|
|
2443
|
-
...allHandlers.get(eventType) ?? [],
|
|
2444
|
-
eventHandler
|
|
2445
|
-
]);
|
|
2446
|
-
}
|
|
2447
|
-
},
|
|
2448
|
-
dequeue: () => {
|
|
2449
|
-
const pending = pendingMessages;
|
|
2450
|
-
pendingMessages = [];
|
|
2451
|
-
return pending;
|
|
2452
|
-
}
|
|
2453
|
-
};
|
|
1928
|
+
//#endregion
|
|
1929
|
+
//#region src/messageBus/index.ts
|
|
1930
|
+
const getInMemoryMessageBus = () => {
|
|
1931
|
+
const allHandlers = /* @__PURE__ */ new Map();
|
|
1932
|
+
let pendingMessages = [];
|
|
1933
|
+
return {
|
|
1934
|
+
send: async (command) => {
|
|
1935
|
+
const handlers = allHandlers.get(command.type);
|
|
1936
|
+
if (handlers === void 0 || handlers.length === 0) throw new EmmettError(`No handler registered for command ${command.type}!`);
|
|
1937
|
+
const commandHandler = handlers[0];
|
|
1938
|
+
await commandHandler(command);
|
|
1939
|
+
},
|
|
1940
|
+
publish: async (event) => {
|
|
1941
|
+
const handlers = allHandlers.get(event.type) ?? [];
|
|
1942
|
+
for (const handler of handlers) await handler(event);
|
|
1943
|
+
},
|
|
1944
|
+
schedule: (message, when) => {
|
|
1945
|
+
pendingMessages = [...pendingMessages, {
|
|
1946
|
+
message,
|
|
1947
|
+
options: when
|
|
1948
|
+
}];
|
|
1949
|
+
},
|
|
1950
|
+
handle: (commandHandler, ...commandTypes) => {
|
|
1951
|
+
const alreadyRegistered = [...allHandlers.keys()].filter((registered) => commandTypes.includes(registered));
|
|
1952
|
+
if (alreadyRegistered.length > 0) throw new EmmettError(`Cannot register handler for commands ${alreadyRegistered.join(", ")} as they're already registered!`);
|
|
1953
|
+
for (const commandType of commandTypes) allHandlers.set(commandType, [commandHandler]);
|
|
1954
|
+
},
|
|
1955
|
+
subscribe(eventHandler, ...eventTypes) {
|
|
1956
|
+
for (const eventType of eventTypes) {
|
|
1957
|
+
if (!allHandlers.has(eventType)) allHandlers.set(eventType, []);
|
|
1958
|
+
allHandlers.set(eventType, [...allHandlers.get(eventType) ?? [], eventHandler]);
|
|
1959
|
+
}
|
|
1960
|
+
},
|
|
1961
|
+
dequeue: () => {
|
|
1962
|
+
const pending = pendingMessages;
|
|
1963
|
+
pendingMessages = [];
|
|
1964
|
+
return pending;
|
|
1965
|
+
}
|
|
1966
|
+
};
|
|
2454
1967
|
};
|
|
2455
1968
|
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
);
|
|
2463
|
-
if (duplicateRegistrations.length > 0) {
|
|
2464
|
-
throw new EmmettError(`You cannot register multiple projections with the same name (or without the name).
|
|
1969
|
+
//#endregion
|
|
1970
|
+
//#region src/projections/index.ts
|
|
1971
|
+
const filterProjections = (type, projections) => {
|
|
1972
|
+
const inlineProjections = projections.filter((projection) => projection.type === type).map(({ projection }) => projection);
|
|
1973
|
+
const duplicateRegistrations = arrayUtils.getDuplicates(inlineProjections, (proj) => proj.name);
|
|
1974
|
+
if (duplicateRegistrations.length > 0) throw new EmmettError(`You cannot register multiple projections with the same name (or without the name).
|
|
2465
1975
|
Ensure that:
|
|
2466
1976
|
${JSONSerializer.serialize(duplicateRegistrations)}
|
|
2467
1977
|
have different names`);
|
|
2468
|
-
|
|
2469
|
-
return inlineProjections2;
|
|
1978
|
+
return inlineProjections;
|
|
2470
1979
|
};
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
1980
|
+
const projection = (definition) => definition;
|
|
1981
|
+
const inlineProjections = (definitions) => definitions.map((definition) => ({
|
|
1982
|
+
type: "inline",
|
|
1983
|
+
projection: definition
|
|
2475
1984
|
}));
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
1985
|
+
const asyncProjections = (definitions) => definitions.map((definition) => ({
|
|
1986
|
+
type: "inline",
|
|
1987
|
+
projection: definition
|
|
2479
1988
|
}));
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
1989
|
+
const projections = {
|
|
1990
|
+
inline: inlineProjections,
|
|
1991
|
+
async: asyncProjections
|
|
2483
1992
|
};
|
|
2484
1993
|
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
};
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
var emptyHandlerResult = (nextExpectedStreamVersion = 0n) => ({
|
|
2508
|
-
newMessages: [],
|
|
2509
|
-
createdNewStream: false,
|
|
2510
|
-
nextExpectedStreamVersion
|
|
1994
|
+
//#endregion
|
|
1995
|
+
//#region src/workflows/handleWorkflow.ts
|
|
1996
|
+
const WorkflowHandlerStreamVersionConflictRetryOptions = {
|
|
1997
|
+
retries: 3,
|
|
1998
|
+
minTimeout: 100,
|
|
1999
|
+
factor: 1.5,
|
|
2000
|
+
shouldRetryError: isExpectedVersionConflictError
|
|
2001
|
+
};
|
|
2002
|
+
const fromWorkflowHandlerRetryOptions = (retryOptions) => {
|
|
2003
|
+
if (retryOptions === void 0) return NoRetries;
|
|
2004
|
+
if ("onVersionConflict" in retryOptions) if (typeof retryOptions.onVersionConflict === "boolean") return WorkflowHandlerStreamVersionConflictRetryOptions;
|
|
2005
|
+
else if (typeof retryOptions.onVersionConflict === "number") return {
|
|
2006
|
+
...WorkflowHandlerStreamVersionConflictRetryOptions,
|
|
2007
|
+
retries: retryOptions.onVersionConflict
|
|
2008
|
+
};
|
|
2009
|
+
else return retryOptions.onVersionConflict;
|
|
2010
|
+
return retryOptions;
|
|
2011
|
+
};
|
|
2012
|
+
const emptyHandlerResult = (nextExpectedStreamVersion = 0n) => ({
|
|
2013
|
+
newMessages: [],
|
|
2014
|
+
createdNewStream: false,
|
|
2015
|
+
nextExpectedStreamVersion
|
|
2511
2016
|
});
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2017
|
+
const createInputMetadata = (originalMessageId, action) => ({
|
|
2018
|
+
originalMessageId,
|
|
2019
|
+
input: true,
|
|
2020
|
+
action
|
|
2516
2021
|
});
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
};
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
};
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
}
|
|
2636
|
-
const messageForDecide = hasWorkflowPrefix ? {
|
|
2637
|
-
...messageWithMetadata,
|
|
2638
|
-
type: messageType.replace(`${workflowName}:`, "")
|
|
2639
|
-
} : messageWithMetadata;
|
|
2640
|
-
const result2 = decide(messageForDecide, state);
|
|
2641
|
-
const inputMetadata = createInputMetadata(
|
|
2642
|
-
inputMessageId,
|
|
2643
|
-
aggregationResult.streamExists ? "Received" : "InitiatedBy"
|
|
2644
|
-
);
|
|
2645
|
-
const inputToStore = {
|
|
2646
|
-
type: `${workflowName}:${messageWithMetadata.type}`,
|
|
2647
|
-
data: messageWithMetadata.data,
|
|
2648
|
-
kind: messageWithMetadata.kind,
|
|
2649
|
-
metadata: inputMetadata
|
|
2650
|
-
};
|
|
2651
|
-
const outputMessages = (Array.isArray(result2) ? result2 : [result2]).filter((msg) => msg !== void 0 && msg !== null);
|
|
2652
|
-
const outputCommandTypes = options.outputs?.commands ?? [];
|
|
2653
|
-
const taggedOutputMessages = outputMessages.map((msg) => {
|
|
2654
|
-
const action = outputCommandTypes.includes(
|
|
2655
|
-
msg.type
|
|
2656
|
-
) ? "Sent" : "Published";
|
|
2657
|
-
return tagOutputMessage(msg, action);
|
|
2658
|
-
});
|
|
2659
|
-
const messagesToAppend = options.separateInputInboxFromProcessing && hasWorkflowPrefix ? [...taggedOutputMessages] : [inputToStore, ...taggedOutputMessages];
|
|
2660
|
-
if (messagesToAppend.length === 0) {
|
|
2661
|
-
return emptyHandlerResult(currentStreamVersion);
|
|
2662
|
-
}
|
|
2663
|
-
const expectedStreamVersion = handleOptions?.expectedStreamVersion ?? (aggregationResult.streamExists ? currentStreamVersion : STREAM_DOES_NOT_EXIST);
|
|
2664
|
-
const appendResult = await eventStore.appendToStream(
|
|
2665
|
-
streamName,
|
|
2666
|
-
// TODO: Fix this cast
|
|
2667
|
-
messagesToAppend,
|
|
2668
|
-
{
|
|
2669
|
-
...handleOptions,
|
|
2670
|
-
expectedStreamVersion
|
|
2671
|
-
}
|
|
2672
|
-
);
|
|
2673
|
-
return {
|
|
2674
|
-
...appendResult,
|
|
2675
|
-
newMessages: outputMessages
|
|
2676
|
-
};
|
|
2677
|
-
});
|
|
2678
|
-
return result;
|
|
2679
|
-
},
|
|
2680
|
-
fromWorkflowHandlerRetryOptions(
|
|
2681
|
-
handleOptions && "retry" in handleOptions ? handleOptions.retry : options.retry
|
|
2682
|
-
)
|
|
2683
|
-
);
|
|
2684
|
-
var withSession2 = (eventStore, callback) => {
|
|
2685
|
-
const sessionFactory = canCreateEventStoreSession(eventStore) ? eventStore : nulloSessionFactory(eventStore);
|
|
2686
|
-
return sessionFactory.withSession(callback);
|
|
2022
|
+
const tagOutputMessage = (msg, action) => {
|
|
2023
|
+
const existingMetadata = "metadata" in msg && msg.metadata ? msg.metadata : {};
|
|
2024
|
+
return {
|
|
2025
|
+
...msg,
|
|
2026
|
+
metadata: {
|
|
2027
|
+
...existingMetadata,
|
|
2028
|
+
action
|
|
2029
|
+
}
|
|
2030
|
+
};
|
|
2031
|
+
};
|
|
2032
|
+
const createWrappedInitialState = (initialState) => {
|
|
2033
|
+
return () => ({
|
|
2034
|
+
userState: initialState(),
|
|
2035
|
+
processedInputIds: /* @__PURE__ */ new Set()
|
|
2036
|
+
});
|
|
2037
|
+
};
|
|
2038
|
+
const createWrappedEvolve = (evolve, workflowName, separateInputInboxFromProcessing) => {
|
|
2039
|
+
return (state, event) => {
|
|
2040
|
+
const metadata = event.metadata;
|
|
2041
|
+
let processedInputIds = state.processedInputIds;
|
|
2042
|
+
if (metadata?.input === true && typeof metadata?.originalMessageId === "string") {
|
|
2043
|
+
processedInputIds = new Set(state.processedInputIds);
|
|
2044
|
+
processedInputIds.add(metadata.originalMessageId);
|
|
2045
|
+
}
|
|
2046
|
+
if (separateInputInboxFromProcessing && metadata?.input === true) return {
|
|
2047
|
+
userState: state.userState,
|
|
2048
|
+
processedInputIds
|
|
2049
|
+
};
|
|
2050
|
+
const eventType = event.type;
|
|
2051
|
+
const eventForEvolve = eventType.startsWith(`${workflowName}:`) ? {
|
|
2052
|
+
...event,
|
|
2053
|
+
type: eventType.replace(`${workflowName}:`, "")
|
|
2054
|
+
} : event;
|
|
2055
|
+
return {
|
|
2056
|
+
userState: evolve(state.userState, eventForEvolve),
|
|
2057
|
+
processedInputIds
|
|
2058
|
+
};
|
|
2059
|
+
};
|
|
2060
|
+
};
|
|
2061
|
+
const workflowStreamName = ({ workflowName, workflowId }) => `emt:workflow:${workflowName}:${workflowId}`;
|
|
2062
|
+
const WorkflowHandler = (options) => async (store, message, handleOptions) => asyncRetry(async () => {
|
|
2063
|
+
return await withSession(store, async ({ eventStore }) => {
|
|
2064
|
+
const { workflow: { evolve, initialState, decide, name: workflowName }, getWorkflowId } = options;
|
|
2065
|
+
const inputMessageId = ("metadata" in message && message.metadata?.messageId ? message.metadata.messageId : void 0) ?? v7();
|
|
2066
|
+
const messageWithMetadata = {
|
|
2067
|
+
...message,
|
|
2068
|
+
metadata: {
|
|
2069
|
+
messageId: inputMessageId,
|
|
2070
|
+
...message.metadata
|
|
2071
|
+
}
|
|
2072
|
+
};
|
|
2073
|
+
const workflowId = getWorkflowId(messageWithMetadata);
|
|
2074
|
+
if (!workflowId) return emptyHandlerResult();
|
|
2075
|
+
const streamName = options.mapWorkflowId ? options.mapWorkflowId(workflowId) : workflowStreamName({
|
|
2076
|
+
workflowName,
|
|
2077
|
+
workflowId
|
|
2078
|
+
});
|
|
2079
|
+
const messageType = messageWithMetadata.type;
|
|
2080
|
+
const hasWorkflowPrefix = messageType.startsWith(`${workflowName}:`);
|
|
2081
|
+
if (options.separateInputInboxFromProcessing && !hasWorkflowPrefix) {
|
|
2082
|
+
const inputMetadata = createInputMetadata(inputMessageId, "InitiatedBy");
|
|
2083
|
+
const inputToStore = {
|
|
2084
|
+
type: `${workflowName}:${messageWithMetadata.type}`,
|
|
2085
|
+
data: messageWithMetadata.data,
|
|
2086
|
+
kind: messageWithMetadata.kind,
|
|
2087
|
+
metadata: inputMetadata
|
|
2088
|
+
};
|
|
2089
|
+
return {
|
|
2090
|
+
...await eventStore.appendToStream(streamName, [inputToStore], {
|
|
2091
|
+
...handleOptions,
|
|
2092
|
+
expectedStreamVersion: handleOptions?.expectedStreamVersion ?? "NO_CONCURRENCY_CHECK"
|
|
2093
|
+
}),
|
|
2094
|
+
newMessages: []
|
|
2095
|
+
};
|
|
2096
|
+
}
|
|
2097
|
+
const wrappedInitialState = createWrappedInitialState(initialState);
|
|
2098
|
+
const wrappedEvolve = createWrappedEvolve(evolve, workflowName, options.separateInputInboxFromProcessing ?? false);
|
|
2099
|
+
const aggregationResult = await eventStore.aggregateStream(streamName, {
|
|
2100
|
+
evolve: wrappedEvolve,
|
|
2101
|
+
initialState: wrappedInitialState,
|
|
2102
|
+
read: {
|
|
2103
|
+
...handleOptions,
|
|
2104
|
+
expectedStreamVersion: handleOptions?.expectedStreamVersion ?? "NO_CONCURRENCY_CHECK"
|
|
2105
|
+
}
|
|
2106
|
+
});
|
|
2107
|
+
const { currentStreamVersion } = aggregationResult;
|
|
2108
|
+
const { userState: state, processedInputIds } = aggregationResult.state;
|
|
2109
|
+
if (processedInputIds.has(inputMessageId)) return emptyHandlerResult(currentStreamVersion);
|
|
2110
|
+
const result = decide(hasWorkflowPrefix ? {
|
|
2111
|
+
...messageWithMetadata,
|
|
2112
|
+
type: messageType.replace(`${workflowName}:`, "")
|
|
2113
|
+
} : messageWithMetadata, state);
|
|
2114
|
+
const inputMetadata = createInputMetadata(inputMessageId, aggregationResult.streamExists ? "Received" : "InitiatedBy");
|
|
2115
|
+
const inputToStore = {
|
|
2116
|
+
type: `${workflowName}:${messageWithMetadata.type}`,
|
|
2117
|
+
data: messageWithMetadata.data,
|
|
2118
|
+
kind: messageWithMetadata.kind,
|
|
2119
|
+
metadata: inputMetadata
|
|
2120
|
+
};
|
|
2121
|
+
const outputMessages = (Array.isArray(result) ? result : [result]).filter((msg) => msg !== void 0 && msg !== null);
|
|
2122
|
+
const outputCommandTypes = options.outputs?.commands ?? [];
|
|
2123
|
+
const taggedOutputMessages = outputMessages.map((msg) => {
|
|
2124
|
+
return tagOutputMessage(msg, outputCommandTypes.includes(msg.type) ? "Sent" : "Published");
|
|
2125
|
+
});
|
|
2126
|
+
const messagesToAppend = options.separateInputInboxFromProcessing && hasWorkflowPrefix ? [...taggedOutputMessages] : [inputToStore, ...taggedOutputMessages];
|
|
2127
|
+
if (messagesToAppend.length === 0) return emptyHandlerResult(currentStreamVersion);
|
|
2128
|
+
const expectedStreamVersion = handleOptions?.expectedStreamVersion ?? (aggregationResult.streamExists ? currentStreamVersion : "STREAM_DOES_NOT_EXIST");
|
|
2129
|
+
return {
|
|
2130
|
+
...await eventStore.appendToStream(streamName, messagesToAppend, {
|
|
2131
|
+
...handleOptions,
|
|
2132
|
+
expectedStreamVersion
|
|
2133
|
+
}),
|
|
2134
|
+
newMessages: outputMessages
|
|
2135
|
+
};
|
|
2136
|
+
});
|
|
2137
|
+
}, fromWorkflowHandlerRetryOptions(handleOptions && "retry" in handleOptions ? handleOptions.retry : options.retry));
|
|
2138
|
+
const withSession = (eventStore, callback) => {
|
|
2139
|
+
return (canCreateEventStoreSession(eventStore) ? eventStore : nulloSessionFactory(eventStore)).withSession(callback);
|
|
2687
2140
|
};
|
|
2688
2141
|
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2142
|
+
//#endregion
|
|
2143
|
+
//#region src/workflows/workflow.ts
|
|
2144
|
+
const Workflow = (workflow) => {
|
|
2145
|
+
return workflow;
|
|
2692
2146
|
};
|
|
2693
2147
|
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
if (messagesToAppend.length === 0) {
|
|
2748
|
-
return;
|
|
2749
|
-
}
|
|
2750
|
-
const workflowId = options.getWorkflowId(
|
|
2751
|
-
message2
|
|
2752
|
-
);
|
|
2753
|
-
if (!workflowId) return;
|
|
2754
|
-
const streamName = options.mapWorkflowId ? options.mapWorkflowId(workflowId) : workflowStreamName({
|
|
2755
|
-
workflowName: workflow.name,
|
|
2756
|
-
workflowId
|
|
2757
|
-
});
|
|
2758
|
-
await context.connection.messageStore.appendToStream(
|
|
2759
|
-
streamName,
|
|
2760
|
-
messagesToAppend
|
|
2761
|
-
);
|
|
2762
|
-
return;
|
|
2763
|
-
}
|
|
2764
|
-
return;
|
|
2765
|
-
}
|
|
2766
|
-
});
|
|
2767
|
-
};
|
|
2768
|
-
export {
|
|
2769
|
-
AssertionError,
|
|
2770
|
-
CommandHandler,
|
|
2771
|
-
CommandHandlerStreamVersionConflictRetryOptions,
|
|
2772
|
-
ConcurrencyError,
|
|
2773
|
-
ConcurrencyInMemoryDatabaseError,
|
|
2774
|
-
DATABASE_REQUIRED_ERROR_MESSAGE,
|
|
2775
|
-
DeciderCommandHandler,
|
|
2776
|
-
DeciderSpecification,
|
|
2777
|
-
EmmettError,
|
|
2778
|
-
ExpectedVersionConflictError,
|
|
2779
|
-
GlobalStreamCaughtUpType,
|
|
2780
|
-
IllegalStateError,
|
|
2781
|
-
InMemoryEventStoreDefaultStreamVersion,
|
|
2782
|
-
InMemoryProjectionSpec,
|
|
2783
|
-
InProcessLock,
|
|
2784
|
-
JSONCodec,
|
|
2785
|
-
JSONReplacer,
|
|
2786
|
-
JSONReplacers,
|
|
2787
|
-
JSONReviver,
|
|
2788
|
-
JSONRevivers,
|
|
2789
|
-
JSONSerializer,
|
|
2790
|
-
MessageProcessor,
|
|
2791
|
-
MessageProcessorType,
|
|
2792
|
-
NO_CONCURRENCY_CHECK,
|
|
2793
|
-
NoRetries,
|
|
2794
|
-
NotFoundError,
|
|
2795
|
-
STREAM_DOES_NOT_EXIST,
|
|
2796
|
-
STREAM_EXISTS,
|
|
2797
|
-
TaskProcessor,
|
|
2798
|
-
ValidationError,
|
|
2799
|
-
ValidationErrors,
|
|
2800
|
-
Workflow,
|
|
2801
|
-
WorkflowHandler,
|
|
2802
|
-
WorkflowHandlerStreamVersionConflictRetryOptions,
|
|
2803
|
-
WorkflowSpecification,
|
|
2804
|
-
WrapEventStore,
|
|
2805
|
-
argMatches,
|
|
2806
|
-
argValue,
|
|
2807
|
-
arrayUtils,
|
|
2808
|
-
assertDeepEqual,
|
|
2809
|
-
assertDefined,
|
|
2810
|
-
assertDoesNotThrow,
|
|
2811
|
-
assertEqual,
|
|
2812
|
-
assertExpectedVersionMatchesCurrent,
|
|
2813
|
-
assertFails,
|
|
2814
|
-
assertFalse,
|
|
2815
|
-
assertIsNotNull,
|
|
2816
|
-
assertIsNull,
|
|
2817
|
-
assertMatches,
|
|
2818
|
-
assertNotDeepEqual,
|
|
2819
|
-
assertNotEmptyString,
|
|
2820
|
-
assertNotEqual,
|
|
2821
|
-
assertOk,
|
|
2822
|
-
assertPositiveNumber,
|
|
2823
|
-
assertRejects,
|
|
2824
|
-
assertThat,
|
|
2825
|
-
assertThatArray,
|
|
2826
|
-
assertThrows,
|
|
2827
|
-
assertThrowsAsync,
|
|
2828
|
-
assertTrue,
|
|
2829
|
-
assertUnsignedBigInt,
|
|
2830
|
-
asyncAwaiter,
|
|
2831
|
-
asyncProjections,
|
|
2832
|
-
asyncRetry,
|
|
2833
|
-
bigInt,
|
|
2834
|
-
bigIntProcessorCheckpoint,
|
|
2835
|
-
canCreateEventStoreSession,
|
|
2836
|
-
caughtUpEventFrom,
|
|
2837
|
-
command,
|
|
2838
|
-
composeJSONReplacers,
|
|
2839
|
-
composeJSONRevivers,
|
|
2840
|
-
deepEquals,
|
|
2841
|
-
defaultProcessingMessageProcessingScope,
|
|
2842
|
-
defaultProcessorPartition,
|
|
2843
|
-
defaultProcessorVersion,
|
|
2844
|
-
defaultTag,
|
|
2845
|
-
delay,
|
|
2846
|
-
documentExists,
|
|
2847
|
-
downcastRecordedMessage,
|
|
2848
|
-
downcastRecordedMessages,
|
|
2849
|
-
emmettPrefix,
|
|
2850
|
-
event,
|
|
2851
|
-
eventInStream,
|
|
2852
|
-
eventsInStream,
|
|
2853
|
-
expectInMemoryDocuments,
|
|
2854
|
-
filterProjections,
|
|
2855
|
-
formatDateToUtcYYYYMMDD,
|
|
2856
|
-
forwardToMessageBus,
|
|
2857
|
-
getCheckpoint,
|
|
2858
|
-
getInMemoryDatabase,
|
|
2859
|
-
getInMemoryEventStore,
|
|
2860
|
-
getInMemoryMessageBus,
|
|
2861
|
-
getProcessorInstanceId,
|
|
2862
|
-
getProjectorId,
|
|
2863
|
-
getWorkflowId,
|
|
2864
|
-
globalStreamCaughtUp,
|
|
2865
|
-
globalTag,
|
|
2866
|
-
guardBoundedAccess,
|
|
2867
|
-
guardExclusiveAccess,
|
|
2868
|
-
guardInitializedOnce,
|
|
2869
|
-
handleInMemoryProjections,
|
|
2870
|
-
hashText,
|
|
2871
|
-
inMemoryCheckpointer,
|
|
2872
|
-
inMemoryMultiStreamProjection,
|
|
2873
|
-
inMemoryProjection,
|
|
2874
|
-
inMemoryProjector,
|
|
2875
|
-
inMemoryReactor,
|
|
2876
|
-
inMemorySingleStreamProjection,
|
|
2877
|
-
inlineProjections,
|
|
2878
|
-
isBigint,
|
|
2879
|
-
isEquatable,
|
|
2880
|
-
isErrorConstructor,
|
|
2881
|
-
isExpectedVersionConflictError,
|
|
2882
|
-
isGlobalStreamCaughtUp,
|
|
2883
|
-
isNotInternalEvent,
|
|
2884
|
-
isNumber,
|
|
2885
|
-
isPluginConfig,
|
|
2886
|
-
isString,
|
|
2887
|
-
isSubscriptionEvent,
|
|
2888
|
-
isSubset,
|
|
2889
|
-
isValidYYYYMMDD,
|
|
2890
|
-
jsonSerializer,
|
|
2891
|
-
matchesExpectedVersion,
|
|
2892
|
-
merge,
|
|
2893
|
-
message,
|
|
2894
|
-
newEventsInStream,
|
|
2895
|
-
nulloSessionFactory,
|
|
2896
|
-
onShutdown,
|
|
2897
|
-
parseBigIntProcessorCheckpoint,
|
|
2898
|
-
parseDateFromUtcYYYYMMDD,
|
|
2899
|
-
projection,
|
|
2900
|
-
projections,
|
|
2901
|
-
projector,
|
|
2902
|
-
reactor,
|
|
2903
|
-
sum,
|
|
2904
|
-
toNormalizedString,
|
|
2905
|
-
tryPublishMessagesAfterCommit,
|
|
2906
|
-
unknownTag,
|
|
2907
|
-
upcastRecordedMessage,
|
|
2908
|
-
upcastRecordedMessages,
|
|
2909
|
-
verifyThat,
|
|
2910
|
-
wasMessageHandled,
|
|
2911
|
-
workflowOutputHandler,
|
|
2912
|
-
workflowProcessor,
|
|
2913
|
-
workflowStreamName
|
|
2148
|
+
//#endregion
|
|
2149
|
+
//#region src/workflows/workflowProcessor.ts
|
|
2150
|
+
const workflowOutputHandler = (handler) => handler;
|
|
2151
|
+
const getWorkflowId = (options) => `emt:processor:workflow:${options.workflowName}`;
|
|
2152
|
+
const workflowProcessor = (options) => {
|
|
2153
|
+
const { workflow, ...rest } = options;
|
|
2154
|
+
const inputs = [...options.inputs.commands, ...options.inputs.events];
|
|
2155
|
+
let canHandle = inputs;
|
|
2156
|
+
if (options.separateInputInboxFromProcessing) canHandle = [
|
|
2157
|
+
...canHandle,
|
|
2158
|
+
...options.inputs.commands.map((t) => `${workflow.name}:${t}`),
|
|
2159
|
+
...options.inputs.events.map((t) => `${workflow.name}:${t}`)
|
|
2160
|
+
];
|
|
2161
|
+
if (options.outputHandler) canHandle = [...canHandle, ...options.outputHandler.canHandle];
|
|
2162
|
+
const handle = WorkflowHandler(options);
|
|
2163
|
+
return reactor({
|
|
2164
|
+
...rest,
|
|
2165
|
+
processorId: options.processorId ?? getWorkflowId({ workflowName: workflow.name }),
|
|
2166
|
+
canHandle,
|
|
2167
|
+
type: MessageProcessorType.PROJECTOR,
|
|
2168
|
+
eachMessage: async (message, context) => {
|
|
2169
|
+
const messageType = message.type;
|
|
2170
|
+
if (message.metadata?.input === true || inputs.includes(messageType)) {
|
|
2171
|
+
const result = await handle(context.connection.messageStore, message, context);
|
|
2172
|
+
if (options.stopAfter && result.newMessages.length > 0) {
|
|
2173
|
+
for (const outputMessage of result.newMessages) if (options.stopAfter(outputMessage)) return {
|
|
2174
|
+
type: "STOP",
|
|
2175
|
+
reason: "Stop condition reached"
|
|
2176
|
+
};
|
|
2177
|
+
}
|
|
2178
|
+
return;
|
|
2179
|
+
}
|
|
2180
|
+
if (options.outputHandler?.canHandle.includes(messageType) === true) {
|
|
2181
|
+
const recordedMessage = message;
|
|
2182
|
+
const handledOutputMessages = options.outputHandler.eachBatch ? await options.outputHandler.eachBatch([recordedMessage], context) : await options.outputHandler.eachMessage(recordedMessage, context);
|
|
2183
|
+
if (handledOutputMessages instanceof EmmettError) return {
|
|
2184
|
+
type: "STOP",
|
|
2185
|
+
reason: "Routing error",
|
|
2186
|
+
error: handledOutputMessages
|
|
2187
|
+
};
|
|
2188
|
+
const messagesToAppend = Array.isArray(handledOutputMessages) ? handledOutputMessages : handledOutputMessages ? [handledOutputMessages] : [];
|
|
2189
|
+
if (messagesToAppend.length === 0) return;
|
|
2190
|
+
const workflowId = options.getWorkflowId(message);
|
|
2191
|
+
if (!workflowId) return;
|
|
2192
|
+
const streamName = options.mapWorkflowId ? options.mapWorkflowId(workflowId) : workflowStreamName({
|
|
2193
|
+
workflowName: workflow.name,
|
|
2194
|
+
workflowId
|
|
2195
|
+
});
|
|
2196
|
+
await context.connection.messageStore.appendToStream(streamName, messagesToAppend);
|
|
2197
|
+
return;
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
});
|
|
2914
2201
|
};
|
|
2202
|
+
|
|
2203
|
+
//#endregion
|
|
2204
|
+
export { AssertionError, CommandHandler, CommandHandlerStreamVersionConflictRetryOptions, ConcurrencyError, ConcurrencyInMemoryDatabaseError, DATABASE_REQUIRED_ERROR_MESSAGE, DeciderCommandHandler, DeciderSpecification, EmmettError, ExpectedVersionConflictError, GlobalStreamCaughtUpType, IllegalStateError, InMemoryEventStoreDefaultStreamVersion, InMemoryProjectionSpec, InProcessLock, JSONCodec, JSONReplacer, JSONReplacers, JSONReviver, JSONRevivers, JSONSerializer, MessageProcessor, MessageProcessorType, NO_CONCURRENCY_CHECK, NoRetries, NotFoundError, STREAM_DOES_NOT_EXIST, STREAM_EXISTS, TaskProcessor, ValidationError, ValidationErrors, Workflow, WorkflowHandler, WorkflowHandlerStreamVersionConflictRetryOptions, WorkflowSpecification, WrapEventStore, argMatches, argValue, arrayUtils, assertDeepEqual, assertDefined, assertDoesNotThrow, assertEqual, assertExpectedVersionMatchesCurrent, assertFails, assertFalse, assertIsNotNull, assertIsNull, assertMatches, assertNotDeepEqual, assertNotEmptyString, assertNotEqual, assertOk, assertPositiveNumber, assertRejects, assertThat, assertThatArray, assertThrows, assertThrowsAsync, assertTrue, assertUnsignedBigInt, asyncAwaiter, asyncProjections, asyncRetry, bigInt, bigIntProcessorCheckpoint, canCreateEventStoreSession, caughtUpEventFrom, command, composeJSONReplacers, composeJSONRevivers, deepEquals, defaultProcessingMessageProcessingScope, defaultProcessorPartition, defaultProcessorVersion, defaultTag, delay, documentExists, downcastRecordedMessage, downcastRecordedMessages, emmettPrefix, event, eventInStream, eventsInStream, expectInMemoryDocuments, filterProjections, formatDateToUtcYYYYMMDD, forwardToMessageBus, getCheckpoint, getInMemoryDatabase, getInMemoryEventStore, getInMemoryMessageBus, getProcessorInstanceId, getProjectorId, getWorkflowId, globalStreamCaughtUp, globalTag, guardBoundedAccess, guardExclusiveAccess, guardInitializedOnce, handleInMemoryProjections, hashText, inMemoryCheckpointer, inMemoryMultiStreamProjection, inMemoryProjection, inMemoryProjector, inMemoryReactor, inMemorySingleStreamProjection, inlineProjections, isBigint, isEquatable, isErrorConstructor, isExpectedVersionConflictError, isGlobalStreamCaughtUp, isNotInternalEvent, isNumber, isPluginConfig, isString, isSubscriptionEvent, isSubset, isValidYYYYMMDD, jsonSerializer, matchesExpectedVersion, merge, message, newEventsInStream, nulloSessionFactory, onShutdown, parseBigIntProcessorCheckpoint, parseDateFromUtcYYYYMMDD, projection, projections, projector, reactor, reduceAsync, sum, toNormalizedString, tryPublishMessagesAfterCommit, unknownTag, upcastRecordedMessage, upcastRecordedMessages, verifyThat, wasMessageHandled, workflowOutputHandler, workflowProcessor, workflowStreamName };
|
|
2915
2205
|
//# sourceMappingURL=index.js.map
|