@reventlessdev/reventless-core 3.0.0-alpha.274 → 3.0.0-alpha.276
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
CHANGED
|
@@ -3,6 +3,20 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# 3.0.0-alpha.276 (2026-09-25)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **spec:** a chapter that carries both keys backs neither ([cbd823e](https://github.com/ReventlessDev/reventless-core/commit/cbd823e4f87ddd5810debd929bb536b2003d0e4c))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# 3.0.0-alpha.275 (2026-09-25)
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* **spec,core:** a partition tag its chapter backs overrides inference ([4244a4a](https://github.com/ReventlessDev/reventless-core/commit/4244a4af9e7aad4378d9b6276ddeb44878e125b2))
|
|
18
|
+
|
|
19
|
+
|
|
6
20
|
# 3.0.0-alpha.274 (2026-09-24)
|
|
7
21
|
|
|
8
22
|
### Features
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-core",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.276",
|
|
4
4
|
"description": "Core package for Reventless framework",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"jest": {
|
|
@@ -30,20 +30,20 @@
|
|
|
30
30
|
"uuid": "^13.0.0",
|
|
31
31
|
"@reventlessdev/rescript-effect": "0.1.0-alpha.34",
|
|
32
32
|
"@reventlessdev/rescript-fast-csv": "2.0.0-alpha.15",
|
|
33
|
-
"@reventlessdev/rescript-hash-object": "1.2.0-alpha.15",
|
|
34
|
-
"@reventlessdev/rescript-jest": "1.0.0-alpha.11",
|
|
35
33
|
"@reventlessdev/rescript-node": "2.0.0-alpha.15",
|
|
34
|
+
"@reventlessdev/rescript-jest": "1.0.0-alpha.11",
|
|
35
|
+
"@reventlessdev/rescript-hash-object": "1.2.0-alpha.15",
|
|
36
36
|
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.22",
|
|
37
37
|
"@reventlessdev/rescript-ssh2": "2.0.0-alpha.15",
|
|
38
38
|
"@reventlessdev/rescript-uuid": "2.0.0-alpha.1",
|
|
39
|
-
"@reventlessdev/reventless-infra": "3.0.0-alpha.
|
|
40
|
-
"@reventlessdev/reventless-spec": "3.0.0-alpha.
|
|
39
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.176",
|
|
40
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.148",
|
|
41
41
|
"@reventlessdev/reventless-interop": "3.0.0-alpha.38"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"rescript": "12.3.1",
|
|
45
45
|
"sury-ppx": "11.0.0",
|
|
46
|
-
"@reventlessdev/reventless-ppx": "1.0.0-alpha.
|
|
46
|
+
"@reventlessdev/reventless-ppx": "1.0.0-alpha.94"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
49
|
"rescript": "^12.3.1"
|
|
@@ -237,11 +237,14 @@ module Make = (
|
|
|
237
237
|
|
|
238
238
|
// A `@partitionTag` inference contradicts would steer storage, fence and read
|
|
239
239
|
// scope to a key the slice only references, so it stops the build. One that
|
|
240
|
-
// inference agrees with is noise, and says so.
|
|
240
|
+
// inference agrees with is noise, and says so. One its chapter backs against
|
|
241
|
+
// inference is the author deciding what inference cannot see, and is kept.
|
|
241
242
|
let hintIssues = Reventless.DcbValidation.validatePartitionHintsVsInference(
|
|
242
243
|
~shapes=inferenceShapes,
|
|
243
244
|
)
|
|
244
|
-
hintIssues.redundancies
|
|
245
|
+
hintIssues.redundancies
|
|
246
|
+
->Array.concat(hintIssues.overrides)
|
|
247
|
+
->Array.forEach(e =>
|
|
245
248
|
log.info(~comp="Dcb_Builder", `DCB partition (${e.sliceName}): ${e.message}`)
|
|
246
249
|
)
|
|
247
250
|
if hintIssues.contradictions->Array.length > 0 {
|
|
@@ -178,7 +178,7 @@ function Make(DcbEventLogStorage) {
|
|
|
178
178
|
}));
|
|
179
179
|
let inferenceShapes = sliceSchemas.map(DcbTag$Reventless.sliceShape);
|
|
180
180
|
let hintIssues = DcbValidation$Reventless.validatePartitionHintsVsInference(inferenceShapes);
|
|
181
|
-
hintIssues.redundancies.forEach(e => log.info("Dcb_Builder", undefined, `DCB partition (` + e.sliceName + `): ` + e.message));
|
|
181
|
+
hintIssues.redundancies.concat(hintIssues.overrides).forEach(e => log.info("Dcb_Builder", undefined, `DCB partition (` + e.sliceName + `): ` + e.message));
|
|
182
182
|
if (hintIssues.contradictions.length !== 0) {
|
|
183
183
|
let detail = hintIssues.contradictions.map(e => e.sliceName + `: ` + e.message).join(" | ");
|
|
184
184
|
log.error("Dcb_Builder", undefined, `DCB partition contradiction (` + name + `): ` + detail);
|
|
@@ -503,6 +503,105 @@ describe("DcbScopeInference:", () => {
|
|
|
503
503
|
expect(issues.redundancies)->toEqual([])
|
|
504
504
|
},
|
|
505
505
|
)
|
|
506
|
+
|
|
507
|
+
// `ShipOrder` records the carrier's booking, which nothing else in the boundary
|
|
508
|
+
// writes or reads. Rule 1 subtracts `orderId` (read off `OrderPlaced`) and is
|
|
509
|
+
// left with the booking: `AddProduct`'s shape exactly, with the other answer.
|
|
510
|
+
let placeOrder = slice(
|
|
511
|
+
"PlaceOrder",
|
|
512
|
+
~chapter="Order",
|
|
513
|
+
~produced=[ev("OrderPlaced", [scal("orderId")])],
|
|
514
|
+
)
|
|
515
|
+
let shipOrder = slice(
|
|
516
|
+
"ShipOrder",
|
|
517
|
+
~chapter="Order",
|
|
518
|
+
~partitionHint="orderId",
|
|
519
|
+
~command=[scal("orderId"), scal("carrierBookingId")],
|
|
520
|
+
~consumed=[ev("OrderPlaced", [scal("orderId")]), ev("OrderShipped", [scal("orderId")])],
|
|
521
|
+
~produced=[ev("OrderShipped", [scal("orderId"), scal("carrierBookingId")])],
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
testSync(
|
|
525
|
+
"a hint its chapter backs overrides inference instead of contradicting it",
|
|
526
|
+
() => {
|
|
527
|
+
let issues = check([placeOrder, shipOrder])
|
|
528
|
+
expect(issues.contradictions)->toEqual([])
|
|
529
|
+
expect(issues.redundancies)->toEqual([])
|
|
530
|
+
expect(issues.overrides->names)->toEqual(["ShipOrder"])
|
|
531
|
+
// and the hint is what the boundary is partitioned by
|
|
532
|
+
expect(
|
|
533
|
+
DSI.resolvePartitions([placeOrder, shipOrder]).partitionBySlice->Dict.get("ShipOrder"),
|
|
534
|
+
)->toEqual(Some("orderId"))
|
|
535
|
+
},
|
|
536
|
+
)
|
|
537
|
+
|
|
538
|
+
testSync(
|
|
539
|
+
"without a chapter the same hint is still a contradiction",
|
|
540
|
+
() => {
|
|
541
|
+
let noChapter = (s: DSI.sliceShape) => {...s, chapter: ?None}
|
|
542
|
+
let issues = check([noChapter(placeOrder), noChapter(shipOrder)])
|
|
543
|
+
expect(issues.contradictions->names)->toEqual(["ShipOrder"])
|
|
544
|
+
expect(issues.overrides)->toEqual([])
|
|
545
|
+
},
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
// Every producer is present, so the arms it reads by `categoryId` all come from
|
|
549
|
+
// category slices: only the chapter can refuse the hint.
|
|
550
|
+
let categories = [
|
|
551
|
+
{...addCategory, chapter: "Category"},
|
|
552
|
+
{...renameCategory, chapter: "Category"},
|
|
553
|
+
{...archiveCategory, chapter: "Category"},
|
|
554
|
+
]
|
|
555
|
+
|
|
556
|
+
testSync(
|
|
557
|
+
"a hint its chapter disagrees with is still a contradiction",
|
|
558
|
+
() => {
|
|
559
|
+
// `RenameProduct` writes only `productId`, so `Product/` is about products.
|
|
560
|
+
let renameProduct = slice(
|
|
561
|
+
"RenameProduct",
|
|
562
|
+
~chapter="Product",
|
|
563
|
+
~consumed=[ev("ProductAdded", [scal("productId")])],
|
|
564
|
+
~produced=[ev("ProductRenamed", [scal("productId")])],
|
|
565
|
+
)
|
|
566
|
+
let inProduct = {...hinted(addProduct, "categoryId"), chapter: "Product"}
|
|
567
|
+
let issues = check([inProduct, renameProduct, ...categories])
|
|
568
|
+
expect(issues.contradictions->names)->toEqual(["AddProduct"])
|
|
569
|
+
expect(issues.overrides)->toEqual([])
|
|
570
|
+
},
|
|
571
|
+
)
|
|
572
|
+
|
|
573
|
+
testSync(
|
|
574
|
+
"a chapter that carries both keys everywhere backs neither",
|
|
575
|
+
() => {
|
|
576
|
+
// One slice, one event with both ids: the chapter cannot say which one it is about.
|
|
577
|
+
let inProduct = {...hinted(addProduct, "categoryId"), chapter: "Product"}
|
|
578
|
+
let issues = check([inProduct, ...categories])
|
|
579
|
+
expect(issues.contradictions->names)->toEqual(["AddProduct"])
|
|
580
|
+
expect(issues.overrides)->toEqual([])
|
|
581
|
+
},
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
testSync(
|
|
585
|
+
"a chapter-backed hint read off another entity's events is still a contradiction",
|
|
586
|
+
() => {
|
|
587
|
+
// `WarehouseAssigned` carries the order but belongs to the warehouse.
|
|
588
|
+
let assign = slice(
|
|
589
|
+
"AssignWarehouse",
|
|
590
|
+
~chapter="Warehouse",
|
|
591
|
+
~partitionHint="warehouseId",
|
|
592
|
+
~produced=[ev("WarehouseAssigned", [scal("warehouseId"), scal("orderId")])],
|
|
593
|
+
)
|
|
594
|
+
let ship = {
|
|
595
|
+
...shipOrder,
|
|
596
|
+
consumed: shipOrder.consumed->Array.concat([
|
|
597
|
+
ev("WarehouseAssigned", [scal("warehouseId"), scal("orderId")]),
|
|
598
|
+
]),
|
|
599
|
+
}
|
|
600
|
+
let issues = check([placeOrder, ship, assign])
|
|
601
|
+
expect(issues.overrides->names)->toEqual([])
|
|
602
|
+
expect(issues.contradictions->names)->toEqual(["ShipOrder"])
|
|
603
|
+
},
|
|
604
|
+
)
|
|
506
605
|
})
|
|
507
606
|
|
|
508
607
|
describe("ambiguity surfacing", () => {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
|
+
import * as Belt_Array from "@rescript/runtime/lib/es6/Belt_Array.js";
|
|
3
4
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
4
5
|
import * as Primitive_string from "@rescript/runtime/lib/es6/Primitive_string.js";
|
|
5
6
|
import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/components/DcbTag.res.mjs";
|
|
@@ -814,6 +815,156 @@ globalThis.describe("DcbScopeInference:", () => {
|
|
|
814
815
|
globalThis.expect(issues.contradictions).toEqual([]);
|
|
815
816
|
globalThis.expect(issues.redundancies).toEqual([]);
|
|
816
817
|
});
|
|
818
|
+
let placeOrder = slice("PlaceOrder", undefined, undefined, [{
|
|
819
|
+
eventType: "OrderPlaced",
|
|
820
|
+
idFields: [{
|
|
821
|
+
name: "orderId",
|
|
822
|
+
isList: false
|
|
823
|
+
}]
|
|
824
|
+
}], undefined, "Order");
|
|
825
|
+
let shipOrder = slice("ShipOrder", [
|
|
826
|
+
{
|
|
827
|
+
name: "orderId",
|
|
828
|
+
isList: false
|
|
829
|
+
},
|
|
830
|
+
{
|
|
831
|
+
name: "carrierBookingId",
|
|
832
|
+
isList: false
|
|
833
|
+
}
|
|
834
|
+
], [
|
|
835
|
+
{
|
|
836
|
+
eventType: "OrderPlaced",
|
|
837
|
+
idFields: [{
|
|
838
|
+
name: "orderId",
|
|
839
|
+
isList: false
|
|
840
|
+
}]
|
|
841
|
+
},
|
|
842
|
+
{
|
|
843
|
+
eventType: "OrderShipped",
|
|
844
|
+
idFields: [{
|
|
845
|
+
name: "orderId",
|
|
846
|
+
isList: false
|
|
847
|
+
}]
|
|
848
|
+
}
|
|
849
|
+
], [{
|
|
850
|
+
eventType: "OrderShipped",
|
|
851
|
+
idFields: [
|
|
852
|
+
{
|
|
853
|
+
name: "orderId",
|
|
854
|
+
isList: false
|
|
855
|
+
},
|
|
856
|
+
{
|
|
857
|
+
name: "carrierBookingId",
|
|
858
|
+
isList: false
|
|
859
|
+
}
|
|
860
|
+
]
|
|
861
|
+
}], "orderId", "Order");
|
|
862
|
+
globalThis.test("a hint its chapter backs overrides inference instead of contradicting it", () => {
|
|
863
|
+
let issues = DcbValidation$Reventless.validatePartitionHintsVsInference([
|
|
864
|
+
placeOrder,
|
|
865
|
+
shipOrder
|
|
866
|
+
]);
|
|
867
|
+
globalThis.expect(issues.contradictions).toEqual([]);
|
|
868
|
+
globalThis.expect(issues.redundancies).toEqual([]);
|
|
869
|
+
globalThis.expect(issues.overrides.map(e => e.sliceName)).toEqual(["ShipOrder"]);
|
|
870
|
+
globalThis.expect(DcbScopeInference$Reventless.resolvePartitions([
|
|
871
|
+
placeOrder,
|
|
872
|
+
shipOrder
|
|
873
|
+
]).partitionBySlice["ShipOrder"]).toEqual("orderId");
|
|
874
|
+
});
|
|
875
|
+
globalThis.test("without a chapter the same hint is still a contradiction", () => {
|
|
876
|
+
let noChapter = s => {
|
|
877
|
+
let newrecord = {...s};
|
|
878
|
+
newrecord.chapter = undefined;
|
|
879
|
+
return newrecord;
|
|
880
|
+
};
|
|
881
|
+
let issues = DcbValidation$Reventless.validatePartitionHintsVsInference([
|
|
882
|
+
noChapter(placeOrder),
|
|
883
|
+
noChapter(shipOrder)
|
|
884
|
+
]);
|
|
885
|
+
globalThis.expect(issues.contradictions.map(e => e.sliceName)).toEqual(["ShipOrder"]);
|
|
886
|
+
globalThis.expect(issues.overrides).toEqual([]);
|
|
887
|
+
});
|
|
888
|
+
let newrecord = {...addCategory};
|
|
889
|
+
let newrecord$1 = {...renameCategory};
|
|
890
|
+
let newrecord$2 = {...archiveCategory};
|
|
891
|
+
let categories = [
|
|
892
|
+
(newrecord.chapter = "Category", newrecord),
|
|
893
|
+
(newrecord$1.chapter = "Category", newrecord$1),
|
|
894
|
+
(newrecord$2.chapter = "Category", newrecord$2)
|
|
895
|
+
];
|
|
896
|
+
globalThis.test("a hint its chapter disagrees with is still a contradiction", () => {
|
|
897
|
+
let renameProduct = slice("RenameProduct", undefined, [{
|
|
898
|
+
eventType: "ProductAdded",
|
|
899
|
+
idFields: [{
|
|
900
|
+
name: "productId",
|
|
901
|
+
isList: false
|
|
902
|
+
}]
|
|
903
|
+
}], [{
|
|
904
|
+
eventType: "ProductRenamed",
|
|
905
|
+
idFields: [{
|
|
906
|
+
name: "productId",
|
|
907
|
+
isList: false
|
|
908
|
+
}]
|
|
909
|
+
}], undefined, "Product");
|
|
910
|
+
let newrecord = {...hinted(addProduct, "categoryId")};
|
|
911
|
+
newrecord.chapter = "Product";
|
|
912
|
+
let issues = DcbValidation$Reventless.validatePartitionHintsVsInference(Belt_Array.concatMany([
|
|
913
|
+
[
|
|
914
|
+
newrecord,
|
|
915
|
+
renameProduct
|
|
916
|
+
],
|
|
917
|
+
categories
|
|
918
|
+
]));
|
|
919
|
+
globalThis.expect(issues.contradictions.map(e => e.sliceName)).toEqual(["AddProduct"]);
|
|
920
|
+
globalThis.expect(issues.overrides).toEqual([]);
|
|
921
|
+
});
|
|
922
|
+
globalThis.test("a chapter that carries both keys everywhere backs neither", () => {
|
|
923
|
+
let newrecord = {...hinted(addProduct, "categoryId")};
|
|
924
|
+
newrecord.chapter = "Product";
|
|
925
|
+
let issues = DcbValidation$Reventless.validatePartitionHintsVsInference(Belt_Array.concatMany([
|
|
926
|
+
[newrecord],
|
|
927
|
+
categories
|
|
928
|
+
]));
|
|
929
|
+
globalThis.expect(issues.contradictions.map(e => e.sliceName)).toEqual(["AddProduct"]);
|
|
930
|
+
globalThis.expect(issues.overrides).toEqual([]);
|
|
931
|
+
});
|
|
932
|
+
globalThis.test("a chapter-backed hint read off another entity's events is still a contradiction", () => {
|
|
933
|
+
let assign = slice("AssignWarehouse", undefined, undefined, [{
|
|
934
|
+
eventType: "WarehouseAssigned",
|
|
935
|
+
idFields: [
|
|
936
|
+
{
|
|
937
|
+
name: "warehouseId",
|
|
938
|
+
isList: false
|
|
939
|
+
},
|
|
940
|
+
{
|
|
941
|
+
name: "orderId",
|
|
942
|
+
isList: false
|
|
943
|
+
}
|
|
944
|
+
]
|
|
945
|
+
}], "warehouseId", "Warehouse");
|
|
946
|
+
let newrecord = {...shipOrder};
|
|
947
|
+
newrecord.consumed = shipOrder.consumed.concat([{
|
|
948
|
+
eventType: "WarehouseAssigned",
|
|
949
|
+
idFields: [
|
|
950
|
+
{
|
|
951
|
+
name: "warehouseId",
|
|
952
|
+
isList: false
|
|
953
|
+
},
|
|
954
|
+
{
|
|
955
|
+
name: "orderId",
|
|
956
|
+
isList: false
|
|
957
|
+
}
|
|
958
|
+
]
|
|
959
|
+
}]);
|
|
960
|
+
let issues = DcbValidation$Reventless.validatePartitionHintsVsInference([
|
|
961
|
+
placeOrder,
|
|
962
|
+
newrecord,
|
|
963
|
+
assign
|
|
964
|
+
]);
|
|
965
|
+
globalThis.expect(issues.overrides.map(e => e.sliceName)).toEqual([]);
|
|
966
|
+
globalThis.expect(issues.contradictions.map(e => e.sliceName)).toEqual(["ShipOrder"]);
|
|
967
|
+
});
|
|
817
968
|
});
|
|
818
969
|
globalThis.describe("ambiguity surfacing", () => {
|
|
819
970
|
let aOwner = slice("A", undefined, undefined, [{
|