@event-driven-io/emmett 0.43.0-beta.13 → 0.43.0-beta.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/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 +2307 -2916
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +672 -622
- package/dist/index.d.ts +672 -622
- package/dist/index.js +2088 -2844
- 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,2961 +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
|
-
|
|
64
|
-
|
|
65
|
-
// src/eventStore/events/index.ts
|
|
66
|
-
var GlobalStreamCaughtUpType = "__emt:GlobalStreamCaughtUp";
|
|
67
|
-
var isGlobalStreamCaughtUp = (event2) => event2.type === GlobalStreamCaughtUpType;
|
|
68
|
-
var caughtUpEventFrom = (position) => (event2) => event2.type === GlobalStreamCaughtUpType && event2.metadata?.globalPosition >= position;
|
|
69
|
-
var globalStreamCaughtUp = (data) => event(GlobalStreamCaughtUpType, data, {
|
|
70
|
-
globalPosition: data.globalPosition
|
|
71
|
-
});
|
|
72
|
-
var isSubscriptionEvent = (event2) => isGlobalStreamCaughtUp(event2);
|
|
73
|
-
var isNotInternalEvent = (event2) => !isGlobalStreamCaughtUp(event2);
|
|
74
|
-
|
|
75
|
-
// src/eventStore/eventStore.ts
|
|
76
|
-
var canCreateEventStoreSession = (eventStore) => "withSession" in eventStore;
|
|
77
|
-
var nulloSessionFactory = (eventStore) => ({
|
|
78
|
-
withSession: (callback) => {
|
|
79
|
-
const nulloSession = {
|
|
80
|
-
eventStore,
|
|
81
|
-
close: () => Promise.resolve()
|
|
82
|
-
};
|
|
83
|
-
return callback(nulloSession);
|
|
84
|
-
}
|
|
85
|
-
});
|
|
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`;
|
|
86
77
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
return current === expected;
|
|
96
|
-
};
|
|
97
|
-
var assertExpectedVersionMatchesCurrent = (current, expected, defaultVersion) => {
|
|
98
|
-
expected ??= NO_CONCURRENCY_CHECK;
|
|
99
|
-
if (!matchesExpectedVersion(current, expected, defaultVersion))
|
|
100
|
-
throw new ExpectedVersionConflictError(current, expected);
|
|
101
|
-
};
|
|
102
|
-
var ExpectedVersionConflictError = class _ExpectedVersionConflictError extends ConcurrencyError {
|
|
103
|
-
constructor(current, expected) {
|
|
104
|
-
super(current?.toString(), expected?.toString());
|
|
105
|
-
Object.setPrototypeOf(this, _ExpectedVersionConflictError.prototype);
|
|
106
|
-
}
|
|
107
|
-
};
|
|
108
|
-
var isExpectedVersionConflictError = (error) => error instanceof ExpectedVersionConflictError || EmmettError.isInstanceOf(
|
|
109
|
-
error,
|
|
110
|
-
ExpectedVersionConflictError.Codes.ConcurrencyError
|
|
111
|
-
);
|
|
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);
|
|
112
86
|
|
|
113
|
-
|
|
114
|
-
|
|
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
|
+
} });
|
|
115
96
|
|
|
116
|
-
|
|
117
|
-
|
|
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);
|
|
118
119
|
|
|
119
|
-
|
|
120
|
+
//#endregion
|
|
121
|
+
//#region src/utils/async/mapAsync.ts
|
|
120
122
|
async function reduceAsync(items, fn, initial) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
125
|
-
return accumulator;
|
|
123
|
+
let accumulator = initial;
|
|
124
|
+
for (let i = 0; i < items.length; i++) accumulator = await fn(accumulator, items[i], i);
|
|
125
|
+
return accumulator;
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
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();
|
|
145
143
|
};
|
|
146
144
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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;
|
|
163
161
|
};
|
|
164
162
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
163
|
+
//#endregion
|
|
164
|
+
//#region src/utils/collections/index.ts
|
|
165
|
+
const arrayUtils = {
|
|
166
|
+
merge,
|
|
167
|
+
hasDuplicates,
|
|
168
|
+
getDuplicates
|
|
170
169
|
};
|
|
171
170
|
|
|
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
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
if (left === right) return true;
|
|
314
|
-
if (isEquatable(left)) {
|
|
315
|
-
return left.equals(right);
|
|
316
|
-
}
|
|
317
|
-
const leftType = getType(left);
|
|
318
|
-
const rightType = getType(right);
|
|
319
|
-
if (leftType !== rightType) return false;
|
|
320
|
-
switch (leftType) {
|
|
321
|
-
case "null":
|
|
322
|
-
case "undefined":
|
|
323
|
-
case "boolean":
|
|
324
|
-
case "number":
|
|
325
|
-
case "bigint":
|
|
326
|
-
case "string":
|
|
327
|
-
case "symbol":
|
|
328
|
-
case "function":
|
|
329
|
-
return left === right;
|
|
330
|
-
case "array":
|
|
331
|
-
return compareArrays(left, right);
|
|
332
|
-
case "date":
|
|
333
|
-
return compareDates(left, right);
|
|
334
|
-
case "regexp":
|
|
335
|
-
return compareRegExps(left, right);
|
|
336
|
-
case "error":
|
|
337
|
-
return compareErrors(left, right);
|
|
338
|
-
case "map":
|
|
339
|
-
return compareMaps(
|
|
340
|
-
left,
|
|
341
|
-
right
|
|
342
|
-
);
|
|
343
|
-
case "set":
|
|
344
|
-
return compareSets(left, right);
|
|
345
|
-
case "arraybuffer":
|
|
346
|
-
return compareArrayBuffers(left, right);
|
|
347
|
-
case "dataview":
|
|
348
|
-
case "weakmap":
|
|
349
|
-
case "weakset":
|
|
350
|
-
return false;
|
|
351
|
-
case "typedarray":
|
|
352
|
-
return compareTypedArrays(
|
|
353
|
-
left,
|
|
354
|
-
right
|
|
355
|
-
);
|
|
356
|
-
case "boxed-boolean":
|
|
357
|
-
return left.valueOf() === right.valueOf();
|
|
358
|
-
case "boxed-number":
|
|
359
|
-
return left.valueOf() === right.valueOf();
|
|
360
|
-
case "boxed-string":
|
|
361
|
-
return left.valueOf() === right.valueOf();
|
|
362
|
-
case "object":
|
|
363
|
-
return compareObjects(
|
|
364
|
-
left,
|
|
365
|
-
right
|
|
366
|
-
);
|
|
367
|
-
default:
|
|
368
|
-
return false;
|
|
369
|
-
}
|
|
370
|
-
};
|
|
371
|
-
var isEquatable = (left) => {
|
|
372
|
-
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";
|
|
373
312
|
};
|
|
374
313
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
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;
|
|
383
323
|
};
|
|
384
324
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
// src/taskProcessing/taskProcessor.ts
|
|
325
|
+
//#endregion
|
|
326
|
+
//#region src/taskProcessing/taskProcessor.ts
|
|
389
327
|
var TaskProcessor = class {
|
|
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
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
hasItemsToProcess = () => this.queue.findIndex(
|
|
493
|
-
(item) => !item.options?.taskGroupId || !this.activeGroups.has(item.options.taskGroupId)
|
|
494
|
-
) !== -1;
|
|
495
|
-
};
|
|
496
|
-
var DEFAULT_PROMISE_DEADLINE = 2147483647;
|
|
497
|
-
var promiseWithDeadline = (executor, options) => {
|
|
498
|
-
return new Promise((resolve, reject) => {
|
|
499
|
-
let taskStarted = false;
|
|
500
|
-
let timeoutId = null;
|
|
501
|
-
const deadline = options.deadline ?? DEFAULT_PROMISE_DEADLINE;
|
|
502
|
-
timeoutId = setTimeout(() => {
|
|
503
|
-
if (!taskStarted) {
|
|
504
|
-
reject(
|
|
505
|
-
new Error("Task was not started within the maximum waiting time")
|
|
506
|
-
);
|
|
507
|
-
}
|
|
508
|
-
}, deadline);
|
|
509
|
-
timeoutId.unref();
|
|
510
|
-
executor(
|
|
511
|
-
(value) => {
|
|
512
|
-
taskStarted = true;
|
|
513
|
-
if (timeoutId) {
|
|
514
|
-
clearTimeout(timeoutId);
|
|
515
|
-
}
|
|
516
|
-
timeoutId = null;
|
|
517
|
-
resolve(value);
|
|
518
|
-
},
|
|
519
|
-
(reason) => {
|
|
520
|
-
if (timeoutId) {
|
|
521
|
-
clearTimeout(timeoutId);
|
|
522
|
-
}
|
|
523
|
-
timeoutId = null;
|
|
524
|
-
reject(reason);
|
|
525
|
-
}
|
|
526
|
-
);
|
|
527
|
-
});
|
|
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
|
+
});
|
|
528
430
|
};
|
|
529
431
|
|
|
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
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
return ensureInitialized(retryCount + 1);
|
|
642
|
-
}
|
|
643
|
-
throw error;
|
|
644
|
-
}
|
|
645
|
-
},
|
|
646
|
-
{ taskGroupId: uuid() }
|
|
647
|
-
);
|
|
648
|
-
};
|
|
649
|
-
return {
|
|
650
|
-
ensureInitialized,
|
|
651
|
-
reset: () => {
|
|
652
|
-
initPromise = null;
|
|
653
|
-
},
|
|
654
|
-
stop: (options2) => taskProcessor.stop(options2)
|
|
655
|
-
};
|
|
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
|
+
};
|
|
656
543
|
};
|
|
657
544
|
|
|
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
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
} finally {
|
|
701
|
-
locks.delete(lockId);
|
|
702
|
-
ack();
|
|
703
|
-
}
|
|
704
|
-
},
|
|
705
|
-
{ taskGroupId: lockId }
|
|
706
|
-
);
|
|
707
|
-
}
|
|
708
|
-
};
|
|
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
|
+
};
|
|
709
587
|
};
|
|
710
588
|
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
};
|
|
589
|
+
//#endregion
|
|
590
|
+
//#region src/utils/numbers/bigint.ts
|
|
591
|
+
const toNormalizedString = (value) => value.toString().padStart(19, "0");
|
|
592
|
+
const bigInt = { toNormalizedString };
|
|
716
593
|
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
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;
|
|
730
609
|
};
|
|
731
610
|
|
|
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
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
)
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
);
|
|
814
|
-
var JSONReplacers = {
|
|
815
|
-
bigInt: bigIntReplacer,
|
|
816
|
-
date: dateReplacer
|
|
817
|
-
};
|
|
818
|
-
var JSONRevivers = {
|
|
819
|
-
bigInt: bigIntReviver,
|
|
820
|
-
date: dateReviver
|
|
821
|
-
};
|
|
822
|
-
var jsonSerializer = (options) => {
|
|
823
|
-
const defaultReplacer = JSONReplacer(options);
|
|
824
|
-
const defaultReviver = JSONReviver(options);
|
|
825
|
-
return {
|
|
826
|
-
serialize: (object, serializerOptions) => JSON.stringify(
|
|
827
|
-
object,
|
|
828
|
-
serializerOptions ? JSONReplacer(serializerOptions) : defaultReplacer
|
|
829
|
-
),
|
|
830
|
-
deserialize: (payload, deserializerOptions) => JSON.parse(
|
|
831
|
-
payload,
|
|
832
|
-
deserializerOptions ? JSONReviver(deserializerOptions) : defaultReviver
|
|
833
|
-
)
|
|
834
|
-
};
|
|
835
|
-
};
|
|
836
|
-
var JSONSerializer = Object.assign(jsonSerializer(), {
|
|
837
|
-
from: (options) => options?.serialization?.serializer ?? (options?.serialization?.options ? jsonSerializer(options?.serialization?.options) : JSONSerializer)
|
|
838
|
-
});
|
|
839
|
-
var JSONCodec = (options) => {
|
|
840
|
-
const serializer = "serializer" in options && options.serializer ? options.serializer : jsonSerializer(
|
|
841
|
-
"serializerOptions" in options ? options.serializerOptions : void 0
|
|
842
|
-
);
|
|
843
|
-
const upcast = options.upcast ?? ((doc) => doc);
|
|
844
|
-
const downcast = options.downcast ?? ((doc) => doc);
|
|
845
|
-
return {
|
|
846
|
-
decode: (payload, decodeOptions) => {
|
|
847
|
-
const deserialized = decodeOptions ? serializer.deserialize(payload, decodeOptions) : serializer.deserialize(payload);
|
|
848
|
-
return upcast(deserialized);
|
|
849
|
-
},
|
|
850
|
-
encode: (object, encodeOptions) => {
|
|
851
|
-
const downcasted = downcast(object);
|
|
852
|
-
return encodeOptions ? serializer.serialize(downcasted, encodeOptions) : serializer.serialize(downcasted);
|
|
853
|
-
}
|
|
854
|
-
};
|
|
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
|
+
};
|
|
855
692
|
};
|
|
856
693
|
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
}
|
|
876
|
-
throw error;
|
|
877
|
-
}
|
|
878
|
-
},
|
|
879
|
-
opts ?? { retries: 0 }
|
|
880
|
-
);
|
|
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 });
|
|
881
712
|
};
|
|
882
713
|
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
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 () => {};
|
|
909
740
|
};
|
|
910
741
|
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
);
|
|
918
|
-
const view = new BigInt64Array(hashBuffer, 0, 1);
|
|
919
|
-
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];
|
|
920
748
|
};
|
|
921
749
|
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
if (options.errors?.throwOnOperationFailures)
|
|
942
|
-
operationResult2.assertSuccessful();
|
|
943
|
-
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;
|
|
944
769
|
};
|
|
945
770
|
|
|
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
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
return {
|
|
1152
|
-
...replaceResult,
|
|
1153
|
-
document: {
|
|
1154
|
-
...result,
|
|
1155
|
-
_version: replaceResult.nextExpectedVersion
|
|
1156
|
-
}
|
|
1157
|
-
};
|
|
1158
|
-
}
|
|
1159
|
-
return operationResult(
|
|
1160
|
-
{
|
|
1161
|
-
successful: true,
|
|
1162
|
-
document: existing
|
|
1163
|
-
},
|
|
1164
|
-
{ operationName: "handle", collectionName, errors }
|
|
1165
|
-
);
|
|
1166
|
-
}
|
|
1167
|
-
};
|
|
1168
|
-
return collection;
|
|
1169
|
-
}
|
|
1170
|
-
};
|
|
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
|
+
} };
|
|
1171
976
|
};
|
|
1172
977
|
|
|
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
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
reason: "Stop condition reached",
|
|
1341
|
-
lastSuccessfulMessage: unhandledMessages[stopMessageIndex]
|
|
1342
|
-
} : batchResult;
|
|
1343
|
-
const isStop = messageProcessingResult && messageProcessingResult.type === "STOP";
|
|
1344
|
-
const checkpointMessage = messageProcessingResult?.type === "STOP" ? messageProcessingResult.lastSuccessfulMessage : messagesAboveCheckpoint[messagesAboveCheckpoint.length - 1];
|
|
1345
|
-
if (checkpointMessage && checkpoints) {
|
|
1346
|
-
const storeCheckpointResult = await checkpoints.store(
|
|
1347
|
-
{
|
|
1348
|
-
processorId,
|
|
1349
|
-
version,
|
|
1350
|
-
message: checkpointMessage,
|
|
1351
|
-
lastCheckpoint,
|
|
1352
|
-
partition
|
|
1353
|
-
},
|
|
1354
|
-
context
|
|
1355
|
-
);
|
|
1356
|
-
if (storeCheckpointResult.success) {
|
|
1357
|
-
lastCheckpoint = storeCheckpointResult.newCheckpoint;
|
|
1358
|
-
}
|
|
1359
|
-
}
|
|
1360
|
-
if (isStop) {
|
|
1361
|
-
isActive = false;
|
|
1362
|
-
return messageProcessingResult;
|
|
1363
|
-
}
|
|
1364
|
-
return void 0;
|
|
1365
|
-
}, partialContext);
|
|
1366
|
-
}
|
|
1367
|
-
};
|
|
1368
|
-
};
|
|
1369
|
-
var projector = (options) => {
|
|
1370
|
-
const {
|
|
1371
|
-
projection: projection2,
|
|
1372
|
-
processorId = getProjectorId({
|
|
1373
|
-
projectionName: projection2.name ?? "unknown"
|
|
1374
|
-
}),
|
|
1375
|
-
...rest
|
|
1376
|
-
} = options;
|
|
1377
|
-
return reactor({
|
|
1378
|
-
...rest,
|
|
1379
|
-
type: MessageProcessorType.PROJECTOR,
|
|
1380
|
-
canHandle: projection2.canHandle,
|
|
1381
|
-
processorId,
|
|
1382
|
-
messageOptions: options.projection.eventsOptions,
|
|
1383
|
-
hooks: {
|
|
1384
|
-
onInit: options.hooks?.onInit,
|
|
1385
|
-
onStart: options.truncateOnStart && options.projection.truncate || options.hooks?.onStart ? async (context) => {
|
|
1386
|
-
if (options.truncateOnStart && options.projection.truncate)
|
|
1387
|
-
await options.projection.truncate(context);
|
|
1388
|
-
if (options.hooks?.onStart) await options.hooks?.onStart(context);
|
|
1389
|
-
} : void 0,
|
|
1390
|
-
onClose: options.hooks?.onClose
|
|
1391
|
-
},
|
|
1392
|
-
eachBatch: async (events, context) => projection2.handle(events, context)
|
|
1393
|
-
});
|
|
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
|
+
});
|
|
1394
1145
|
};
|
|
1395
1146
|
|
|
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
|
-
};
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
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
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
processorId: options.processorId
|
|
1475
|
-
}),
|
|
1476
|
-
checkpoints: inMemoryCheckpointer()
|
|
1477
|
-
});
|
|
1478
|
-
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 });
|
|
1479
1225
|
};
|
|
1480
1226
|
|
|
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
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
...context,
|
|
1517
|
-
database: context.database
|
|
1518
|
-
});
|
|
1519
|
-
} : 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
|
|
1520
1262
|
});
|
|
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
|
-
...options,
|
|
1554
|
-
getDocumentId: options.getDocumentId ?? ((event2) => event2.metadata.streamName)
|
|
1555
|
-
});
|
|
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
|
+
});
|
|
1556
1295
|
};
|
|
1557
1296
|
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
// src/testing/assertions.ts
|
|
1297
|
+
//#endregion
|
|
1298
|
+
//#region src/testing/assertions.ts
|
|
1562
1299
|
var AssertionError = class extends Error {
|
|
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
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
};
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
await promise;
|
|
1644
|
-
throw new AssertionError("Function didn't throw expected error");
|
|
1645
|
-
} catch (error) {
|
|
1646
|
-
if (!errorCheck) return;
|
|
1647
|
-
if (errorCheck instanceof Error) assertDeepEqual(error, errorCheck);
|
|
1648
|
-
else assertTrue(errorCheck(error));
|
|
1649
|
-
}
|
|
1650
|
-
};
|
|
1651
|
-
var assertMatches = (actual, expected, message2) => {
|
|
1652
|
-
if (!isSubset(actual, expected))
|
|
1653
|
-
throw new AssertionError(
|
|
1654
|
-
message2 ?? `subObj:
|
|
1655
|
-
${JSONSerializer.serialize(expected)}
|
|
1656
|
-
is not subset of
|
|
1657
|
-
${JSONSerializer.serialize(actual)}`
|
|
1658
|
-
);
|
|
1659
|
-
};
|
|
1660
|
-
var assertDeepEqual = (actual, expected, message2) => {
|
|
1661
|
-
if (!deepEquals(actual, expected))
|
|
1662
|
-
throw new AssertionError(
|
|
1663
|
-
message2 ?? `subObj:
|
|
1664
|
-
${JSONSerializer.serialize(expected)}
|
|
1665
|
-
is not equal to
|
|
1666
|
-
${JSONSerializer.serialize(actual)}`
|
|
1667
|
-
);
|
|
1668
|
-
};
|
|
1669
|
-
var assertNotDeepEqual = (actual, expected, message2) => {
|
|
1670
|
-
if (deepEquals(actual, expected))
|
|
1671
|
-
throw new AssertionError(
|
|
1672
|
-
message2 ?? `subObj:
|
|
1673
|
-
${JSONSerializer.serialize(expected)}
|
|
1674
|
-
is equals to
|
|
1675
|
-
${JSONSerializer.serialize(actual)}`
|
|
1676
|
-
);
|
|
1677
|
-
};
|
|
1678
|
-
var assertThat = (item) => {
|
|
1679
|
-
return {
|
|
1680
|
-
isEqualTo: (other) => assertTrue(deepEquals(item, other))
|
|
1681
|
-
};
|
|
1682
|
-
};
|
|
1683
|
-
var assertDefined = (value, message2) => {
|
|
1684
|
-
assertOk(value, message2 instanceof Error ? message2.message : message2);
|
|
1685
|
-
};
|
|
1686
|
-
function assertFalse(condition, message2) {
|
|
1687
|
-
if (condition !== false)
|
|
1688
|
-
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`);
|
|
1689
1380
|
}
|
|
1690
|
-
function assertTrue(condition,
|
|
1691
|
-
|
|
1692
|
-
throw new AssertionError(message2 ?? `Condition is false`);
|
|
1381
|
+
function assertTrue(condition, message) {
|
|
1382
|
+
if (condition !== true) throw new AssertionError(message ?? `Condition is false`);
|
|
1693
1383
|
}
|
|
1694
|
-
function assertOk(obj,
|
|
1695
|
-
|
|
1384
|
+
function assertOk(obj, message) {
|
|
1385
|
+
if (!obj) throw new AssertionError(message ?? `Condition is not truthy`);
|
|
1696
1386
|
}
|
|
1697
|
-
function assertEqual(expected, actual,
|
|
1698
|
-
|
|
1699
|
-
throw new AssertionError(
|
|
1700
|
-
`${message2 ?? "Objects are not equal"}:
|
|
1701
|
-
Expected: ${JSONSerializer.serialize(expected)}
|
|
1702
|
-
Actual: ${JSONSerializer.serialize(actual)}`
|
|
1703
|
-
);
|
|
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)}`);
|
|
1704
1389
|
}
|
|
1705
|
-
function assertNotEqual(obj, other,
|
|
1706
|
-
|
|
1707
|
-
throw new AssertionError(
|
|
1708
|
-
message2 ?? `Objects are equal: ${JSONSerializer.serialize(obj)}`
|
|
1709
|
-
);
|
|
1390
|
+
function assertNotEqual(obj, other, message) {
|
|
1391
|
+
if (obj === other) throw new AssertionError(message ?? `Objects are equal: ${JSONSerializer.serialize(obj)}`);
|
|
1710
1392
|
}
|
|
1711
1393
|
function assertIsNotNull(result) {
|
|
1712
|
-
|
|
1713
|
-
|
|
1394
|
+
assertNotEqual(result, null);
|
|
1395
|
+
assertOk(result);
|
|
1714
1396
|
}
|
|
1715
1397
|
function assertIsNull(result) {
|
|
1716
|
-
|
|
1398
|
+
assertEqual(result, null);
|
|
1717
1399
|
}
|
|
1718
|
-
|
|
1719
|
-
|
|
1400
|
+
const argValue = (value) => (arg) => deepEquals(arg, value);
|
|
1401
|
+
const argMatches = (matches) => (arg) => matches(arg);
|
|
1720
1402
|
function verifyThat(fn) {
|
|
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
|
-
fn.mock?.calls.length !== void 0 && fn.mock.calls.length >= 1
|
|
1746
|
-
);
|
|
1747
|
-
assertTrue(
|
|
1748
|
-
fn.mock?.calls.length !== void 0 && fn.mock.calls.length >= 1 && fn.mock.calls.some(
|
|
1749
|
-
(call) => call.arguments && call.arguments.length >= matches.length && matches.every((match, index) => match(call.arguments[index]))
|
|
1750
|
-
)
|
|
1751
|
-
);
|
|
1752
|
-
},
|
|
1753
|
-
notCalledWithArgumentMatching: (...matches) => {
|
|
1754
|
-
assertFalse(
|
|
1755
|
-
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(
|
|
1756
|
-
(match, index) => match(fn.mock.calls[0].arguments[index])
|
|
1757
|
-
)
|
|
1758
|
-
);
|
|
1759
|
-
}
|
|
1760
|
-
};
|
|
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
|
+
};
|
|
1761
1427
|
}
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
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
|
-
anyMatches: (matches) => {
|
|
1814
|
-
assertTrue(array.some(matches));
|
|
1815
|
-
},
|
|
1816
|
-
allMatchAsync: async (matches) => {
|
|
1817
|
-
for (const item of array) {
|
|
1818
|
-
assertTrue(await matches(item));
|
|
1819
|
-
}
|
|
1820
|
-
}
|
|
1821
|
-
};
|
|
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
|
+
};
|
|
1822
1478
|
};
|
|
1823
1479
|
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
};
|
|
1480
|
+
//#endregion
|
|
1481
|
+
//#region src/testing/deciderSpecification.ts
|
|
1482
|
+
const DeciderSpecification = { for: deciderSpecificationFor };
|
|
1828
1483
|
function deciderSpecificationFor(decider) {
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
"Handler did not fail as expected"
|
|
1867
|
-
);
|
|
1868
|
-
}).catch((error) => {
|
|
1869
|
-
thenThrowsErrorHandler(error, args);
|
|
1870
|
-
});
|
|
1871
|
-
}
|
|
1872
|
-
throw new AssertionError("Handler did not fail as expected");
|
|
1873
|
-
} catch (error) {
|
|
1874
|
-
thenThrowsErrorHandler(error, args);
|
|
1875
|
-
}
|
|
1876
|
-
}
|
|
1877
|
-
};
|
|
1878
|
-
}
|
|
1879
|
-
};
|
|
1880
|
-
};
|
|
1881
|
-
}
|
|
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
|
+
};
|
|
1882
1521
|
}
|
|
1883
|
-
function thenHandler(events, expectedEvents) {
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
expectedEventsArray
|
|
1888
|
-
);
|
|
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);
|
|
1889
1526
|
}
|
|
1890
|
-
function thenNothingHappensHandler(events) {
|
|
1891
|
-
|
|
1892
|
-
assertThatArray(resultEventsArray).isEmpty();
|
|
1527
|
+
function thenNothingHappensHandler$1(events) {
|
|
1528
|
+
assertThatArray(Array.isArray(events) ? events : [events]).isEmpty();
|
|
1893
1529
|
}
|
|
1894
|
-
function thenThrowsErrorHandler(error, args) {
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
}
|
|
1904
|
-
assertTrue(
|
|
1905
|
-
error instanceof args[0],
|
|
1906
|
-
`Caught error is not an instance of the expected type: ${error?.toString()}`
|
|
1907
|
-
);
|
|
1908
|
-
if (args[1]) {
|
|
1909
|
-
assertTrue(
|
|
1910
|
-
args[1](error),
|
|
1911
|
-
`Error didn't match the error condition: ${error?.toString()}`
|
|
1912
|
-
);
|
|
1913
|
-
}
|
|
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()}`);
|
|
1914
1539
|
}
|
|
1915
1540
|
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
};
|
|
1541
|
+
//#endregion
|
|
1542
|
+
//#region src/testing/workflowSpecification.ts
|
|
1543
|
+
const WorkflowSpecification = { for: workflowSpecificationFor };
|
|
1920
1544
|
function workflowSpecificationFor(workflow) {
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
} catch (error) {
|
|
1946
|
-
thenThrowsErrorHandler2(error, args);
|
|
1947
|
-
}
|
|
1948
|
-
}
|
|
1949
|
-
};
|
|
1950
|
-
}
|
|
1951
|
-
};
|
|
1952
|
-
};
|
|
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
|
+
};
|
|
1953
1569
|
}
|
|
1954
|
-
function
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
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);
|
|
1958
1574
|
}
|
|
1959
|
-
function
|
|
1960
|
-
|
|
1961
|
-
assertThatArray(resultArray).isEmpty();
|
|
1575
|
+
function thenNothingHappensHandler(result) {
|
|
1576
|
+
assertThatArray(Array.isArray(result) ? result : [result]).isEmpty();
|
|
1962
1577
|
}
|
|
1963
|
-
function
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
}
|
|
1973
|
-
assertTrue(
|
|
1974
|
-
error instanceof args[0],
|
|
1975
|
-
`Caught error is not an instance of the expected type: ${error?.toString()}`
|
|
1976
|
-
);
|
|
1977
|
-
if (args[1]) {
|
|
1978
|
-
assertTrue(
|
|
1979
|
-
args[1](error),
|
|
1980
|
-
`Error didn't match the error condition: ${error?.toString()}`
|
|
1981
|
-
);
|
|
1982
|
-
}
|
|
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()}`);
|
|
1983
1587
|
}
|
|
1984
1588
|
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
[...currentStream[1], ...events]
|
|
2009
|
-
]);
|
|
2010
|
-
return result;
|
|
2011
|
-
},
|
|
2012
|
-
appendedEvents,
|
|
2013
|
-
setup: async (streamName, events) => {
|
|
2014
|
-
return eventStore.appendToStream(streamName, events);
|
|
2015
|
-
}
|
|
2016
|
-
// streamEvents: (): ReadableStream<
|
|
2017
|
-
// // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
|
|
2018
|
-
// ReadEvent<Event, ReadEventMetadataType> | GlobalSubscriptionEvent
|
|
2019
|
-
// > => {
|
|
2020
|
-
// return eventStore.streamEvents();
|
|
2021
|
-
// },
|
|
2022
|
-
};
|
|
2023
|
-
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
|
+
};
|
|
2024
1612
|
};
|
|
2025
1613
|
|
|
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
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
}
|
|
2126
|
-
}
|
|
2127
|
-
}
|
|
2128
|
-
};
|
|
2129
|
-
}
|
|
2130
|
-
};
|
|
2131
|
-
};
|
|
2132
|
-
}
|
|
2133
|
-
};
|
|
2134
|
-
var eventInStream = (streamName, event2) => {
|
|
2135
|
-
return {
|
|
2136
|
-
...event2,
|
|
2137
|
-
metadata: {
|
|
2138
|
-
...event2.metadata ?? {},
|
|
2139
|
-
streamName: event2.metadata?.streamName ?? streamName
|
|
2140
|
-
}
|
|
2141
|
-
};
|
|
2142
|
-
};
|
|
2143
|
-
var eventsInStream = (streamName, events) => {
|
|
2144
|
-
return events.map((e) => eventInStream(streamName, e));
|
|
2145
|
-
};
|
|
2146
|
-
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;
|
|
2147
1713
|
function documentExists(expected, options) {
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
}
|
|
2166
|
-
}
|
|
2167
|
-
return Promise.resolve(true);
|
|
2168
|
-
};
|
|
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
|
+
};
|
|
2169
1731
|
}
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
inCollection: collectionName,
|
|
2175
|
-
withId: id
|
|
2176
|
-
})
|
|
2177
|
-
})
|
|
2178
|
-
})
|
|
2179
|
-
};
|
|
1732
|
+
const expectInMemoryDocuments = { fromCollection: (collectionName) => ({ withId: (id) => ({ toBeEqual: (expected) => documentExists(expected, {
|
|
1733
|
+
inCollection: collectionName,
|
|
1734
|
+
withId: id
|
|
1735
|
+
}) }) }) };
|
|
2180
1736
|
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
};
|
|
2199
|
-
};
|
|
2200
|
-
var downcastRecordedMessages = (recordedMessages, options) => {
|
|
2201
|
-
if (!options?.downcast)
|
|
2202
|
-
return recordedMessages;
|
|
2203
|
-
return recordedMessages.map(
|
|
2204
|
-
(recordedMessage) => downcastRecordedMessage(recordedMessage, options)
|
|
2205
|
-
);
|
|
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));
|
|
2206
1754
|
};
|
|
2207
1755
|
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
};
|
|
2226
|
-
};
|
|
2227
|
-
var upcastRecordedMessages = (recordedMessages, options) => {
|
|
2228
|
-
if (!options?.upcast)
|
|
2229
|
-
return recordedMessages;
|
|
2230
|
-
return recordedMessages.map(
|
|
2231
|
-
(recordedMessage) => upcastRecordedMessage(recordedMessage, options)
|
|
2232
|
-
);
|
|
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));
|
|
2233
1773
|
};
|
|
2234
1774
|
|
|
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
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
...downcastRecordedMessages(newEvents, options?.schema?.versioning)
|
|
2315
|
-
]);
|
|
2316
|
-
if (inlineProjections2.length > 0) {
|
|
2317
|
-
await handleInMemoryProjections({
|
|
2318
|
-
projections: inlineProjections2,
|
|
2319
|
-
events: newEvents,
|
|
2320
|
-
database: eventStore.database,
|
|
2321
|
-
eventStore
|
|
2322
|
-
});
|
|
2323
|
-
}
|
|
2324
|
-
const result = {
|
|
2325
|
-
nextExpectedStreamVersion: positionOfLastEventInTheStream,
|
|
2326
|
-
createdNewStream: currentStreamVersion === InMemoryEventStoreDefaultStreamVersion
|
|
2327
|
-
};
|
|
2328
|
-
await tryPublishMessagesAfterCommit(
|
|
2329
|
-
newEvents,
|
|
2330
|
-
eventStoreOptions?.hooks
|
|
2331
|
-
);
|
|
2332
|
-
return result;
|
|
2333
|
-
},
|
|
2334
|
-
streamExists: (streamName) => {
|
|
2335
|
-
const events = streams.get(streamName);
|
|
2336
|
-
return Promise.resolve(events !== void 0 && events.length > 0);
|
|
2337
|
-
}
|
|
2338
|
-
};
|
|
2339
|
-
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;
|
|
2340
1854
|
};
|
|
2341
1855
|
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
};
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
};
|
|
2405
|
-
}
|
|
2406
|
-
const expectedStreamVersion = handleOptions?.expectedStreamVersion ?? (aggregationResult.streamExists ? currentStreamVersion : STREAM_DOES_NOT_EXIST);
|
|
2407
|
-
const appendResult = await eventStore.appendToStream(
|
|
2408
|
-
streamName,
|
|
2409
|
-
eventsToAppend,
|
|
2410
|
-
{
|
|
2411
|
-
...handleOptions,
|
|
2412
|
-
expectedStreamVersion
|
|
2413
|
-
}
|
|
2414
|
-
);
|
|
2415
|
-
return {
|
|
2416
|
-
...appendResult,
|
|
2417
|
-
newEvents: eventsToAppend,
|
|
2418
|
-
newState: state
|
|
2419
|
-
};
|
|
2420
|
-
});
|
|
2421
|
-
return result;
|
|
2422
|
-
},
|
|
2423
|
-
fromCommandHandlerRetryOptions(
|
|
2424
|
-
handleOptions && "retry" in handleOptions ? handleOptions.retry : options.retry
|
|
2425
|
-
)
|
|
2426
|
-
);
|
|
2427
|
-
var withSession = (eventStore, callback) => {
|
|
2428
|
-
const sessionFactory = canCreateEventStoreSession(eventStore) ? eventStore : nulloSessionFactory(eventStore);
|
|
2429
|
-
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);
|
|
2430
1918
|
};
|
|
2431
1919
|
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
return CommandHandler(rest)(
|
|
2439
|
-
eventStore,
|
|
2440
|
-
id,
|
|
2441
|
-
deciders,
|
|
2442
|
-
handleOptions
|
|
2443
|
-
);
|
|
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);
|
|
2444
1926
|
};
|
|
2445
1927
|
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
for (const eventType of eventTypes) {
|
|
2486
|
-
if (!allHandlers.has(eventType)) allHandlers.set(eventType, []);
|
|
2487
|
-
allHandlers.set(eventType, [
|
|
2488
|
-
...allHandlers.get(eventType) ?? [],
|
|
2489
|
-
eventHandler
|
|
2490
|
-
]);
|
|
2491
|
-
}
|
|
2492
|
-
},
|
|
2493
|
-
dequeue: () => {
|
|
2494
|
-
const pending = pendingMessages;
|
|
2495
|
-
pendingMessages = [];
|
|
2496
|
-
return pending;
|
|
2497
|
-
}
|
|
2498
|
-
};
|
|
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
|
+
};
|
|
2499
1967
|
};
|
|
2500
1968
|
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
);
|
|
2508
|
-
if (duplicateRegistrations.length > 0) {
|
|
2509
|
-
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).
|
|
2510
1975
|
Ensure that:
|
|
2511
1976
|
${JSONSerializer.serialize(duplicateRegistrations)}
|
|
2512
1977
|
have different names`);
|
|
2513
|
-
|
|
2514
|
-
return inlineProjections2;
|
|
1978
|
+
return inlineProjections;
|
|
2515
1979
|
};
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
1980
|
+
const projection = (definition) => definition;
|
|
1981
|
+
const inlineProjections = (definitions) => definitions.map((definition) => ({
|
|
1982
|
+
type: "inline",
|
|
1983
|
+
projection: definition
|
|
2520
1984
|
}));
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
1985
|
+
const asyncProjections = (definitions) => definitions.map((definition) => ({
|
|
1986
|
+
type: "inline",
|
|
1987
|
+
projection: definition
|
|
2524
1988
|
}));
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
1989
|
+
const projections = {
|
|
1990
|
+
inline: inlineProjections,
|
|
1991
|
+
async: asyncProjections
|
|
2528
1992
|
};
|
|
2529
1993
|
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
};
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
var emptyHandlerResult = (nextExpectedStreamVersion = 0n) => ({
|
|
2553
|
-
newMessages: [],
|
|
2554
|
-
createdNewStream: false,
|
|
2555
|
-
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
|
|
2556
2016
|
});
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2017
|
+
const createInputMetadata = (originalMessageId, action) => ({
|
|
2018
|
+
originalMessageId,
|
|
2019
|
+
input: true,
|
|
2020
|
+
action
|
|
2561
2021
|
});
|
|
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
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
}
|
|
2681
|
-
const messageForDecide = hasWorkflowPrefix ? {
|
|
2682
|
-
...messageWithMetadata,
|
|
2683
|
-
type: messageType.replace(`${workflowName}:`, "")
|
|
2684
|
-
} : messageWithMetadata;
|
|
2685
|
-
const result2 = decide(messageForDecide, state);
|
|
2686
|
-
const inputMetadata = createInputMetadata(
|
|
2687
|
-
inputMessageId,
|
|
2688
|
-
aggregationResult.streamExists ? "Received" : "InitiatedBy"
|
|
2689
|
-
);
|
|
2690
|
-
const inputToStore = {
|
|
2691
|
-
type: `${workflowName}:${messageWithMetadata.type}`,
|
|
2692
|
-
data: messageWithMetadata.data,
|
|
2693
|
-
kind: messageWithMetadata.kind,
|
|
2694
|
-
metadata: inputMetadata
|
|
2695
|
-
};
|
|
2696
|
-
const outputMessages = (Array.isArray(result2) ? result2 : [result2]).filter((msg) => msg !== void 0 && msg !== null);
|
|
2697
|
-
const outputCommandTypes = options.outputs?.commands ?? [];
|
|
2698
|
-
const taggedOutputMessages = outputMessages.map((msg) => {
|
|
2699
|
-
const action = outputCommandTypes.includes(
|
|
2700
|
-
msg.type
|
|
2701
|
-
) ? "Sent" : "Published";
|
|
2702
|
-
return tagOutputMessage(msg, action);
|
|
2703
|
-
});
|
|
2704
|
-
const messagesToAppend = options.separateInputInboxFromProcessing && hasWorkflowPrefix ? [...taggedOutputMessages] : [inputToStore, ...taggedOutputMessages];
|
|
2705
|
-
if (messagesToAppend.length === 0) {
|
|
2706
|
-
return emptyHandlerResult(currentStreamVersion);
|
|
2707
|
-
}
|
|
2708
|
-
const expectedStreamVersion = handleOptions?.expectedStreamVersion ?? (aggregationResult.streamExists ? currentStreamVersion : STREAM_DOES_NOT_EXIST);
|
|
2709
|
-
const appendResult = await eventStore.appendToStream(
|
|
2710
|
-
streamName,
|
|
2711
|
-
// TODO: Fix this cast
|
|
2712
|
-
messagesToAppend,
|
|
2713
|
-
{
|
|
2714
|
-
...handleOptions,
|
|
2715
|
-
expectedStreamVersion
|
|
2716
|
-
}
|
|
2717
|
-
);
|
|
2718
|
-
return {
|
|
2719
|
-
...appendResult,
|
|
2720
|
-
newMessages: outputMessages
|
|
2721
|
-
};
|
|
2722
|
-
});
|
|
2723
|
-
return result;
|
|
2724
|
-
},
|
|
2725
|
-
fromWorkflowHandlerRetryOptions(
|
|
2726
|
-
handleOptions && "retry" in handleOptions ? handleOptions.retry : options.retry
|
|
2727
|
-
)
|
|
2728
|
-
);
|
|
2729
|
-
var withSession2 = (eventStore, callback) => {
|
|
2730
|
-
const sessionFactory = canCreateEventStoreSession(eventStore) ? eventStore : nulloSessionFactory(eventStore);
|
|
2731
|
-
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);
|
|
2732
2140
|
};
|
|
2733
2141
|
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2142
|
+
//#endregion
|
|
2143
|
+
//#region src/workflows/workflow.ts
|
|
2144
|
+
const Workflow = (workflow) => {
|
|
2145
|
+
return workflow;
|
|
2737
2146
|
};
|
|
2738
2147
|
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
if (messagesToAppend.length === 0) {
|
|
2793
|
-
return;
|
|
2794
|
-
}
|
|
2795
|
-
const workflowId = options.getWorkflowId(
|
|
2796
|
-
message2
|
|
2797
|
-
);
|
|
2798
|
-
if (!workflowId) return;
|
|
2799
|
-
const streamName = options.mapWorkflowId ? options.mapWorkflowId(workflowId) : workflowStreamName({
|
|
2800
|
-
workflowName: workflow.name,
|
|
2801
|
-
workflowId
|
|
2802
|
-
});
|
|
2803
|
-
await context.connection.messageStore.appendToStream(
|
|
2804
|
-
streamName,
|
|
2805
|
-
messagesToAppend
|
|
2806
|
-
);
|
|
2807
|
-
return;
|
|
2808
|
-
}
|
|
2809
|
-
return;
|
|
2810
|
-
}
|
|
2811
|
-
});
|
|
2812
|
-
};
|
|
2813
|
-
export {
|
|
2814
|
-
AssertionError,
|
|
2815
|
-
CommandHandler,
|
|
2816
|
-
CommandHandlerStreamVersionConflictRetryOptions,
|
|
2817
|
-
ConcurrencyError,
|
|
2818
|
-
ConcurrencyInMemoryDatabaseError,
|
|
2819
|
-
DATABASE_REQUIRED_ERROR_MESSAGE,
|
|
2820
|
-
DeciderCommandHandler,
|
|
2821
|
-
DeciderSpecification,
|
|
2822
|
-
EmmettError,
|
|
2823
|
-
ExpectedVersionConflictError,
|
|
2824
|
-
GlobalStreamCaughtUpType,
|
|
2825
|
-
IllegalStateError,
|
|
2826
|
-
InMemoryEventStoreDefaultStreamVersion,
|
|
2827
|
-
InMemoryProjectionSpec,
|
|
2828
|
-
InProcessLock,
|
|
2829
|
-
JSONCodec,
|
|
2830
|
-
JSONReplacer,
|
|
2831
|
-
JSONReplacers,
|
|
2832
|
-
JSONReviver,
|
|
2833
|
-
JSONRevivers,
|
|
2834
|
-
JSONSerializer,
|
|
2835
|
-
MessageProcessor,
|
|
2836
|
-
MessageProcessorType,
|
|
2837
|
-
NO_CONCURRENCY_CHECK,
|
|
2838
|
-
NoRetries,
|
|
2839
|
-
NotFoundError,
|
|
2840
|
-
STREAM_DOES_NOT_EXIST,
|
|
2841
|
-
STREAM_EXISTS,
|
|
2842
|
-
TaskProcessor,
|
|
2843
|
-
ValidationError,
|
|
2844
|
-
ValidationErrors,
|
|
2845
|
-
Workflow,
|
|
2846
|
-
WorkflowHandler,
|
|
2847
|
-
WorkflowHandlerStreamVersionConflictRetryOptions,
|
|
2848
|
-
WorkflowSpecification,
|
|
2849
|
-
WrapEventStore,
|
|
2850
|
-
argMatches,
|
|
2851
|
-
argValue,
|
|
2852
|
-
arrayUtils,
|
|
2853
|
-
assertDeepEqual,
|
|
2854
|
-
assertDefined,
|
|
2855
|
-
assertDoesNotThrow,
|
|
2856
|
-
assertEqual,
|
|
2857
|
-
assertExpectedVersionMatchesCurrent,
|
|
2858
|
-
assertFails,
|
|
2859
|
-
assertFalse,
|
|
2860
|
-
assertIsNotNull,
|
|
2861
|
-
assertIsNull,
|
|
2862
|
-
assertMatches,
|
|
2863
|
-
assertNotDeepEqual,
|
|
2864
|
-
assertNotEmptyString,
|
|
2865
|
-
assertNotEqual,
|
|
2866
|
-
assertOk,
|
|
2867
|
-
assertPositiveNumber,
|
|
2868
|
-
assertRejects,
|
|
2869
|
-
assertThat,
|
|
2870
|
-
assertThatArray,
|
|
2871
|
-
assertThrows,
|
|
2872
|
-
assertThrowsAsync,
|
|
2873
|
-
assertTrue,
|
|
2874
|
-
assertUnsignedBigInt,
|
|
2875
|
-
asyncAwaiter,
|
|
2876
|
-
asyncProjections,
|
|
2877
|
-
asyncRetry,
|
|
2878
|
-
bigInt,
|
|
2879
|
-
bigIntProcessorCheckpoint,
|
|
2880
|
-
canCreateEventStoreSession,
|
|
2881
|
-
caughtUpEventFrom,
|
|
2882
|
-
command,
|
|
2883
|
-
composeJSONReplacers,
|
|
2884
|
-
composeJSONRevivers,
|
|
2885
|
-
deepEquals,
|
|
2886
|
-
defaultProcessingMessageProcessingScope,
|
|
2887
|
-
defaultProcessorPartition,
|
|
2888
|
-
defaultProcessorVersion,
|
|
2889
|
-
defaultTag,
|
|
2890
|
-
delay,
|
|
2891
|
-
documentExists,
|
|
2892
|
-
downcastRecordedMessage,
|
|
2893
|
-
downcastRecordedMessages,
|
|
2894
|
-
emmettPrefix,
|
|
2895
|
-
event,
|
|
2896
|
-
eventInStream,
|
|
2897
|
-
eventsInStream,
|
|
2898
|
-
expectInMemoryDocuments,
|
|
2899
|
-
filterProjections,
|
|
2900
|
-
formatDateToUtcYYYYMMDD,
|
|
2901
|
-
forwardToMessageBus,
|
|
2902
|
-
getCheckpoint,
|
|
2903
|
-
getInMemoryDatabase,
|
|
2904
|
-
getInMemoryEventStore,
|
|
2905
|
-
getInMemoryMessageBus,
|
|
2906
|
-
getProcessorInstanceId,
|
|
2907
|
-
getProjectorId,
|
|
2908
|
-
getWorkflowId,
|
|
2909
|
-
globalStreamCaughtUp,
|
|
2910
|
-
globalTag,
|
|
2911
|
-
guardBoundedAccess,
|
|
2912
|
-
guardExclusiveAccess,
|
|
2913
|
-
guardInitializedOnce,
|
|
2914
|
-
handleInMemoryProjections,
|
|
2915
|
-
hashText,
|
|
2916
|
-
inMemoryCheckpointer,
|
|
2917
|
-
inMemoryMultiStreamProjection,
|
|
2918
|
-
inMemoryProjection,
|
|
2919
|
-
inMemoryProjector,
|
|
2920
|
-
inMemoryReactor,
|
|
2921
|
-
inMemorySingleStreamProjection,
|
|
2922
|
-
inlineProjections,
|
|
2923
|
-
isBigint,
|
|
2924
|
-
isEquatable,
|
|
2925
|
-
isErrorConstructor,
|
|
2926
|
-
isExpectedVersionConflictError,
|
|
2927
|
-
isGlobalStreamCaughtUp,
|
|
2928
|
-
isNotInternalEvent,
|
|
2929
|
-
isNumber,
|
|
2930
|
-
isPluginConfig,
|
|
2931
|
-
isString,
|
|
2932
|
-
isSubscriptionEvent,
|
|
2933
|
-
isSubset,
|
|
2934
|
-
isValidYYYYMMDD,
|
|
2935
|
-
jsonSerializer,
|
|
2936
|
-
matchesExpectedVersion,
|
|
2937
|
-
merge,
|
|
2938
|
-
message,
|
|
2939
|
-
newEventsInStream,
|
|
2940
|
-
nulloSessionFactory,
|
|
2941
|
-
onShutdown,
|
|
2942
|
-
parseBigIntProcessorCheckpoint,
|
|
2943
|
-
parseDateFromUtcYYYYMMDD,
|
|
2944
|
-
projection,
|
|
2945
|
-
projections,
|
|
2946
|
-
projector,
|
|
2947
|
-
reactor,
|
|
2948
|
-
reduceAsync,
|
|
2949
|
-
sum,
|
|
2950
|
-
toNormalizedString,
|
|
2951
|
-
tryPublishMessagesAfterCommit,
|
|
2952
|
-
unknownTag,
|
|
2953
|
-
upcastRecordedMessage,
|
|
2954
|
-
upcastRecordedMessages,
|
|
2955
|
-
verifyThat,
|
|
2956
|
-
wasMessageHandled,
|
|
2957
|
-
workflowOutputHandler,
|
|
2958
|
-
workflowProcessor,
|
|
2959
|
-
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
|
+
});
|
|
2960
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 };
|
|
2961
2205
|
//# sourceMappingURL=index.js.map
|