@reventlessdev/reventless-core 3.0.0-alpha.262 → 3.0.0-alpha.264

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.
Files changed (34) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/package.json +9 -9
  3. package/src/admin/Platform_Admin_Structure.res.mjs +1 -1
  4. package/src/components/Aggregate/Aggregate_Builder.res.mjs +1 -1
  5. package/src/components/CommandGenerator/CommandGenerator.res +4 -4
  6. package/src/components/CommandGenerator/CommandGenerator_Callback.res +13 -20
  7. package/src/components/CommandGenerator/CommandGenerator_Callback.res.mjs +3 -24
  8. package/src/components/Dcb/Dcb_Builder.res +45 -49
  9. package/src/components/Dcb/Dcb_Builder.res.mjs +31 -40
  10. package/src/components/StateChangeSlice/StateChangeSlice.res +2 -0
  11. package/src/components/StateChangeSlice/StateChangeSlice_Builder.res +6 -0
  12. package/src/components/StateChangeSlice/StateChangeSlice_Builder.res.mjs +4 -4
  13. package/src/components/StateChangeSlice/StateChangeSlice_Callback.res +51 -22
  14. package/src/components/StateChangeSlice/StateChangeSlice_Callback.res.mjs +47 -25
  15. package/src/plugin/component/Plugin_Structure.res +101 -4
  16. package/src/plugin/component/Plugin_Structure.res.mjs +82 -9
  17. package/tests/adapter/RuntimeExtensionHookReachTest.res.mjs +1 -1
  18. package/tests/commandgenerator/CommandGeneratorCallbackTest.res +1 -1
  19. package/tests/commandgenerator/CommandGeneratorCallbackTest.res.mjs +1 -1
  20. package/tests/commandgenerator/CommandGeneratorFixtures.res +2 -0
  21. package/tests/commandgenerator/CommandGeneratorFixtures.res.mjs +17 -3
  22. package/tests/commandgenerator/OwnerStampingTest.res.mjs +2 -2
  23. package/tests/commandtopic/CommandOutcomeHookTest.res.mjs +1 -1
  24. package/tests/dcb/DcbFixtures.res +18 -1
  25. package/tests/dcb/DcbFixtures.res.mjs +14 -0
  26. package/tests/dcb/DcbScopeInferenceTest.res +234 -0
  27. package/tests/dcb/DcbScopeInferenceTest.res.mjs +425 -13
  28. package/tests/dcb/DcbStateChangeSliceTest.res.mjs +22 -22
  29. package/tests/dcb/DcbTagTest.res +81 -68
  30. package/tests/dcb/DcbTagTest.res.mjs +88 -89
  31. package/tests/plugin/PluginStructureAccessTest.res +1 -0
  32. package/tests/plugin/PluginStructureAccessTest.res.mjs +1 -1
  33. package/tests/plugin/PluginStructureTest.res +47 -0
  34. package/tests/plugin/PluginStructureTest.res.mjs +50 -10
@@ -28,7 +28,7 @@ function ev(eventType, idFields) {
28
28
  };
29
29
  }
30
30
 
31
- function slice(sliceName, commandOpt, consumedOpt, producedOpt, partitionHint) {
31
+ function slice(sliceName, commandOpt, consumedOpt, producedOpt, partitionHint, chapter) {
32
32
  let command = commandOpt !== undefined ? commandOpt : [];
33
33
  let consumed = consumedOpt !== undefined ? consumedOpt : [];
34
34
  let produced = producedOpt !== undefined ? producedOpt : [];
@@ -37,7 +37,8 @@ function slice(sliceName, commandOpt, consumedOpt, producedOpt, partitionHint) {
37
37
  command: command,
38
38
  consumed: consumed,
39
39
  produced: produced,
40
- partitionHint: partitionHint
40
+ partitionHint: partitionHint,
41
+ chapter: chapter
41
42
  };
42
43
  }
43
44
 
@@ -84,7 +85,7 @@ let addProduct = slice("AddProduct", [
84
85
  isList: false
85
86
  }
86
87
  ]
87
- }], undefined);
88
+ }], undefined, undefined);
88
89
 
89
90
  let addCategory = slice("AddCategory", [{
90
91
  name: "categoryId",
@@ -104,7 +105,7 @@ let addCategory = slice("AddCategory", [{
104
105
  name: "categoryId",
105
106
  isList: false
106
107
  }]
107
- }], undefined);
108
+ }], undefined, undefined);
108
109
 
109
110
  let renameCategory = slice("RenameCategory", [{
110
111
  name: "categoryId",
@@ -128,7 +129,7 @@ let renameCategory = slice("RenameCategory", [{
128
129
  name: "categoryId",
129
130
  isList: false
130
131
  }]
131
- }], undefined);
132
+ }], undefined, undefined);
132
133
 
133
134
  let archiveCategory = slice("ArchiveCategory", [{
134
135
  name: "categoryId",
@@ -148,7 +149,7 @@ let archiveCategory = slice("ArchiveCategory", [{
148
149
  name: "categoryId",
149
150
  isList: false
150
151
  }]
151
- }], undefined);
152
+ }], undefined, undefined);
152
153
 
153
154
  let catalog = [
154
155
  addProduct,
@@ -242,7 +243,7 @@ globalThis.describe("DcbScopeInference:", () => {
242
243
  }
243
244
  ]
244
245
  }
245
- ], "productId");
246
+ ], "productId", undefined);
246
247
  let d = DcbScopeInference$Reventless.infer([recordDemand]);
247
248
  globalThis.test("the @partitionTag hint resolves the partition (no ambiguity)", () => {
248
249
  globalThis.expect(d.partitionBySlice["RecordProductDemand"]).toEqual("productId");
@@ -295,7 +296,7 @@ globalThis.describe("DcbScopeInference:", () => {
295
296
  isList: true
296
297
  }
297
298
  ]
298
- }], undefined);
299
+ }], undefined, undefined);
299
300
  let productEntity = slice("SyncCatalogProduct", [{
300
301
  name: "productId",
301
302
  isList: false
@@ -308,7 +309,7 @@ globalThis.describe("DcbScopeInference:", () => {
308
309
  name: "productId",
309
310
  isList: false
310
311
  }]
311
- }], undefined);
312
+ }], undefined, undefined);
312
313
  let d = DcbScopeInference$Reventless.infer([
313
314
  placeOrder,
314
315
  productEntity
@@ -342,7 +343,7 @@ globalThis.describe("DcbScopeInference:", () => {
342
343
  name: "studentId",
343
344
  isList: false
344
345
  }]
345
- }], undefined);
346
+ }], undefined, undefined);
346
347
  let subscribe_command = [
347
348
  {
348
349
  name: "courseId",
@@ -403,6 +404,417 @@ globalThis.describe("DcbScopeInference:", () => {
403
404
  globalThis.expect(issues.redundancies).toEqual([]);
404
405
  });
405
406
  });
407
+ globalThis.describe("partition: identity given back only when subtraction leaves nothing", () => {
408
+ globalThis.test("AddProduct without a hint stays productId, though its category arms come from a categoryId slice", () => {
409
+ let d = DcbScopeInference$Reventless.infer(catalog);
410
+ globalThis.expect(addProduct.partitionHint).toEqual(undefined);
411
+ globalThis.expect(d.partitionBySlice["AddProduct"]).toEqual("productId");
412
+ });
413
+ globalThis.test("a lifecycle arm naming the slice's own id resolves", () => {
414
+ let changeName = slice("ChangeProductName", [{
415
+ name: "productId",
416
+ isList: false
417
+ }], [{
418
+ eventType: "ProductAdded",
419
+ idFields: [{
420
+ name: "productId",
421
+ isList: false
422
+ }]
423
+ }], [{
424
+ eventType: "ProductNameChanged",
425
+ idFields: [{
426
+ name: "productId",
427
+ isList: false
428
+ }]
429
+ }], undefined, undefined);
430
+ let d = DcbScopeInference$Reventless.infer(catalog.concat([changeName]));
431
+ globalThis.expect(d.partitionBySlice["ChangeProductName"]).toEqual("productId");
432
+ globalThis.expect(d.ambiguities).toEqual([]);
433
+ });
434
+ let placeOrder = slice("PlaceOrder", [
435
+ {
436
+ name: "orderId",
437
+ isList: false
438
+ },
439
+ {
440
+ name: "customerId",
441
+ isList: false
442
+ }
443
+ ], undefined, [{
444
+ eventType: "OrderPlaced",
445
+ idFields: [
446
+ {
447
+ name: "orderId",
448
+ isList: false
449
+ },
450
+ {
451
+ name: "customerId",
452
+ isList: false
453
+ },
454
+ {
455
+ name: "productIds",
456
+ isList: true
457
+ }
458
+ ]
459
+ }], "orderId", undefined);
460
+ let shipOrder = slice("ShipOrder", [{
461
+ name: "orderId",
462
+ isList: false
463
+ }], [
464
+ {
465
+ eventType: "OrderPlaced",
466
+ idFields: [
467
+ {
468
+ name: "orderId",
469
+ isList: false
470
+ },
471
+ {
472
+ name: "customerId",
473
+ isList: false
474
+ },
475
+ {
476
+ name: "productIds",
477
+ isList: true
478
+ }
479
+ ]
480
+ },
481
+ {
482
+ eventType: "OrderCancelled",
483
+ idFields: [
484
+ {
485
+ name: "orderId",
486
+ isList: false
487
+ },
488
+ {
489
+ name: "productIds",
490
+ isList: true
491
+ }
492
+ ]
493
+ }
494
+ ], [{
495
+ eventType: "OrderShipped",
496
+ idFields: [
497
+ {
498
+ name: "orderId",
499
+ isList: false
500
+ },
501
+ {
502
+ name: "customerId",
503
+ isList: false
504
+ }
505
+ ]
506
+ }], undefined, undefined);
507
+ let cancelOrder = slice("CancelOrder", [{
508
+ name: "orderId",
509
+ isList: false
510
+ }], [
511
+ {
512
+ eventType: "OrderPlaced",
513
+ idFields: [
514
+ {
515
+ name: "orderId",
516
+ isList: false
517
+ },
518
+ {
519
+ name: "customerId",
520
+ isList: false
521
+ },
522
+ {
523
+ name: "productIds",
524
+ isList: true
525
+ }
526
+ ]
527
+ },
528
+ {
529
+ eventType: "OrderShipped",
530
+ idFields: [
531
+ {
532
+ name: "orderId",
533
+ isList: false
534
+ },
535
+ {
536
+ name: "customerId",
537
+ isList: false
538
+ }
539
+ ]
540
+ }
541
+ ], [{
542
+ eventType: "OrderCancelled",
543
+ idFields: [
544
+ {
545
+ name: "orderId",
546
+ isList: false
547
+ },
548
+ {
549
+ name: "productIds",
550
+ isList: true
551
+ }
552
+ ]
553
+ }], undefined, undefined);
554
+ globalThis.test("the ShipOrder ⇄ CancelOrder cycle resolves both to orderId", () => {
555
+ let d = DcbScopeInference$Reventless.infer([
556
+ placeOrder,
557
+ shipOrder,
558
+ cancelOrder
559
+ ]);
560
+ globalThis.expect(d.partitionBySlice["ShipOrder"]).toEqual("orderId");
561
+ globalThis.expect(d.partitionBySlice["CancelOrder"]).toEqual("orderId");
562
+ globalThis.expect(d.ambiguities).toEqual([]);
563
+ });
564
+ globalThis.test("seen alone, the same slice names the arms to change", () => {
565
+ globalThis.expect(DcbScopeInference$Reventless.partitionBlockers(cancelOrder)).toEqual([
566
+ [
567
+ "orderId",
568
+ [
569
+ "OrderPlaced",
570
+ "OrderShipped"
571
+ ]
572
+ ],
573
+ [
574
+ "productId",
575
+ ["OrderPlaced"]
576
+ ]
577
+ ]);
578
+ });
579
+ let recordDemand = slice("RecordProductDemand", [
580
+ {
581
+ name: "productId",
582
+ isList: false
583
+ },
584
+ {
585
+ name: "orderId",
586
+ isList: false
587
+ }
588
+ ], [{
589
+ eventType: "ProductDemandRecorded",
590
+ idFields: [{
591
+ name: "orderId",
592
+ isList: false
593
+ }]
594
+ }], [{
595
+ eventType: "ProductDemandRecorded",
596
+ idFields: [
597
+ {
598
+ name: "productId",
599
+ isList: false
600
+ },
601
+ {
602
+ name: "orderId",
603
+ isList: false
604
+ }
605
+ ]
606
+ }], undefined, undefined);
607
+ globalThis.test("a join without a hint stays ambiguous", () => {
608
+ let d = DcbScopeInference$Reventless.infer([recordDemand]);
609
+ globalThis.expect(d.partitionBySlice["RecordProductDemand"]).toEqual(undefined);
610
+ globalThis.expect(d.ambiguities.map(param => param[0])).toEqual(["RecordProductDemand"]);
611
+ });
612
+ globalThis.test("a produced id another slice is partitioned by is not dropped", () => {
613
+ let d = DcbScopeInference$Reventless.infer(catalog.concat([
614
+ placeOrder,
615
+ recordDemand
616
+ ]));
617
+ globalThis.expect(d.partitionBySlice["RecordProductDemand"]).toEqual(undefined);
618
+ globalThis.expect(d.ambiguities.some(param => {
619
+ if (param[0] === "RecordProductDemand") {
620
+ return param[1].includes("orderId, productId");
621
+ } else {
622
+ return false;
623
+ }
624
+ })).toBe(true);
625
+ });
626
+ globalThis.test("a boundary that never settles is reported, not guessed", () => {
627
+ let a = slice("A", undefined, [
628
+ {
629
+ eventType: "BHappened",
630
+ idFields: [{
631
+ name: "xId",
632
+ isList: false
633
+ }]
634
+ },
635
+ {
636
+ eventType: "ZHappened",
637
+ idFields: [{
638
+ name: "yId",
639
+ isList: false
640
+ }]
641
+ }
642
+ ], [{
643
+ eventType: "AHappened",
644
+ idFields: [
645
+ {
646
+ name: "xId",
647
+ isList: false
648
+ },
649
+ {
650
+ name: "yId",
651
+ isList: false
652
+ }
653
+ ]
654
+ }], undefined, undefined);
655
+ let b = slice("B", undefined, [
656
+ {
657
+ eventType: "AHappened",
658
+ idFields: [{
659
+ name: "yId",
660
+ isList: false
661
+ }]
662
+ },
663
+ {
664
+ eventType: "Z2Happened",
665
+ idFields: [{
666
+ name: "xId",
667
+ isList: false
668
+ }]
669
+ }
670
+ ], [{
671
+ eventType: "BHappened",
672
+ idFields: [
673
+ {
674
+ name: "xId",
675
+ isList: false
676
+ },
677
+ {
678
+ name: "yId",
679
+ isList: false
680
+ }
681
+ ]
682
+ }], undefined, undefined);
683
+ let d = DcbScopeInference$Reventless.infer([
684
+ a,
685
+ b
686
+ ]);
687
+ globalThis.expect(d.ambiguities.map(param => param[0])).toEqual([
688
+ "A",
689
+ "B"
690
+ ]);
691
+ globalThis.expect(d.ambiguities.every(param => param[1].includes("did not settle"))).toBe(true);
692
+ });
693
+ });
694
+ globalThis.describe("partition: the chapter breaks a tie", () => {
695
+ let placeOrder = chapter => slice("PlaceOrder", [
696
+ {
697
+ name: "orderId",
698
+ isList: false
699
+ },
700
+ {
701
+ name: "customerId",
702
+ isList: false
703
+ }
704
+ ], [{
705
+ eventType: "OrderPlaced",
706
+ idFields: [{
707
+ name: "orderId",
708
+ isList: false
709
+ }]
710
+ }], [{
711
+ eventType: "OrderPlaced",
712
+ idFields: [
713
+ {
714
+ name: "orderId",
715
+ isList: false
716
+ },
717
+ {
718
+ name: "customerId",
719
+ isList: false
720
+ }
721
+ ]
722
+ }], undefined, chapter);
723
+ let cancelOrder = slice("CancelOrder", undefined, [{
724
+ eventType: "OrderPlaced",
725
+ idFields: []
726
+ }], [{
727
+ eventType: "OrderCancelled",
728
+ idFields: [{
729
+ name: "orderId",
730
+ isList: false
731
+ }]
732
+ }], undefined, "Order");
733
+ globalThis.test("an order and its customer resolve to the id every Order event carries", () => {
734
+ let d = DcbScopeInference$Reventless.infer([
735
+ placeOrder("Order"),
736
+ cancelOrder
737
+ ]);
738
+ globalThis.expect(d.partitionBySlice["PlaceOrder"]).toEqual("orderId");
739
+ globalThis.expect(d.ambiguities).toEqual([]);
740
+ });
741
+ globalThis.test("without a chapter the same slice stays ambiguous", () => {
742
+ let d = DcbScopeInference$Reventless.infer([
743
+ placeOrder(undefined),
744
+ cancelOrder
745
+ ]);
746
+ globalThis.expect(d.partitionBySlice["PlaceOrder"]).toEqual(undefined);
747
+ });
748
+ globalThis.test("a join whose chapter carries both ids stays ambiguous, and the reason says so", () => {
749
+ let demand = slice("RecordProductDemand", undefined, undefined, [{
750
+ eventType: "ProductDemandRecorded",
751
+ idFields: [
752
+ {
753
+ name: "productId",
754
+ isList: false
755
+ },
756
+ {
757
+ name: "orderId",
758
+ isList: false
759
+ }
760
+ ]
761
+ }], undefined, "ProductDemand");
762
+ let d = DcbScopeInference$Reventless.infer([demand]);
763
+ globalThis.expect(d.partitionBySlice["RecordProductDemand"]).toEqual(undefined);
764
+ globalThis.expect(d.ambiguities.some(param => param[1].includes("The ProductDemand chapter does not decide"))).toBe(true);
765
+ });
766
+ globalThis.test("it never overrides a key the subtraction decided", () => {
767
+ let newrecord = {...addProduct};
768
+ newrecord.chapter = "Category";
769
+ let newrecord$1 = {...addCategory};
770
+ let d = DcbScopeInference$Reventless.infer([
771
+ newrecord,
772
+ (newrecord$1.chapter = "Category", newrecord$1)
773
+ ]);
774
+ globalThis.expect(d.partitionBySlice["AddProduct"]).toEqual("productId");
775
+ });
776
+ });
777
+ globalThis.describe("validatePartitionHintsVsInference (@partitionTag ⇄ inference)", () => {
778
+ let hinted = (s, key) => {
779
+ let newrecord = {...s};
780
+ newrecord.partitionHint = key;
781
+ return newrecord;
782
+ };
783
+ globalThis.test("a hint naming a key the slice only references is a contradiction", () => {
784
+ let issues = DcbValidation$Reventless.validatePartitionHintsVsInference([
785
+ hinted(addProduct, "categoryId"),
786
+ addCategory
787
+ ]);
788
+ globalThis.expect(issues.contradictions.map(e => e.sliceName)).toEqual(["AddProduct"]);
789
+ globalThis.expect(issues.redundancies).toEqual([]);
790
+ });
791
+ globalThis.test("a hint inference reaches unaided is redundant", () => {
792
+ let issues = DcbValidation$Reventless.validatePartitionHintsVsInference([
793
+ hinted(addProduct, "productId"),
794
+ addCategory
795
+ ]);
796
+ globalThis.expect(issues.contradictions).toEqual([]);
797
+ globalThis.expect(issues.redundancies.map(e => e.sliceName)).toEqual(["AddProduct"]);
798
+ });
799
+ globalThis.test("a hint that decides a join is neither", () => {
800
+ let demand = slice("RecordProductDemand", undefined, undefined, [{
801
+ eventType: "ProductDemandRecorded",
802
+ idFields: [
803
+ {
804
+ name: "productId",
805
+ isList: false
806
+ },
807
+ {
808
+ name: "orderId",
809
+ isList: false
810
+ }
811
+ ]
812
+ }], "productId", undefined);
813
+ let issues = DcbValidation$Reventless.validatePartitionHintsVsInference([demand]);
814
+ globalThis.expect(issues.contradictions).toEqual([]);
815
+ globalThis.expect(issues.redundancies).toEqual([]);
816
+ });
817
+ });
406
818
  globalThis.describe("ambiguity surfacing", () => {
407
819
  let aOwner = slice("A", undefined, undefined, [{
408
820
  eventType: "AHappened",
@@ -410,14 +822,14 @@ globalThis.describe("DcbScopeInference:", () => {
410
822
  name: "aId",
411
823
  isList: false
412
824
  }]
413
- }], undefined);
825
+ }], undefined, undefined);
414
826
  let bOwner = slice("B", undefined, undefined, [{
415
827
  eventType: "BHappened",
416
828
  idFields: [{
417
829
  name: "bId",
418
830
  isList: false
419
831
  }]
420
- }], undefined);
832
+ }], undefined, undefined);
421
833
  let join = slice("Join", undefined, [
422
834
  {
423
835
  eventType: "AHappened",
@@ -445,7 +857,7 @@ globalThis.describe("DcbScopeInference:", () => {
445
857
  isList: false
446
858
  }
447
859
  ]
448
- }], undefined);
860
+ }], undefined, undefined);
449
861
  let d = DcbScopeInference$Reventless.infer([
450
862
  aOwner,
451
863
  bOwner,