@reventlessdev/reventless-core 3.0.0-alpha.136 → 3.0.0-alpha.138
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/CHANGELOG.md +19 -0
- package/package.json +7 -7
- package/src/Message.res +43 -10
- package/src/Message.res.mjs +21 -4
- package/src/Projection.res +65 -46
- package/src/Projection.res.mjs +53 -49
- package/src/components/Aggregate/Aggregate_Callback.res +207 -6
- package/src/components/Aggregate/Aggregate_Callback.res.mjs +160 -10
- package/src/components/DcbEventLog/DcbEventLog_Operations.res +3 -0
- package/src/components/DcbEventLog/DcbEventLog_Operations.res.mjs +3 -1
- package/src/components/EventLog/EventLog.res +20 -1
- package/src/components/EventLog/EventLog_Adapter.res +3 -1
- package/src/components/EventLog/EventLog_Builder.res +4 -0
- package/src/components/EventLog/EventLog_Builder.res.mjs +3 -1
- package/src/components/EventLog/EventLog_Operations.res +11 -2
- package/src/components/EventLog/EventLog_Operations.res.mjs +6 -2
- package/src/components/EventLog/EventPublish_Callback.res +7 -0
- package/src/components/StateChangeSlice/StateChangeSlice_Callback.res +26 -6
- package/src/components/StateChangeSlice/StateChangeSlice_Callback.res.mjs +21 -6
- package/src/plugin/lifecycle/PluginBehavior.res +6 -0
- package/src/plugin/lifecycle/PluginBehavior.res.mjs +3 -0
- package/src/util/Logger.res +10 -0
- package/src/util/Logger.res.mjs +12 -2
- package/tests/aggregate/AggregateCacheTest.res +271 -0
- package/tests/aggregate/AggregateCacheTest.res.mjs +433 -0
- package/tests/aggregate/AggregateCallbackTest.res +4 -1
- package/tests/aggregate/AggregateCallbackTest.res.mjs +4 -1
- package/tests/aggregate/AggregateCausationTest.res +4 -1
- package/tests/aggregate/AggregateCausationTest.res.mjs +4 -1
- package/tests/aggregate/AggregateConflictTest.res +14 -3
- package/tests/aggregate/AggregateConflictTest.res.mjs +26 -3
- package/tests/aggregate/AggregateFixtures.res +10 -2
- package/tests/aggregate/AggregateFixtures.res.mjs +22 -2
- package/tests/aggregate/AggregateRejectionTest.res +4 -1
- package/tests/aggregate/AggregateRejectionTest.res.mjs +4 -1
- package/tests/aggregate/AggregateSnapshotTest.res +357 -0
- package/tests/aggregate/AggregateSnapshotTest.res.mjs +645 -0
- package/tests/eventlog/EventLogFixtures.res +13 -2
- package/tests/eventlog/EventLogFixtures.res.mjs +27 -3
- package/tests/logger/LoggerLazyTest.res +36 -0
- package/tests/logger/LoggerLazyTest.res.mjs +40 -0
|
@@ -0,0 +1,645 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as S from "sury/src/S.res.mjs";
|
|
4
|
+
import * as Stream from "@reventlessdev/rescript-effect/src/Stream.res.mjs";
|
|
5
|
+
import * as Stdlib_Dict from "@rescript/runtime/lib/es6/Stdlib_Dict.js";
|
|
6
|
+
import * as Id$Reventless from "@reventlessdev/reventless-spec/src/types/Id.res.mjs";
|
|
7
|
+
import * as Effect from "effect/Effect";
|
|
8
|
+
import * as Stream$1 from "effect/Stream";
|
|
9
|
+
import * as Aggregate_Callback$ReventlessCore from "../../src/components/Aggregate/Aggregate_Callback.res.mjs";
|
|
10
|
+
|
|
11
|
+
S.enableJson();
|
|
12
|
+
|
|
13
|
+
let name = "SnapshotTestAggregate";
|
|
14
|
+
|
|
15
|
+
let commandSchema = S.union([
|
|
16
|
+
S.schema(s => ({
|
|
17
|
+
TAG: "Add",
|
|
18
|
+
item: s.m(S.string)
|
|
19
|
+
})),
|
|
20
|
+
S.literal("Noop")
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
let eventSchema = S.schema(s => ({
|
|
24
|
+
TAG: "Added",
|
|
25
|
+
item: s.m(S.string)
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
let errorSchema = S.literal("Never");
|
|
29
|
+
|
|
30
|
+
let moduleUrl = import.meta.url;
|
|
31
|
+
|
|
32
|
+
function commandAuthorization(param) {
|
|
33
|
+
return "AllowAuthenticated";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let AggSpec = {
|
|
37
|
+
Id: undefined,
|
|
38
|
+
name: name,
|
|
39
|
+
commandSchema: commandSchema,
|
|
40
|
+
eventSchema: eventSchema,
|
|
41
|
+
errorSchema: errorSchema,
|
|
42
|
+
moduleUrl: moduleUrl,
|
|
43
|
+
commandAuthorization: commandAuthorization
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
let stateSchema = S.schema(s => ({
|
|
47
|
+
items: s.m(S.array(S.string))
|
|
48
|
+
}));
|
|
49
|
+
|
|
50
|
+
let initialState = {
|
|
51
|
+
items: []
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
let snapshot = {
|
|
55
|
+
interval: 10,
|
|
56
|
+
stateSchema: stateSchema
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
let moduleUrl$1 = import.meta.url;
|
|
60
|
+
|
|
61
|
+
function evolve(state, event) {
|
|
62
|
+
return {
|
|
63
|
+
items: state.items.concat([event.item])
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function decide(_state, command) {
|
|
68
|
+
if (typeof command !== "object") {
|
|
69
|
+
return {
|
|
70
|
+
TAG: "Ok",
|
|
71
|
+
_0: []
|
|
72
|
+
};
|
|
73
|
+
} else {
|
|
74
|
+
return {
|
|
75
|
+
TAG: "Ok",
|
|
76
|
+
_0: [{
|
|
77
|
+
TAG: "Added",
|
|
78
|
+
item: command.item
|
|
79
|
+
}]
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let TestBehavior = {
|
|
85
|
+
Spec: undefined,
|
|
86
|
+
stateSchema: stateSchema,
|
|
87
|
+
initialState: initialState,
|
|
88
|
+
snapshot: snapshot,
|
|
89
|
+
moduleUrl: moduleUrl$1,
|
|
90
|
+
evolve: evolve,
|
|
91
|
+
decide: decide
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
let testMeta = {
|
|
95
|
+
service: "SnapshotTestAggregate",
|
|
96
|
+
time: "2024-01-01T00:00:00Z",
|
|
97
|
+
ip: "127.0.0.1",
|
|
98
|
+
user: "test-user",
|
|
99
|
+
msgId: "msg-1",
|
|
100
|
+
correlationId: "corr-1"
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
function makeMockEL() {
|
|
104
|
+
let stored = {
|
|
105
|
+
contents: []
|
|
106
|
+
};
|
|
107
|
+
let snapshots = {};
|
|
108
|
+
let replayCount = {
|
|
109
|
+
contents: 0
|
|
110
|
+
};
|
|
111
|
+
let lastFromSeq = {
|
|
112
|
+
contents: -1
|
|
113
|
+
};
|
|
114
|
+
let snapReads = {
|
|
115
|
+
contents: 0
|
|
116
|
+
};
|
|
117
|
+
let snapWrites = {
|
|
118
|
+
contents: 0
|
|
119
|
+
};
|
|
120
|
+
let failWrite = {
|
|
121
|
+
contents: false
|
|
122
|
+
};
|
|
123
|
+
let eventsFor = id => stored.contents.filter(e => e.id === id);
|
|
124
|
+
let appendFn = async (seqNr, id, newEvents) => {
|
|
125
|
+
if (seqNr !== eventsFor(id).length) {
|
|
126
|
+
return {
|
|
127
|
+
TAG: "Error",
|
|
128
|
+
_0: "Conflict"
|
|
129
|
+
};
|
|
130
|
+
} else {
|
|
131
|
+
stored.contents = stored.contents.concat(newEvents);
|
|
132
|
+
return {
|
|
133
|
+
TAG: "Ok",
|
|
134
|
+
_0: undefined
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
let replayStreamFn = (id, fromSeqOpt) => {
|
|
139
|
+
let fromSeq = fromSeqOpt !== undefined ? fromSeqOpt : 0;
|
|
140
|
+
replayCount.contents = replayCount.contents + 1 | 0;
|
|
141
|
+
lastFromSeq.contents = fromSeq;
|
|
142
|
+
return Stream$1.fromIterable(eventsFor(id).map(e => e.event).filter((param, i) => i >= fromSeq));
|
|
143
|
+
};
|
|
144
|
+
let latestSnapshotFn = async id => {
|
|
145
|
+
snapReads.contents = snapReads.contents + 1 | 0;
|
|
146
|
+
return {
|
|
147
|
+
TAG: "Ok",
|
|
148
|
+
_0: snapshots[id]
|
|
149
|
+
};
|
|
150
|
+
};
|
|
151
|
+
let writeSnapshotFn = async (id, snap) => {
|
|
152
|
+
if (failWrite.contents) {
|
|
153
|
+
failWrite.contents = false;
|
|
154
|
+
return {
|
|
155
|
+
TAG: "Error",
|
|
156
|
+
_0: "mock write failure"
|
|
157
|
+
};
|
|
158
|
+
} else {
|
|
159
|
+
snapWrites.contents = snapWrites.contents + 1 | 0;
|
|
160
|
+
snapshots[id] = snap;
|
|
161
|
+
return {
|
|
162
|
+
TAG: "Ok",
|
|
163
|
+
_0: undefined
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
return {
|
|
168
|
+
appendFn: appendFn,
|
|
169
|
+
replayStreamFn: replayStreamFn,
|
|
170
|
+
latestSnapshotFn: latestSnapshotFn,
|
|
171
|
+
writeSnapshotFn: writeSnapshotFn,
|
|
172
|
+
getAll: () => stored.contents,
|
|
173
|
+
getSnapshot: id => snapshots[id],
|
|
174
|
+
setSnapshot: (id, snap) => {
|
|
175
|
+
snapshots[id] = snap;
|
|
176
|
+
},
|
|
177
|
+
replayCallCount: replayCount,
|
|
178
|
+
lastReplayFromSeq: lastFromSeq,
|
|
179
|
+
snapshotReadCount: snapReads,
|
|
180
|
+
snapshotWriteCount: snapWrites,
|
|
181
|
+
failNextWrite: failWrite,
|
|
182
|
+
reset: () => {
|
|
183
|
+
stored.contents = [];
|
|
184
|
+
Stdlib_Dict.forEachWithKey(snapshots, (param, k) => Stdlib_Dict.$$delete(snapshots, k));
|
|
185
|
+
replayCount.contents = 0;
|
|
186
|
+
lastFromSeq.contents = -1;
|
|
187
|
+
snapReads.contents = 0;
|
|
188
|
+
snapWrites.contents = 0;
|
|
189
|
+
failWrite.contents = false;
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
let mock = makeMockEL();
|
|
195
|
+
|
|
196
|
+
function MakeOps(B) {
|
|
197
|
+
let Spec_name = B.name;
|
|
198
|
+
let Spec = {
|
|
199
|
+
Id: undefined,
|
|
200
|
+
name: Spec_name,
|
|
201
|
+
eventSchema: eventSchema
|
|
202
|
+
};
|
|
203
|
+
let make = (param, param$1) => 0;
|
|
204
|
+
let EventLog = {
|
|
205
|
+
Spec: Spec,
|
|
206
|
+
make: make
|
|
207
|
+
};
|
|
208
|
+
let eventLog_append = mock.appendFn;
|
|
209
|
+
let eventLog_replay = async id => await Effect.runPromise(Stream.runCollect(mock.replayStreamFn(id, undefined)));
|
|
210
|
+
let eventLog_replayStream = mock.replayStreamFn;
|
|
211
|
+
let eventLog_appendStream = (_startingSeqNr, _id, stream) => Stream$1.runDrain(stream);
|
|
212
|
+
let eventLog_latestSnapshot = mock.latestSnapshotFn;
|
|
213
|
+
let eventLog_writeSnapshot = mock.writeSnapshotFn;
|
|
214
|
+
let eventLog = {
|
|
215
|
+
append: eventLog_append,
|
|
216
|
+
replay: eventLog_replay,
|
|
217
|
+
replayStream: eventLog_replayStream,
|
|
218
|
+
appendStream: eventLog_appendStream,
|
|
219
|
+
latestSnapshot: eventLog_latestSnapshot,
|
|
220
|
+
writeSnapshot: eventLog_writeSnapshot
|
|
221
|
+
};
|
|
222
|
+
return {
|
|
223
|
+
Spec: undefined,
|
|
224
|
+
EventLog: EventLog,
|
|
225
|
+
eventLog: eventLog
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
let Spec = {
|
|
230
|
+
Id: undefined,
|
|
231
|
+
name: "SnapshotMockEventLog",
|
|
232
|
+
eventSchema: eventSchema
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
function make(param, param$1) {
|
|
236
|
+
return 0;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
let EventLog = {
|
|
240
|
+
Spec: Spec,
|
|
241
|
+
make: make
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
let eventLog_append = mock.appendFn;
|
|
245
|
+
|
|
246
|
+
async function eventLog_replay(id) {
|
|
247
|
+
return await Effect.runPromise(Stream.runCollect(mock.replayStreamFn(id, undefined)));
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
let eventLog_replayStream = mock.replayStreamFn;
|
|
251
|
+
|
|
252
|
+
function eventLog_appendStream(_startingSeqNr, _id, stream) {
|
|
253
|
+
return Stream$1.runDrain(stream);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
let eventLog_latestSnapshot = mock.latestSnapshotFn;
|
|
257
|
+
|
|
258
|
+
let eventLog_writeSnapshot = mock.writeSnapshotFn;
|
|
259
|
+
|
|
260
|
+
let eventLog = {
|
|
261
|
+
append: eventLog_append,
|
|
262
|
+
replay: eventLog_replay,
|
|
263
|
+
replayStream: eventLog_replayStream,
|
|
264
|
+
appendStream: eventLog_appendStream,
|
|
265
|
+
latestSnapshot: eventLog_latestSnapshot,
|
|
266
|
+
writeSnapshot: eventLog_writeSnapshot
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
let TestOps = {
|
|
270
|
+
Spec: undefined,
|
|
271
|
+
EventLog: EventLog,
|
|
272
|
+
eventLog: eventLog
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
let TestHandler = Aggregate_Callback$ReventlessCore.Make({
|
|
276
|
+
Id: {
|
|
277
|
+
schema: Id$Reventless.StringPure.schema,
|
|
278
|
+
make: prim => prim,
|
|
279
|
+
makeFromString: prim => prim,
|
|
280
|
+
toString: prim => prim,
|
|
281
|
+
cmp: Id$Reventless.StringPure.cmp
|
|
282
|
+
},
|
|
283
|
+
name: name,
|
|
284
|
+
eventSchema: eventSchema,
|
|
285
|
+
errorSchema: errorSchema,
|
|
286
|
+
commandSchema: commandSchema,
|
|
287
|
+
moduleUrl: moduleUrl,
|
|
288
|
+
commandAuthorization: commandAuthorization
|
|
289
|
+
})({
|
|
290
|
+
initialState: initialState,
|
|
291
|
+
evolve: evolve,
|
|
292
|
+
decide: decide,
|
|
293
|
+
snapshot: snapshot,
|
|
294
|
+
moduleUrl: moduleUrl$1
|
|
295
|
+
})({
|
|
296
|
+
Spec: {
|
|
297
|
+
Id: {
|
|
298
|
+
schema: Id$Reventless.StringPure.schema,
|
|
299
|
+
make: prim => prim,
|
|
300
|
+
makeFromString: prim => prim,
|
|
301
|
+
toString: prim => prim,
|
|
302
|
+
cmp: Id$Reventless.StringPure.cmp
|
|
303
|
+
},
|
|
304
|
+
name: name,
|
|
305
|
+
eventSchema: eventSchema,
|
|
306
|
+
errorSchema: errorSchema,
|
|
307
|
+
commandSchema: commandSchema,
|
|
308
|
+
moduleUrl: moduleUrl,
|
|
309
|
+
commandAuthorization: commandAuthorization
|
|
310
|
+
},
|
|
311
|
+
EventLog: {
|
|
312
|
+
Spec: {
|
|
313
|
+
Id: {
|
|
314
|
+
schema: Id$Reventless.StringPure.schema,
|
|
315
|
+
make: prim => prim,
|
|
316
|
+
makeFromString: prim => prim,
|
|
317
|
+
toString: prim => prim,
|
|
318
|
+
cmp: Id$Reventless.StringPure.cmp
|
|
319
|
+
},
|
|
320
|
+
name: "SnapshotMockEventLog",
|
|
321
|
+
eventSchema: eventSchema
|
|
322
|
+
},
|
|
323
|
+
make: make
|
|
324
|
+
},
|
|
325
|
+
eventLog: eventLog
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
let stateSchema$1 = S.schema(s => ({
|
|
329
|
+
items: s.m(S.array(S.string))
|
|
330
|
+
}));
|
|
331
|
+
|
|
332
|
+
let initialState$1 = {
|
|
333
|
+
items: []
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
let moduleUrl$2 = import.meta.url;
|
|
337
|
+
|
|
338
|
+
function evolve$1(state, event) {
|
|
339
|
+
return {
|
|
340
|
+
items: state.items.concat([event.item])
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function decide$1(_state, command) {
|
|
345
|
+
if (typeof command !== "object") {
|
|
346
|
+
return {
|
|
347
|
+
TAG: "Ok",
|
|
348
|
+
_0: []
|
|
349
|
+
};
|
|
350
|
+
} else {
|
|
351
|
+
return {
|
|
352
|
+
TAG: "Ok",
|
|
353
|
+
_0: [{
|
|
354
|
+
TAG: "Added",
|
|
355
|
+
item: command.item
|
|
356
|
+
}]
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
let PlainBehavior = {
|
|
362
|
+
Spec: undefined,
|
|
363
|
+
stateSchema: stateSchema$1,
|
|
364
|
+
initialState: initialState$1,
|
|
365
|
+
snapshot: undefined,
|
|
366
|
+
moduleUrl: moduleUrl$2,
|
|
367
|
+
evolve: evolve$1,
|
|
368
|
+
decide: decide$1
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
let Spec$1 = {
|
|
372
|
+
Id: undefined,
|
|
373
|
+
name: "PlainMockEventLog",
|
|
374
|
+
eventSchema: eventSchema
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
function make$1(param, param$1) {
|
|
378
|
+
return 0;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
let EventLog$1 = {
|
|
382
|
+
Spec: Spec$1,
|
|
383
|
+
make: make$1
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
let eventLog_append$1 = mock.appendFn;
|
|
387
|
+
|
|
388
|
+
async function eventLog_replay$1(id) {
|
|
389
|
+
return await Effect.runPromise(Stream.runCollect(mock.replayStreamFn(id, undefined)));
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
let eventLog_replayStream$1 = mock.replayStreamFn;
|
|
393
|
+
|
|
394
|
+
function eventLog_appendStream$1(_startingSeqNr, _id, stream) {
|
|
395
|
+
return Stream$1.runDrain(stream);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
let eventLog_latestSnapshot$1 = mock.latestSnapshotFn;
|
|
399
|
+
|
|
400
|
+
let eventLog_writeSnapshot$1 = mock.writeSnapshotFn;
|
|
401
|
+
|
|
402
|
+
let eventLog$1 = {
|
|
403
|
+
append: eventLog_append$1,
|
|
404
|
+
replay: eventLog_replay$1,
|
|
405
|
+
replayStream: eventLog_replayStream$1,
|
|
406
|
+
appendStream: eventLog_appendStream$1,
|
|
407
|
+
latestSnapshot: eventLog_latestSnapshot$1,
|
|
408
|
+
writeSnapshot: eventLog_writeSnapshot$1
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
let PlainOps = {
|
|
412
|
+
Spec: undefined,
|
|
413
|
+
EventLog: EventLog$1,
|
|
414
|
+
eventLog: eventLog$1
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
let PlainHandler = Aggregate_Callback$ReventlessCore.Make({
|
|
418
|
+
Id: {
|
|
419
|
+
schema: Id$Reventless.StringPure.schema,
|
|
420
|
+
make: prim => prim,
|
|
421
|
+
makeFromString: prim => prim,
|
|
422
|
+
toString: prim => prim,
|
|
423
|
+
cmp: Id$Reventless.StringPure.cmp
|
|
424
|
+
},
|
|
425
|
+
name: name,
|
|
426
|
+
eventSchema: eventSchema,
|
|
427
|
+
errorSchema: errorSchema,
|
|
428
|
+
commandSchema: commandSchema,
|
|
429
|
+
moduleUrl: moduleUrl,
|
|
430
|
+
commandAuthorization: commandAuthorization
|
|
431
|
+
})({
|
|
432
|
+
initialState: initialState$1,
|
|
433
|
+
evolve: evolve$1,
|
|
434
|
+
decide: decide$1,
|
|
435
|
+
snapshot: undefined,
|
|
436
|
+
moduleUrl: moduleUrl$2
|
|
437
|
+
})({
|
|
438
|
+
Spec: {
|
|
439
|
+
Id: {
|
|
440
|
+
schema: Id$Reventless.StringPure.schema,
|
|
441
|
+
make: prim => prim,
|
|
442
|
+
makeFromString: prim => prim,
|
|
443
|
+
toString: prim => prim,
|
|
444
|
+
cmp: Id$Reventless.StringPure.cmp
|
|
445
|
+
},
|
|
446
|
+
name: name,
|
|
447
|
+
eventSchema: eventSchema,
|
|
448
|
+
errorSchema: errorSchema,
|
|
449
|
+
commandSchema: commandSchema,
|
|
450
|
+
moduleUrl: moduleUrl,
|
|
451
|
+
commandAuthorization: commandAuthorization
|
|
452
|
+
},
|
|
453
|
+
EventLog: {
|
|
454
|
+
Spec: {
|
|
455
|
+
Id: {
|
|
456
|
+
schema: Id$Reventless.StringPure.schema,
|
|
457
|
+
make: prim => prim,
|
|
458
|
+
makeFromString: prim => prim,
|
|
459
|
+
toString: prim => prim,
|
|
460
|
+
cmp: Id$Reventless.StringPure.cmp
|
|
461
|
+
},
|
|
462
|
+
name: "PlainMockEventLog",
|
|
463
|
+
eventSchema: eventSchema
|
|
464
|
+
},
|
|
465
|
+
make: make$1
|
|
466
|
+
},
|
|
467
|
+
eventLog: eventLog$1
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
function makeItem(idOpt, reference, command) {
|
|
471
|
+
let id = idOpt !== undefined ? idOpt : "agg-1";
|
|
472
|
+
return {
|
|
473
|
+
command: {
|
|
474
|
+
id: id,
|
|
475
|
+
meta: testMeta,
|
|
476
|
+
command: command
|
|
477
|
+
},
|
|
478
|
+
reference: reference
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
async function run(handler, idOpt, command) {
|
|
483
|
+
let id = idOpt !== undefined ? idOpt : "agg-1";
|
|
484
|
+
return await Effect.runPromise(handler(Stream$1.fromIterable([makeItem(id, "ref", command)])));
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function tick() {
|
|
488
|
+
return Promise.resolve().then(() => Promise.resolve());
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
globalThis.beforeEach(() => {
|
|
492
|
+
mock.reset();
|
|
493
|
+
TestHandler.resetCache();
|
|
494
|
+
PlainHandler.resetCache();
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
globalThis.describe("Aggregate_Callback — snapshots:", () => {
|
|
498
|
+
globalThis.test("writes a snapshot each time an interval boundary is crossed", async () => {
|
|
499
|
+
for (let i = 1; i <= 25; ++i) {
|
|
500
|
+
await run(TestHandler.handleCommands, undefined, {
|
|
501
|
+
TAG: "Add",
|
|
502
|
+
item: `i` + i.toString()
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
await tick();
|
|
506
|
+
globalThis.expect(mock.snapshotWriteCount.contents).toBe(2);
|
|
507
|
+
let snap = mock.getSnapshot("agg-1");
|
|
508
|
+
if (snap !== undefined) {
|
|
509
|
+
globalThis.expect(snap.seqNr).toBe(20);
|
|
510
|
+
} else {
|
|
511
|
+
globalThis.expect("expected a snapshot").toBe("none");
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
globalThis.test("cold replay seeds from the snapshot and reads only the delta", async () => {
|
|
515
|
+
for (let i = 1; i <= 12; ++i) {
|
|
516
|
+
await run(TestHandler.handleCommands, undefined, {
|
|
517
|
+
TAG: "Add",
|
|
518
|
+
item: `i` + i.toString()
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
await tick();
|
|
522
|
+
TestHandler.resetCache();
|
|
523
|
+
mock.replayCallCount.contents = 0;
|
|
524
|
+
await run(TestHandler.handleCommands, undefined, {
|
|
525
|
+
TAG: "Add",
|
|
526
|
+
item: "i13"
|
|
527
|
+
});
|
|
528
|
+
globalThis.expect(mock.snapshotReadCount.contents >= 1).toBe(true);
|
|
529
|
+
globalThis.expect(mock.lastReplayFromSeq.contents).toBe(10);
|
|
530
|
+
globalThis.expect(mock.getAll().length).toBe(13);
|
|
531
|
+
});
|
|
532
|
+
globalThis.test("state seeded from snapshot equals a full-replay fold", async () => {
|
|
533
|
+
for (let i = 1; i <= 15; ++i) {
|
|
534
|
+
await run(TestHandler.handleCommands, undefined, {
|
|
535
|
+
TAG: "Add",
|
|
536
|
+
item: `x` + i.toString()
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
await tick();
|
|
540
|
+
TestHandler.resetCache();
|
|
541
|
+
await run(TestHandler.handleCommands, undefined, "Noop");
|
|
542
|
+
let seededSnap = mock.getSnapshot("agg-1");
|
|
543
|
+
for (let i$1 = 16; i$1 <= 20; ++i$1) {
|
|
544
|
+
await run(TestHandler.handleCommands, undefined, {
|
|
545
|
+
TAG: "Add",
|
|
546
|
+
item: `x` + i$1.toString()
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
await tick();
|
|
550
|
+
let snap = mock.getSnapshot("agg-1");
|
|
551
|
+
if (snap !== undefined) {
|
|
552
|
+
globalThis.expect(snap.seqNr).toBe(20);
|
|
553
|
+
let decoded = S.parseJsonOrThrow(snap.state, stateSchema);
|
|
554
|
+
globalThis.expect(decoded.items.length).toBe(20);
|
|
555
|
+
} else {
|
|
556
|
+
globalThis.expect("expected snapshot at 20").toBe("none");
|
|
557
|
+
}
|
|
558
|
+
if (seededSnap !== undefined) {
|
|
559
|
+
globalThis.expect(seededSnap.seqNr).toBe(10);
|
|
560
|
+
} else {
|
|
561
|
+
globalThis.expect("expected seeded snapshot").toBe("none");
|
|
562
|
+
}
|
|
563
|
+
});
|
|
564
|
+
globalThis.test("schema-drift snapshot is ignored → full replay from 0", async () => {
|
|
565
|
+
for (let i = 1; i <= 12; ++i) {
|
|
566
|
+
await run(TestHandler.handleCommands, undefined, {
|
|
567
|
+
TAG: "Add",
|
|
568
|
+
item: `i` + i.toString()
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
await tick();
|
|
572
|
+
let snap = mock.getSnapshot("agg-1");
|
|
573
|
+
if (snap !== undefined) {
|
|
574
|
+
mock.setSnapshot("agg-1", {
|
|
575
|
+
seqNr: snap.seqNr,
|
|
576
|
+
state: snap.state,
|
|
577
|
+
schemaHash: "STALE-HASH"
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
TestHandler.resetCache();
|
|
581
|
+
mock.replayCallCount.contents = 0;
|
|
582
|
+
await run(TestHandler.handleCommands, undefined, "Noop");
|
|
583
|
+
globalThis.expect(mock.lastReplayFromSeq.contents).toBe(0);
|
|
584
|
+
});
|
|
585
|
+
globalThis.test("snapshot write failure does not fail the command", async () => {
|
|
586
|
+
for (let i = 1; i <= 9; ++i) {
|
|
587
|
+
await run(TestHandler.handleCommands, undefined, {
|
|
588
|
+
TAG: "Add",
|
|
589
|
+
item: `i` + i.toString()
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
mock.failNextWrite.contents = true;
|
|
593
|
+
let result = await run(TestHandler.handleCommands, undefined, {
|
|
594
|
+
TAG: "Add",
|
|
595
|
+
item: "i10"
|
|
596
|
+
});
|
|
597
|
+
await tick();
|
|
598
|
+
globalThis.expect(result).toEqual([{
|
|
599
|
+
TAG: "Ok",
|
|
600
|
+
_0: "ref"
|
|
601
|
+
}]);
|
|
602
|
+
globalThis.expect(mock.getAll().length).toBe(10);
|
|
603
|
+
globalThis.expect(mock.getSnapshot("agg-1")).toEqual(undefined);
|
|
604
|
+
});
|
|
605
|
+
globalThis.test("disabled aggregate does zero snapshot reads and writes", async () => {
|
|
606
|
+
for (let i = 1; i <= 12; ++i) {
|
|
607
|
+
await run(PlainHandler.handleCommands, undefined, {
|
|
608
|
+
TAG: "Add",
|
|
609
|
+
item: `i` + i.toString()
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
await tick();
|
|
613
|
+
PlainHandler.resetCache();
|
|
614
|
+
await run(PlainHandler.handleCommands, undefined, "Noop");
|
|
615
|
+
globalThis.expect([
|
|
616
|
+
mock.snapshotReadCount.contents,
|
|
617
|
+
mock.snapshotWriteCount.contents
|
|
618
|
+
]).toEqual([
|
|
619
|
+
0,
|
|
620
|
+
0
|
|
621
|
+
]);
|
|
622
|
+
globalThis.expect(mock.lastReplayFromSeq.contents).toBe(0);
|
|
623
|
+
});
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
let OuterEventLog;
|
|
627
|
+
|
|
628
|
+
export {
|
|
629
|
+
AggSpec,
|
|
630
|
+
TestBehavior,
|
|
631
|
+
testMeta,
|
|
632
|
+
makeMockEL,
|
|
633
|
+
mock,
|
|
634
|
+
OuterEventLog,
|
|
635
|
+
MakeOps,
|
|
636
|
+
TestOps,
|
|
637
|
+
TestHandler,
|
|
638
|
+
PlainBehavior,
|
|
639
|
+
PlainOps,
|
|
640
|
+
PlainHandler,
|
|
641
|
+
makeItem,
|
|
642
|
+
run,
|
|
643
|
+
tick,
|
|
644
|
+
}
|
|
645
|
+
/* Not a pure module */
|
|
@@ -17,6 +17,7 @@ module ItemEventLogSpec = {
|
|
|
17
17
|
// ─────────────────────────────────────────────────────────────
|
|
18
18
|
|
|
19
19
|
let storedEvents: ref<dict<array<JSON.t>>> = ref(Dict.make())
|
|
20
|
+
let storedSnapshots: ref<dict<EventLog.snapshot>> = ref(Dict.make())
|
|
20
21
|
let failNextAppend = ref(false)
|
|
21
22
|
// Inject N transient failures: each call decrements the counter and returns ThrottlingException
|
|
22
23
|
let failNextAppendsWithTransient: ref<int> = ref(0)
|
|
@@ -41,8 +42,12 @@ let mockStorage: EventLog_Adapter.operations = {
|
|
|
41
42
|
replay: async id => {
|
|
42
43
|
storedEvents.contents->Dict.get(id)->Option.getOr([])
|
|
43
44
|
},
|
|
44
|
-
replayStream: id =>
|
|
45
|
-
storedEvents.contents
|
|
45
|
+
replayStream: (id, ~fromSeq=0) =>
|
|
46
|
+
storedEvents.contents
|
|
47
|
+
->Dict.get(id)
|
|
48
|
+
->Option.getOr([])
|
|
49
|
+
->Array.slice(~start=fromSeq)
|
|
50
|
+
->Stream.fromIterable,
|
|
46
51
|
appendStream: (_startingSeqNr, id, stream) =>
|
|
47
52
|
stream->Stream.runForEach(json =>
|
|
48
53
|
Effect.sync(() => {
|
|
@@ -50,6 +55,11 @@ let mockStorage: EventLog_Adapter.operations = {
|
|
|
50
55
|
storedEvents.contents->Dict.set(id, existing->Array.concat([json]))
|
|
51
56
|
})
|
|
52
57
|
),
|
|
58
|
+
latestSnapshot: async id => Ok(storedSnapshots.contents->Dict.get(id)),
|
|
59
|
+
writeSnapshot: async (id, snap) => {
|
|
60
|
+
storedSnapshots.contents->Dict.set(id, snap)
|
|
61
|
+
Ok()
|
|
62
|
+
},
|
|
53
63
|
}
|
|
54
64
|
|
|
55
65
|
// ─────────────────────────────────────────────────────────────
|
|
@@ -106,6 +116,7 @@ let makeEvent' = (id, event) => {
|
|
|
106
116
|
|
|
107
117
|
let reset = () => {
|
|
108
118
|
storedEvents := Dict.make()
|
|
119
|
+
storedSnapshots := Dict.make()
|
|
109
120
|
failNextAppend := false
|
|
110
121
|
failNextAppendsWithTransient := 0
|
|
111
122
|
appendCallCount := 0
|