@reventlessdev/reventless-core 3.0.0-alpha.261 → 3.0.0-alpha.263
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 +31 -0
- package/package.json +9 -9
- package/src/adapter/Secrets/Secrets_Node_Backend.res +60 -0
- package/src/adapter/Secrets/Secrets_Node_Backend.res.mjs +69 -0
- package/src/admin/Platform_Admin_Structure.res.mjs +1 -1
- package/src/components/Aggregate/Aggregate_Builder.res.mjs +1 -1
- package/src/components/CommandGenerator/CommandGenerator.res +4 -4
- package/src/components/CommandGenerator/CommandGenerator_Callback.res +13 -20
- package/src/components/CommandGenerator/CommandGenerator_Callback.res.mjs +3 -24
- package/src/components/Dcb/Dcb_Builder.res +45 -49
- package/src/components/Dcb/Dcb_Builder.res.mjs +31 -40
- package/src/components/StateChangeSlice/StateChangeSlice.res +2 -0
- package/src/components/StateChangeSlice/StateChangeSlice_Builder.res +6 -0
- package/src/components/StateChangeSlice/StateChangeSlice_Builder.res.mjs +4 -4
- package/src/components/StateChangeSlice/StateChangeSlice_Callback.res +51 -22
- package/src/components/StateChangeSlice/StateChangeSlice_Callback.res.mjs +47 -25
- package/src/plugin/component/Plugin_Structure.res +101 -4
- package/src/plugin/component/Plugin_Structure.res.mjs +82 -9
- package/tests/Secrets_Node_BackendTest.res +141 -0
- package/tests/Secrets_Node_BackendTest.res.mjs +152 -0
- package/tests/adapter/RuntimeExtensionHookReachTest.res.mjs +1 -1
- package/tests/admin/Platform_Admin_StructureTest.res +12 -3
- package/tests/admin/Platform_Admin_StructureTest.res.mjs +4 -3
- package/tests/commandgenerator/CommandGeneratorCallbackTest.res +1 -1
- package/tests/commandgenerator/CommandGeneratorCallbackTest.res.mjs +1 -1
- package/tests/commandgenerator/CommandGeneratorFixtures.res +2 -0
- package/tests/commandgenerator/CommandGeneratorFixtures.res.mjs +17 -3
- package/tests/commandgenerator/OwnerStampingTest.res.mjs +2 -2
- package/tests/commandtopic/CommandOutcomeHookTest.res.mjs +1 -1
- package/tests/dcb/DcbFixtures.res +18 -1
- package/tests/dcb/DcbFixtures.res.mjs +14 -0
- package/tests/dcb/DcbScopeInferenceTest.res +234 -0
- package/tests/dcb/DcbScopeInferenceTest.res.mjs +425 -13
- package/tests/dcb/DcbStateChangeSliceTest.res.mjs +22 -22
- package/tests/dcb/DcbTagTest.res +81 -68
- package/tests/dcb/DcbTagTest.res.mjs +88 -89
- package/tests/plugin/PluginStructureAccessTest.res +1 -0
- package/tests/plugin/PluginStructureAccessTest.res.mjs +1 -1
- package/tests/plugin/PluginStructureTest.res +47 -0
- package/tests/plugin/PluginStructureTest.res.mjs +50 -10
|
@@ -201,7 +201,7 @@ globalThis.beforeEach(() => {
|
|
|
201
201
|
globalThis.describe("StateChangeSlice_Callback:", () => {
|
|
202
202
|
globalThis.describe("handleCommands - happy path", () => {
|
|
203
203
|
globalThis.test("CreateItem on empty log succeeds", async () => {
|
|
204
|
-
let results = await Effect.runPromise(TestHandler.handleCommands(undefined, undefined, testDcbEventLog, Stream.fromIterable([makeTopicItem("ref-1", {
|
|
204
|
+
let results = await Effect.runPromise(TestHandler.handleCommands(undefined, undefined, undefined, testDcbEventLog, Stream.fromIterable([makeTopicItem("ref-1", {
|
|
205
205
|
TAG: "CreateItem",
|
|
206
206
|
itemId: "item-1",
|
|
207
207
|
name: "Test"
|
|
@@ -220,7 +220,7 @@ globalThis.describe("StateChangeSlice_Callback:", () => {
|
|
|
220
220
|
]);
|
|
221
221
|
});
|
|
222
222
|
globalThis.test("stored event carries only its entity tags (no framework provenance tag)", async () => {
|
|
223
|
-
await Effect.runPromise(TestHandler.handleCommands(undefined, undefined, testDcbEventLog, Stream.fromIterable([makeTopicItem("ref-1", {
|
|
223
|
+
await Effect.runPromise(TestHandler.handleCommands(undefined, undefined, undefined, testDcbEventLog, Stream.fromIterable([makeTopicItem("ref-1", {
|
|
224
224
|
TAG: "CreateItem",
|
|
225
225
|
itemId: "item-1",
|
|
226
226
|
name: "Test"
|
|
@@ -234,7 +234,7 @@ globalThis.describe("StateChangeSlice_Callback:", () => {
|
|
|
234
234
|
});
|
|
235
235
|
globalThis.describe("handleCommands - decide returns Ok([])", () => {
|
|
236
236
|
globalThis.test("NoOp returns Ok without storing events", async () => {
|
|
237
|
-
let results = await Effect.runPromise(TestHandler.handleCommands(undefined, undefined, testDcbEventLog, Stream.fromIterable([makeTopicItem("ref-noop", "NoOp")])));
|
|
237
|
+
let results = await Effect.runPromise(TestHandler.handleCommands(undefined, undefined, undefined, testDcbEventLog, Stream.fromIterable([makeTopicItem("ref-noop", "NoOp")])));
|
|
238
238
|
globalThis.expect([
|
|
239
239
|
results,
|
|
240
240
|
mock.getEvents().length
|
|
@@ -249,12 +249,12 @@ globalThis.describe("StateChangeSlice_Callback:", () => {
|
|
|
249
249
|
});
|
|
250
250
|
globalThis.describe("handleCommands - decide returns Error", () => {
|
|
251
251
|
globalThis.test("CreateItem when item exists returns Ok (business-rule violations ACK, not NACK)", async () => {
|
|
252
|
-
await Effect.runPromise(TestHandler.handleCommands(undefined, undefined, testDcbEventLog, Stream.fromIterable([makeTopicItem("ref-1", {
|
|
252
|
+
await Effect.runPromise(TestHandler.handleCommands(undefined, undefined, undefined, testDcbEventLog, Stream.fromIterable([makeTopicItem("ref-1", {
|
|
253
253
|
TAG: "CreateItem",
|
|
254
254
|
itemId: "item-1",
|
|
255
255
|
name: "Test"
|
|
256
256
|
})])));
|
|
257
|
-
let results = await Effect.runPromise(TestHandler.handleCommands(undefined, undefined, testDcbEventLog, Stream.fromIterable([makeTopicItem("ref-2", {
|
|
257
|
+
let results = await Effect.runPromise(TestHandler.handleCommands(undefined, undefined, undefined, testDcbEventLog, Stream.fromIterable([makeTopicItem("ref-2", {
|
|
258
258
|
TAG: "CreateItem",
|
|
259
259
|
itemId: "item-1",
|
|
260
260
|
name: "Duplicate"
|
|
@@ -268,7 +268,7 @@ globalThis.describe("StateChangeSlice_Callback:", () => {
|
|
|
268
268
|
globalThis.describe("handleCommands - retry on conflict", () => {
|
|
269
269
|
globalThis.test("retries and succeeds after 1 append failure", async () => {
|
|
270
270
|
mock.failNextAppends.contents = 1;
|
|
271
|
-
let results = await Effect.runPromise(TestHandler.handleCommands(undefined, undefined, testDcbEventLog, Stream.fromIterable([makeTopicItem("ref-1", {
|
|
271
|
+
let results = await Effect.runPromise(TestHandler.handleCommands(undefined, undefined, undefined, testDcbEventLog, Stream.fromIterable([makeTopicItem("ref-1", {
|
|
272
272
|
TAG: "CreateItem",
|
|
273
273
|
itemId: "item-1",
|
|
274
274
|
name: "Test"
|
|
@@ -286,7 +286,7 @@ globalThis.describe("StateChangeSlice_Callback:", () => {
|
|
|
286
286
|
});
|
|
287
287
|
globalThis.test("returns Error after retries exhausted (4 failures)", async () => {
|
|
288
288
|
mock.failNextAppends.contents = 4;
|
|
289
|
-
let results = await Effect.runPromise(TestHandler.handleCommands(undefined, undefined, testDcbEventLog, Stream.fromIterable([makeTopicItem("ref-1", {
|
|
289
|
+
let results = await Effect.runPromise(TestHandler.handleCommands(undefined, undefined, undefined, testDcbEventLog, Stream.fromIterable([makeTopicItem("ref-1", {
|
|
290
290
|
TAG: "CreateItem",
|
|
291
291
|
itemId: "item-1",
|
|
292
292
|
name: "Test"
|
|
@@ -305,7 +305,7 @@ globalThis.describe("StateChangeSlice_Callback:", () => {
|
|
|
305
305
|
});
|
|
306
306
|
globalThis.describe("handleCommands - read consistency (eventual-first, strong-on-retry)", () => {
|
|
307
307
|
globalThis.test("a conflict-free command reads eventually-consistent (no strong read)", async () => {
|
|
308
|
-
await Effect.runPromise(TestHandler.handleCommands(undefined, undefined, testDcbEventLog, Stream.fromIterable([makeTopicItem("ref-1", {
|
|
308
|
+
await Effect.runPromise(TestHandler.handleCommands(undefined, undefined, undefined, testDcbEventLog, Stream.fromIterable([makeTopicItem("ref-1", {
|
|
309
309
|
TAG: "CreateItem",
|
|
310
310
|
itemId: "item-1",
|
|
311
311
|
name: "Test"
|
|
@@ -314,7 +314,7 @@ globalThis.describe("StateChangeSlice_Callback:", () => {
|
|
|
314
314
|
});
|
|
315
315
|
globalThis.test("escalates to strong consistency on retry after a conflict", async () => {
|
|
316
316
|
mock.failNextAppends.contents = 1;
|
|
317
|
-
await Effect.runPromise(TestHandler.handleCommands(undefined, undefined, testDcbEventLog, Stream.fromIterable([makeTopicItem("ref-1", {
|
|
317
|
+
await Effect.runPromise(TestHandler.handleCommands(undefined, undefined, undefined, testDcbEventLog, Stream.fromIterable([makeTopicItem("ref-1", {
|
|
318
318
|
TAG: "CreateItem",
|
|
319
319
|
itemId: "item-1",
|
|
320
320
|
name: "Test"
|
|
@@ -326,7 +326,7 @@ globalThis.describe("StateChangeSlice_Callback:", () => {
|
|
|
326
326
|
});
|
|
327
327
|
globalThis.test("every retry after the first is strong (2 failures)", async () => {
|
|
328
328
|
mock.failNextAppends.contents = 2;
|
|
329
|
-
await Effect.runPromise(TestHandler.handleCommands(undefined, undefined, testDcbEventLog, Stream.fromIterable([makeTopicItem("ref-1", {
|
|
329
|
+
await Effect.runPromise(TestHandler.handleCommands(undefined, undefined, undefined, testDcbEventLog, Stream.fromIterable([makeTopicItem("ref-1", {
|
|
330
330
|
TAG: "CreateItem",
|
|
331
331
|
itemId: "item-1",
|
|
332
332
|
name: "Test"
|
|
@@ -340,7 +340,7 @@ globalThis.describe("StateChangeSlice_Callback:", () => {
|
|
|
340
340
|
});
|
|
341
341
|
globalThis.describe("handleCommands - read consistency (per-slice override)", () => {
|
|
342
342
|
globalThis.test("AlwaysStrong reads strong on the first attempt", async () => {
|
|
343
|
-
await Effect.runPromise(StrongHandler.handleCommands(undefined, undefined, testDcbEventLog, Stream.fromIterable([makeTopicItem("ref-1", {
|
|
343
|
+
await Effect.runPromise(StrongHandler.handleCommands(undefined, undefined, undefined, testDcbEventLog, Stream.fromIterable([makeTopicItem("ref-1", {
|
|
344
344
|
TAG: "CreateItem",
|
|
345
345
|
itemId: "item-1",
|
|
346
346
|
name: "Test"
|
|
@@ -349,7 +349,7 @@ globalThis.describe("StateChangeSlice_Callback:", () => {
|
|
|
349
349
|
});
|
|
350
350
|
globalThis.test("AlwaysStrong stays strong across retries (2 failures)", async () => {
|
|
351
351
|
mock.failNextAppends.contents = 2;
|
|
352
|
-
await Effect.runPromise(StrongHandler.handleCommands(undefined, undefined, testDcbEventLog, Stream.fromIterable([makeTopicItem("ref-1", {
|
|
352
|
+
await Effect.runPromise(StrongHandler.handleCommands(undefined, undefined, undefined, testDcbEventLog, Stream.fromIterable([makeTopicItem("ref-1", {
|
|
353
353
|
TAG: "CreateItem",
|
|
354
354
|
itemId: "item-1",
|
|
355
355
|
name: "Test"
|
|
@@ -362,7 +362,7 @@ globalThis.describe("StateChangeSlice_Callback:", () => {
|
|
|
362
362
|
});
|
|
363
363
|
globalThis.test("AlwaysEventual never reads strong, even on retry (2 failures)", async () => {
|
|
364
364
|
mock.failNextAppends.contents = 2;
|
|
365
|
-
await Effect.runPromise(EventualHandler.handleCommands(undefined, undefined, testDcbEventLog, Stream.fromIterable([makeTopicItem("ref-1", {
|
|
365
|
+
await Effect.runPromise(EventualHandler.handleCommands(undefined, undefined, undefined, testDcbEventLog, Stream.fromIterable([makeTopicItem("ref-1", {
|
|
366
366
|
TAG: "CreateItem",
|
|
367
367
|
itemId: "item-1",
|
|
368
368
|
name: "Test"
|
|
@@ -376,12 +376,12 @@ globalThis.describe("StateChangeSlice_Callback:", () => {
|
|
|
376
376
|
});
|
|
377
377
|
globalThis.describe("handleCommands - conditional append", () => {
|
|
378
378
|
globalThis.test("RenameItem after CreateItem uses headPosition in condition", async () => {
|
|
379
|
-
await Effect.runPromise(TestHandler.handleCommands(undefined, undefined, testDcbEventLog, Stream.fromIterable([makeTopicItem("ref-1", {
|
|
379
|
+
await Effect.runPromise(TestHandler.handleCommands(undefined, undefined, undefined, testDcbEventLog, Stream.fromIterable([makeTopicItem("ref-1", {
|
|
380
380
|
TAG: "CreateItem",
|
|
381
381
|
itemId: "item-1",
|
|
382
382
|
name: "Test"
|
|
383
383
|
})])));
|
|
384
|
-
let results = await Effect.runPromise(TestHandler.handleCommands(undefined, undefined, testDcbEventLog, Stream.fromIterable([makeTopicItem("ref-2", {
|
|
384
|
+
let results = await Effect.runPromise(TestHandler.handleCommands(undefined, undefined, undefined, testDcbEventLog, Stream.fromIterable([makeTopicItem("ref-2", {
|
|
385
385
|
TAG: "RenameItem",
|
|
386
386
|
itemId: "item-1",
|
|
387
387
|
newName: "Updated"
|
|
@@ -400,7 +400,7 @@ globalThis.describe("StateChangeSlice_Callback:", () => {
|
|
|
400
400
|
});
|
|
401
401
|
globalThis.describe("handleCommands - batch", () => {
|
|
402
402
|
globalThis.test("multiple successful commands", async () => {
|
|
403
|
-
let results = await Effect.runPromise(TestHandler.handleCommands(undefined, undefined, testDcbEventLog, Stream.fromIterable([
|
|
403
|
+
let results = await Effect.runPromise(TestHandler.handleCommands(undefined, undefined, undefined, testDcbEventLog, Stream.fromIterable([
|
|
404
404
|
makeTopicItem("ref-1", {
|
|
405
405
|
TAG: "CreateItem",
|
|
406
406
|
itemId: "item-1",
|
|
@@ -424,12 +424,12 @@ globalThis.describe("StateChangeSlice_Callback:", () => {
|
|
|
424
424
|
]);
|
|
425
425
|
});
|
|
426
426
|
globalThis.test("mixed: new item succeeds, duplicate item ACKs as Ok (business-rule violation)", async () => {
|
|
427
|
-
await Effect.runPromise(TestHandler.handleCommands(undefined, undefined, testDcbEventLog, Stream.fromIterable([makeTopicItem("ref-0", {
|
|
427
|
+
await Effect.runPromise(TestHandler.handleCommands(undefined, undefined, undefined, testDcbEventLog, Stream.fromIterable([makeTopicItem("ref-0", {
|
|
428
428
|
TAG: "CreateItem",
|
|
429
429
|
itemId: "item-1",
|
|
430
430
|
name: "Existing"
|
|
431
431
|
})])));
|
|
432
|
-
let results = await Effect.runPromise(TestHandler.handleCommands(undefined, undefined, testDcbEventLog, Stream.fromIterable([
|
|
432
|
+
let results = await Effect.runPromise(TestHandler.handleCommands(undefined, undefined, undefined, testDcbEventLog, Stream.fromIterable([
|
|
433
433
|
makeTopicItem("ref-1", {
|
|
434
434
|
TAG: "CreateItem",
|
|
435
435
|
itemId: "item-2",
|
|
@@ -453,13 +453,13 @@ globalThis.describe("StateChangeSlice_Callback:", () => {
|
|
|
453
453
|
]);
|
|
454
454
|
});
|
|
455
455
|
globalThis.test("empty batch returns empty array", async () => {
|
|
456
|
-
let results = await Effect.runPromise(TestHandler.handleCommands(undefined, undefined, testDcbEventLog, Stream.fromIterable([])));
|
|
456
|
+
let results = await Effect.runPromise(TestHandler.handleCommands(undefined, undefined, undefined, testDcbEventLog, Stream.fromIterable([])));
|
|
457
457
|
globalThis.expect(results).toEqual([]);
|
|
458
458
|
});
|
|
459
459
|
});
|
|
460
460
|
globalThis.describe("handleCommands - projection cache", () => {
|
|
461
461
|
globalThis.test("a third same-entity command reads only the delta", async () => {
|
|
462
|
-
let run = (reference, command) => Effect.runPromise(TestHandler.handleCommands(undefined, undefined, testDcbEventLog, Stream.fromIterable([makeTopicItem(reference, command)])));
|
|
462
|
+
let run = (reference, command) => Effect.runPromise(TestHandler.handleCommands(undefined, undefined, undefined, testDcbEventLog, Stream.fromIterable([makeTopicItem(reference, command)])));
|
|
463
463
|
await run("ref-1", {
|
|
464
464
|
TAG: "CreateItem",
|
|
465
465
|
itemId: "item-1",
|
|
@@ -493,7 +493,7 @@ globalThis.describe("StateChangeSlice_Callback:", () => {
|
|
|
493
493
|
]);
|
|
494
494
|
});
|
|
495
495
|
globalThis.test("a different entity does not hit another entity's cache (full read)", async () => {
|
|
496
|
-
let run = (reference, command) => Effect.runPromise(TestHandler.handleCommands(undefined, undefined, testDcbEventLog, Stream.fromIterable([makeTopicItem(reference, command)])));
|
|
496
|
+
let run = (reference, command) => Effect.runPromise(TestHandler.handleCommands(undefined, undefined, undefined, testDcbEventLog, Stream.fromIterable([makeTopicItem(reference, command)])));
|
|
497
497
|
await run("ref-1", {
|
|
498
498
|
TAG: "CreateItem",
|
|
499
499
|
itemId: "item-1",
|
|
@@ -510,7 +510,7 @@ globalThis.describe("StateChangeSlice_Callback:", () => {
|
|
|
510
510
|
]);
|
|
511
511
|
});
|
|
512
512
|
globalThis.test("conflict re-seeds the cache so the retry reads the delta", async () => {
|
|
513
|
-
let run = (reference, command) => Effect.runPromise(TestHandler.handleCommands(undefined, undefined, testDcbEventLog, Stream.fromIterable([makeTopicItem(reference, command)])));
|
|
513
|
+
let run = (reference, command) => Effect.runPromise(TestHandler.handleCommands(undefined, undefined, undefined, testDcbEventLog, Stream.fromIterable([makeTopicItem(reference, command)])));
|
|
514
514
|
await run("ref-1", {
|
|
515
515
|
TAG: "CreateItem",
|
|
516
516
|
itemId: "item-1",
|
package/tests/dcb/DcbTagTest.res
CHANGED
|
@@ -534,107 +534,120 @@ describe("DcbTag:", () => {
|
|
|
534
534
|
)
|
|
535
535
|
})
|
|
536
536
|
|
|
537
|
-
describe("
|
|
538
|
-
let
|
|
537
|
+
describe("deriveBoundaryPartition", () => {
|
|
538
|
+
let slice = (name, eventSchema): Reventless.DcbTag.sliceSchemas => {
|
|
539
|
+
name,
|
|
540
|
+
commandSchema: S.unknown,
|
|
541
|
+
consumedEventSchema: S.unknown,
|
|
542
|
+
eventSchema: eventSchema->S.castToUnknown,
|
|
543
|
+
}
|
|
544
|
+
let thrownMessage = f =>
|
|
545
|
+
try {
|
|
546
|
+
let _ = f()
|
|
547
|
+
None
|
|
548
|
+
} catch {
|
|
549
|
+
| JsExn(err) => JsExn.message(err)
|
|
550
|
+
}
|
|
539
551
|
|
|
540
552
|
testSync(
|
|
541
553
|
"returns Composite for schema with >= 2 composite fields",
|
|
542
|
-
() =>
|
|
543
|
-
|
|
544
|
-
("Sync",
|
|
545
|
-
])
|
|
546
|
-
switch result {
|
|
554
|
+
() =>
|
|
555
|
+
switch Reventless.DcbTag.deriveBoundaryPartition([
|
|
556
|
+
slice("Sync", DcbFixtures.compositeEventSchema),
|
|
557
|
+
]).partitionTag {
|
|
547
558
|
| Composite(spec) =>
|
|
548
|
-
expect(spec.keys)->toEqual(["environment", "platformName", "pluginName"])
|
|
559
|
+
expect(spec.keys)->toEqual(["environment", "platformName", "pluginName"])
|
|
549
560
|
expect(spec.seps)->toEqual(["/", "/"])
|
|
550
|
-
|
|
|
551
|
-
}
|
|
552
|
-
},
|
|
561
|
+
| _ => fail("Expected Composite")
|
|
562
|
+
},
|
|
553
563
|
)
|
|
554
564
|
|
|
555
565
|
testSync(
|
|
556
566
|
"returns Composite with custom separators",
|
|
557
|
-
() =>
|
|
558
|
-
|
|
559
|
-
("Config",
|
|
560
|
-
])
|
|
561
|
-
switch result {
|
|
567
|
+
() =>
|
|
568
|
+
switch Reventless.DcbTag.deriveBoundaryPartition([
|
|
569
|
+
slice("Config", DcbFixtures.compositeEventCustomSepSchema),
|
|
570
|
+
]).partitionTag {
|
|
562
571
|
| Composite(spec) =>
|
|
563
|
-
expect(spec.keys)->toEqual(["tenantId", "region", "service"])
|
|
572
|
+
expect(spec.keys)->toEqual(["tenantId", "region", "service"])
|
|
564
573
|
expect(spec.seps)->toEqual([":", "/"])
|
|
565
|
-
|
|
|
566
|
-
}
|
|
567
|
-
},
|
|
574
|
+
| _ => fail("Expected Composite")
|
|
575
|
+
},
|
|
568
576
|
)
|
|
569
577
|
|
|
570
578
|
testSync(
|
|
571
|
-
"
|
|
579
|
+
"files each event type under its own slice's key — two entities, two annotated keys",
|
|
572
580
|
() => {
|
|
573
|
-
let
|
|
574
|
-
("
|
|
581
|
+
let bp = Reventless.DcbTag.deriveBoundaryPartition([
|
|
582
|
+
slice("PlaceOrder", DcbFixtures.simplePartitionEventSchema),
|
|
583
|
+
slice("LinkAddress", DcbFixtures.addressLinkedEventSchema),
|
|
575
584
|
])
|
|
576
|
-
switch
|
|
577
|
-
|
|
|
578
|
-
|
|
585
|
+
switch bp.partitionTag {
|
|
586
|
+
| ByEventType(keys) =>
|
|
587
|
+
expect(keys->Dict.get("OrderPlaced"))->toEqual(Some("orderId"))
|
|
588
|
+
expect(keys->Dict.get("AddressLinked"))->toEqual(Some("customerId"))
|
|
589
|
+
| _ => fail("Expected ByEventType")
|
|
579
590
|
}
|
|
591
|
+
expect(bp->Reventless.DcbTag.slicePartitionTag("LinkAddress"))->toEqual(
|
|
592
|
+
Some(Reventless.DcbTag.Simple({key: "customerId"})),
|
|
593
|
+
)
|
|
580
594
|
},
|
|
581
595
|
)
|
|
582
596
|
|
|
583
597
|
testSync(
|
|
584
|
-
"
|
|
598
|
+
"a single tagged field is the partition",
|
|
599
|
+
() =>
|
|
600
|
+
expect(
|
|
601
|
+
Reventless.DcbTag.deriveBoundaryPartition([
|
|
602
|
+
slice("Item", DcbFixtures.singleTagCommandSchema),
|
|
603
|
+
]).partitionBySlice->Dict.get("Item"),
|
|
604
|
+
)->toEqual(Some("itemId")),
|
|
605
|
+
)
|
|
606
|
+
|
|
607
|
+
testSync(
|
|
608
|
+
"an unannotated multi-id slice throws naming it, though a sibling is annotated",
|
|
585
609
|
() => {
|
|
586
|
-
let
|
|
587
|
-
(
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
610
|
+
let message =
|
|
611
|
+
thrownMessage(
|
|
612
|
+
() =>
|
|
613
|
+
Reventless.DcbTag.deriveBoundaryPartition([
|
|
614
|
+
slice("PlaceOrder", DcbFixtures.simplePartitionEventSchema),
|
|
615
|
+
slice("LinkCustomerAddress", DcbFixtures.unannotatedLinkEventSchema),
|
|
616
|
+
]),
|
|
617
|
+
)->Option.getOr("")
|
|
618
|
+
expect(message->String.includes("LinkCustomerAddress"))->toBe(true)
|
|
619
|
+
expect(message->String.includes("@partitionTag"))->toBe(true)
|
|
593
620
|
},
|
|
594
621
|
)
|
|
595
622
|
|
|
596
623
|
testSync(
|
|
597
624
|
"throws on mixed composite and simple partition strategy",
|
|
598
|
-
() =>
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
| JsExn(err) =>
|
|
606
|
-
threw := true
|
|
607
|
-
expect(
|
|
608
|
-
JsExn.message(err)
|
|
609
|
-
->Option.getOr("")
|
|
610
|
-
->String.includes("mixes @compositePartitionTag and @partitionTag"),
|
|
625
|
+
() =>
|
|
626
|
+
expect(
|
|
627
|
+
thrownMessage(
|
|
628
|
+
() =>
|
|
629
|
+
Reventless.DcbTag.deriveBoundaryPartition([
|
|
630
|
+
slice("Mixed", DcbFixtures.mixedStrategyEventSchema),
|
|
631
|
+
]),
|
|
611
632
|
)
|
|
612
|
-
->
|
|
613
|
-
->
|
|
614
|
-
|
|
615
|
-
expect(threw.contents)->toBe(true)
|
|
616
|
-
},
|
|
633
|
+
->Option.getOr("")
|
|
634
|
+
->String.includes("mixes @compositePartitionTag and @partitionTag"),
|
|
635
|
+
)->toBe(true),
|
|
617
636
|
)
|
|
618
637
|
|
|
619
638
|
testSync(
|
|
620
639
|
"throws on single composite field (< 2)",
|
|
621
|
-
() =>
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
| JsExn(err) =>
|
|
629
|
-
threw := true
|
|
630
|
-
expect(
|
|
631
|
-
JsExn.message(err)->Option.getOr("")->String.includes("at least 2 annotated fields"),
|
|
640
|
+
() =>
|
|
641
|
+
expect(
|
|
642
|
+
thrownMessage(
|
|
643
|
+
() =>
|
|
644
|
+
Reventless.DcbTag.deriveBoundaryPartition([
|
|
645
|
+
slice("Single", DcbFixtures.singleCompositeEventSchema),
|
|
646
|
+
]),
|
|
632
647
|
)
|
|
633
|
-
->
|
|
634
|
-
->
|
|
635
|
-
|
|
636
|
-
expect(threw.contents)->toBe(true)
|
|
637
|
-
},
|
|
648
|
+
->Option.getOr("")
|
|
649
|
+
->String.includes("at least 2 annotated fields"),
|
|
650
|
+
)->toBe(true),
|
|
638
651
|
)
|
|
639
652
|
})
|
|
640
653
|
|
|
@@ -418,106 +418,105 @@ globalThis.describe("DcbTag:", () => {
|
|
|
418
418
|
globalThis.expect(DcbTag$Reventless.getCompositePartitionKeyValue(tags, spec)).toBe("1/2/3");
|
|
419
419
|
});
|
|
420
420
|
});
|
|
421
|
-
globalThis.describe("
|
|
421
|
+
globalThis.describe("deriveBoundaryPartition", () => {
|
|
422
|
+
let slice = (name, eventSchema) => ({
|
|
423
|
+
name: name,
|
|
424
|
+
commandSchema: Sury.unknown,
|
|
425
|
+
consumedEventSchema: Sury.unknown,
|
|
426
|
+
eventSchema: eventSchema
|
|
427
|
+
});
|
|
428
|
+
let thrownMessage = f => {
|
|
429
|
+
try {
|
|
430
|
+
f();
|
|
431
|
+
return;
|
|
432
|
+
} catch (raw_err) {
|
|
433
|
+
let err = Primitive_exceptions.internalToException(raw_err);
|
|
434
|
+
if (err.RE_EXN_ID === "JsExn") {
|
|
435
|
+
return Stdlib_JsExn.message(err._1);
|
|
436
|
+
}
|
|
437
|
+
throw err;
|
|
438
|
+
}
|
|
439
|
+
};
|
|
422
440
|
globalThis.test("returns Composite for schema with >= 2 composite fields", () => {
|
|
423
|
-
let
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
441
|
+
let spec = DcbTag$Reventless.deriveBoundaryPartition([slice("Sync", DcbFixtures$ReventlessCore.compositeEventSchema)]).partitionTag;
|
|
442
|
+
switch (spec.TAG) {
|
|
443
|
+
case "Composite" :
|
|
444
|
+
let spec$1 = spec._0;
|
|
445
|
+
globalThis.expect(spec$1.keys).toEqual([
|
|
446
|
+
"environment",
|
|
447
|
+
"platformName",
|
|
448
|
+
"pluginName"
|
|
449
|
+
]);
|
|
450
|
+
globalThis.expect(spec$1.seps).toEqual([
|
|
451
|
+
"/",
|
|
452
|
+
"/"
|
|
453
|
+
]);
|
|
454
|
+
return;
|
|
455
|
+
case "Simple" :
|
|
456
|
+
case "ByEventType" :
|
|
457
|
+
return JestGlobals.fail("Expected Composite");
|
|
430
458
|
}
|
|
431
|
-
let spec = result._0;
|
|
432
|
-
globalThis.expect(spec.keys).toEqual([
|
|
433
|
-
"environment",
|
|
434
|
-
"platformName",
|
|
435
|
-
"pluginName"
|
|
436
|
-
]);
|
|
437
|
-
globalThis.expect(spec.seps).toEqual([
|
|
438
|
-
"/",
|
|
439
|
-
"/"
|
|
440
|
-
]);
|
|
441
459
|
});
|
|
442
460
|
globalThis.test("returns Composite with custom separators", () => {
|
|
443
|
-
let
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
461
|
+
let spec = DcbTag$Reventless.deriveBoundaryPartition([slice("Config", DcbFixtures$ReventlessCore.compositeEventCustomSepSchema)]).partitionTag;
|
|
462
|
+
switch (spec.TAG) {
|
|
463
|
+
case "Composite" :
|
|
464
|
+
let spec$1 = spec._0;
|
|
465
|
+
globalThis.expect(spec$1.keys).toEqual([
|
|
466
|
+
"tenantId",
|
|
467
|
+
"region",
|
|
468
|
+
"service"
|
|
469
|
+
]);
|
|
470
|
+
globalThis.expect(spec$1.seps).toEqual([
|
|
471
|
+
":",
|
|
472
|
+
"/"
|
|
473
|
+
]);
|
|
474
|
+
return;
|
|
475
|
+
case "Simple" :
|
|
476
|
+
case "ByEventType" :
|
|
477
|
+
return JestGlobals.fail("Expected Composite");
|
|
450
478
|
}
|
|
451
|
-
let spec = result._0;
|
|
452
|
-
globalThis.expect(spec.keys).toEqual([
|
|
453
|
-
"tenantId",
|
|
454
|
-
"region",
|
|
455
|
-
"service"
|
|
456
|
-
]);
|
|
457
|
-
globalThis.expect(spec.seps).toEqual([
|
|
458
|
-
":",
|
|
459
|
-
"/"
|
|
460
|
-
]);
|
|
461
479
|
});
|
|
462
|
-
globalThis.test("
|
|
463
|
-
let
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
"
|
|
476
|
-
"
|
|
477
|
-
|
|
478
|
-
]]);
|
|
479
|
-
if (result.TAG !== "Simple") {
|
|
480
|
-
return JestGlobals.fail("Expected Simple");
|
|
480
|
+
globalThis.test("files each event type under its own slice's key — two entities, two annotated keys", () => {
|
|
481
|
+
let bp = DcbTag$Reventless.deriveBoundaryPartition([
|
|
482
|
+
slice("PlaceOrder", DcbFixtures$ReventlessCore.simplePartitionEventSchema),
|
|
483
|
+
slice("LinkAddress", DcbFixtures$ReventlessCore.addressLinkedEventSchema)
|
|
484
|
+
]);
|
|
485
|
+
let keys = bp.partitionTag;
|
|
486
|
+
switch (keys.TAG) {
|
|
487
|
+
case "Simple" :
|
|
488
|
+
case "Composite" :
|
|
489
|
+
JestGlobals.fail("Expected ByEventType");
|
|
490
|
+
break;
|
|
491
|
+
case "ByEventType" :
|
|
492
|
+
let keys$1 = keys._0;
|
|
493
|
+
globalThis.expect(keys$1["OrderPlaced"]).toEqual("orderId");
|
|
494
|
+
globalThis.expect(keys$1["AddressLinked"]).toEqual("customerId");
|
|
495
|
+
break;
|
|
481
496
|
}
|
|
482
|
-
globalThis.expect(
|
|
497
|
+
globalThis.expect(DcbTag$Reventless.slicePartitionTag(bp, "LinkAddress")).toEqual({
|
|
498
|
+
TAG: "Simple",
|
|
499
|
+
_0: {
|
|
500
|
+
key: "customerId"
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
});
|
|
504
|
+
globalThis.test("a single tagged field is the partition", () => {
|
|
505
|
+
globalThis.expect(DcbTag$Reventless.deriveBoundaryPartition([slice("Item", DcbFixtures$ReventlessCore.singleTagCommandSchema)]).partitionBySlice["Item"]).toEqual("itemId");
|
|
506
|
+
});
|
|
507
|
+
globalThis.test("an unannotated multi-id slice throws naming it, though a sibling is annotated", () => {
|
|
508
|
+
let message = Stdlib_Option.getOr(thrownMessage(() => DcbTag$Reventless.deriveBoundaryPartition([
|
|
509
|
+
slice("PlaceOrder", DcbFixtures$ReventlessCore.simplePartitionEventSchema),
|
|
510
|
+
slice("LinkCustomerAddress", DcbFixtures$ReventlessCore.unannotatedLinkEventSchema)
|
|
511
|
+
])), "");
|
|
512
|
+
globalThis.expect(message.includes("LinkCustomerAddress")).toBe(true);
|
|
513
|
+
globalThis.expect(message.includes("@partitionTag")).toBe(true);
|
|
483
514
|
});
|
|
484
515
|
globalThis.test("throws on mixed composite and simple partition strategy", () => {
|
|
485
|
-
|
|
486
|
-
try {
|
|
487
|
-
DcbTag$Reventless.derivePartitionTag([[
|
|
488
|
-
"Mixed",
|
|
489
|
-
"test.res",
|
|
490
|
-
DcbFixtures$ReventlessCore.mixedStrategyEventSchema
|
|
491
|
-
]]);
|
|
492
|
-
} catch (raw_err) {
|
|
493
|
-
let err = Primitive_exceptions.internalToException(raw_err);
|
|
494
|
-
if (err.RE_EXN_ID === "JsExn") {
|
|
495
|
-
threw = true;
|
|
496
|
-
globalThis.expect(Stdlib_Option.getOr(Stdlib_JsExn.message(err._1), "").includes("mixes @compositePartitionTag and @partitionTag")).toBe(true);
|
|
497
|
-
} else {
|
|
498
|
-
throw err;
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
globalThis.expect(threw).toBe(true);
|
|
516
|
+
globalThis.expect(Stdlib_Option.getOr(thrownMessage(() => DcbTag$Reventless.deriveBoundaryPartition([slice("Mixed", DcbFixtures$ReventlessCore.mixedStrategyEventSchema)])), "").includes("mixes @compositePartitionTag and @partitionTag")).toBe(true);
|
|
502
517
|
});
|
|
503
518
|
globalThis.test("throws on single composite field (< 2)", () => {
|
|
504
|
-
|
|
505
|
-
try {
|
|
506
|
-
DcbTag$Reventless.derivePartitionTag([[
|
|
507
|
-
"Single",
|
|
508
|
-
"test.res",
|
|
509
|
-
DcbFixtures$ReventlessCore.singleCompositeEventSchema
|
|
510
|
-
]]);
|
|
511
|
-
} catch (raw_err) {
|
|
512
|
-
let err = Primitive_exceptions.internalToException(raw_err);
|
|
513
|
-
if (err.RE_EXN_ID === "JsExn") {
|
|
514
|
-
threw = true;
|
|
515
|
-
globalThis.expect(Stdlib_Option.getOr(Stdlib_JsExn.message(err._1), "").includes("at least 2 annotated fields")).toBe(true);
|
|
516
|
-
} else {
|
|
517
|
-
throw err;
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
globalThis.expect(threw).toBe(true);
|
|
519
|
+
globalThis.expect(Stdlib_Option.getOr(thrownMessage(() => DcbTag$Reventless.deriveBoundaryPartition([slice("Single", DcbFixtures$ReventlessCore.singleCompositeEventSchema)])), "").includes("at least 2 annotated fields")).toBe(true);
|
|
521
520
|
});
|
|
522
521
|
});
|
|
523
522
|
globalThis.describe("isCompositePartitionMember", () => {
|