@reventlessdev/reventless-gwt 1.0.0-alpha.209 → 1.0.0-alpha.211
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 +22 -0
- package/package.json +6 -6
- package/src/Behavior_GWT.res.mjs +1 -1
- package/src/Flow_GWT.res +21 -4
- package/src/Flow_GWT.res.mjs +29 -6
- package/src/Hint.res +5 -0
- package/src/Hint.res.mjs +6 -0
- package/src/MismatchRender.res +4 -0
- package/src/MismatchRender.res.mjs +12 -0
- package/src/Outcome.res +7 -0
- package/src/Outcome.res.mjs +4 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,28 @@
|
|
|
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
|
+
# 1.0.0-alpha.211 (2026-09-20)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @reventlessdev/reventless-gwt
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# 1.0.0-alpha.210 (2026-09-16)
|
|
15
|
+
|
|
16
|
+
* feat(spec)!: infer the DCB partition key per slice, and drop @partitionTag where inference agrees ([6c10161](https://github.com/ReventlessDev/reventless-core/commit/6c101611d438beea4bc9175ae8ea0ea1f6f7493b))
|
|
17
|
+
### Features
|
|
18
|
+
|
|
19
|
+
* **spec:** let a slice's chapter break a partition-key tie ([2b9ae77](https://github.com/ReventlessDev/reventless-core/commit/2b9ae77e98eb42177d2bdd4977bc62bb7c7911aa))
|
|
20
|
+
|
|
21
|
+
### BREAKING CHANGES
|
|
22
|
+
|
|
23
|
+
* DcbTag.derivePartitionTag is removed; a boundary whose
|
|
24
|
+
partition cannot be inferred no longer deploys.
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
6
28
|
# 1.0.0-alpha.209 (2026-09-11)
|
|
7
29
|
|
|
8
30
|
**Note:** Version bump only for package @reventlessdev/reventless-gwt
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-gwt",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.211",
|
|
4
4
|
"description": "Given-When-Then DSLs and test harness for Reventless slice testing",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"jest": {
|
|
@@ -14,12 +14,12 @@
|
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"sury": "11.0.0-rc.2",
|
|
17
|
+
"@reventlessdev/reventless-core": "3.0.0-alpha.264",
|
|
18
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.165",
|
|
19
|
+
"@reventlessdev/rescript-node": "2.0.0-alpha.12",
|
|
20
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.137",
|
|
17
21
|
"@reventlessdev/rescript-effect": "0.1.0-alpha.33",
|
|
18
|
-
"@reventlessdev/rescript-jest": "1.0.0-alpha.10"
|
|
19
|
-
"@reventlessdev/rescript-node": "2.0.0-alpha.11",
|
|
20
|
-
"@reventlessdev/reventless-core": "3.0.0-alpha.262",
|
|
21
|
-
"@reventlessdev/reventless-infra": "3.0.0-alpha.163",
|
|
22
|
-
"@reventlessdev/reventless-spec": "3.0.0-alpha.135"
|
|
22
|
+
"@reventlessdev/rescript-jest": "1.0.0-alpha.10"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"rescript": "12.3.0",
|
package/src/Behavior_GWT.res.mjs
CHANGED
|
@@ -195,7 +195,7 @@ function Make(Spec) {
|
|
|
195
195
|
contents: undefined
|
|
196
196
|
};
|
|
197
197
|
let consumedEventTypes = DcbDecode$Reventless.makeDecoder(Spec.consumedEventSchema).eventTypes;
|
|
198
|
-
let scopeShape = DcbTag$Reventless.sliceShapeFromSchemas("", Spec.commandSchema, Spec.consumedEventSchema, Spec.eventSchema);
|
|
198
|
+
let scopeShape = DcbTag$Reventless.sliceShapeFromSchemas("", Spec.commandSchema, Spec.consumedEventSchema, Spec.eventSchema, undefined);
|
|
199
199
|
let set = new Set();
|
|
200
200
|
DcbTag$Reventless.extractCrossPartitionTagKeys(Spec.eventSchema).forEach(k => {
|
|
201
201
|
set.add(k);
|
package/src/Flow_GWT.res
CHANGED
|
@@ -239,10 +239,27 @@ let thenBoundaryScopeResolves = async (
|
|
|
239
239
|
) => {
|
|
240
240
|
let s = await flowP
|
|
241
241
|
let scope = Reventless.DcbTag.deriveEffectiveScope(slices)
|
|
242
|
-
//
|
|
243
|
-
//
|
|
244
|
-
|
|
245
|
-
|
|
242
|
+
// Deploy and boot derive the storage partition from the same slices and refuse
|
|
243
|
+
// the boundary when it fails, so the test fails with their reasons.
|
|
244
|
+
let partitionReasons = {
|
|
245
|
+
let hints = Reventless.DcbValidation.validatePartitionHintsVsInference(
|
|
246
|
+
~shapes=slices->Array.map(Reventless.DcbTag.sliceShape),
|
|
247
|
+
)
|
|
248
|
+
let contradictions = hints.contradictions->Array.map(e => `${e.sliceName}: ${e.message}`)
|
|
249
|
+
switch Reventless.DcbTag.deriveBoundaryPartition(slices) {
|
|
250
|
+
| _ => contradictions
|
|
251
|
+
| exception JsExn(err) =>
|
|
252
|
+
contradictions->Array.concat([
|
|
253
|
+
JsExn.message(err)->Option.getOr("partition derivation failed"),
|
|
254
|
+
])
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
// Beyond that, an ambiguity that costs nothing is not a failure: a boundary whose
|
|
258
|
+
// fallback carries every derived key reads exactly as the derivation would. What
|
|
259
|
+
// fails is losing a key, because that is the case where the runtime answers wrongly.
|
|
260
|
+
let outcome = if partitionReasons->Array.length > 0 {
|
|
261
|
+
Outcome.fail(PartitionUnresolved({boundary: name, reasons: partitionReasons}))
|
|
262
|
+
} else if scope.droppedCrossPartitionTagKeys->Array.length > 0 {
|
|
246
263
|
Outcome.fail(
|
|
247
264
|
ScopeDegraded({
|
|
248
265
|
boundary: name,
|
package/src/Flow_GWT.res.mjs
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
3
|
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
4
|
+
import * as Stdlib_JsExn from "@rescript/runtime/lib/es6/Stdlib_JsExn.js";
|
|
4
5
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
5
6
|
import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
|
|
6
7
|
import * as Primitive_string from "@rescript/runtime/lib/es6/Primitive_string.js";
|
|
7
8
|
import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/components/DcbTag.res.mjs";
|
|
8
9
|
import * as Message$Reventless from "@reventlessdev/reventless-spec/src/types/Message.res.mjs";
|
|
9
10
|
import * as DcbDecode$Reventless from "@reventlessdev/reventless-spec/src/components/DcbDecode.res.mjs";
|
|
11
|
+
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
10
12
|
import * as Outcome$ReventlessGwt from "./Outcome.res.mjs";
|
|
11
13
|
import * as JestBind$ReventlessGwt from "./JestBind.res.mjs";
|
|
12
14
|
import * as Message$ReventlessCore from "@reventlessdev/reventless-core/src/Message.res.mjs";
|
|
15
|
+
import * as DcbValidation$Reventless from "@reventlessdev/reventless-spec/src/components/DcbValidation.res.mjs";
|
|
13
16
|
import * as StubRuntime$ReventlessGwt from "./StubRuntime.res.mjs";
|
|
14
17
|
import * as DcbScopeInference$Reventless from "@reventlessdev/reventless-spec/src/components/DcbScopeInference.res.mjs";
|
|
15
18
|
import * as Projection_GWT$ReventlessGwt from "./Projection_GWT.res.mjs";
|
|
@@ -134,12 +137,32 @@ function test(name, timeout, body) {
|
|
|
134
137
|
async function thenBoundaryScopeResolves(flowP, name, slices) {
|
|
135
138
|
let s = await flowP;
|
|
136
139
|
let scope = DcbTag$Reventless.deriveEffectiveScope(slices);
|
|
137
|
-
let
|
|
138
|
-
|
|
140
|
+
let hints = DcbValidation$Reventless.validatePartitionHintsVsInference(slices.map(DcbTag$Reventless.sliceShape));
|
|
141
|
+
let contradictions = hints.contradictions.map(e => e.sliceName + `: ` + e.message);
|
|
142
|
+
let partitionReasons;
|
|
143
|
+
try {
|
|
144
|
+
DcbTag$Reventless.deriveBoundaryPartition(slices);
|
|
145
|
+
partitionReasons = contradictions;
|
|
146
|
+
} catch (raw_err) {
|
|
147
|
+
let err = Primitive_exceptions.internalToException(raw_err);
|
|
148
|
+
if (err.RE_EXN_ID === "JsExn") {
|
|
149
|
+
partitionReasons = contradictions.concat([Stdlib_Option.getOr(Stdlib_JsExn.message(err._1), "partition derivation failed")]);
|
|
150
|
+
} else {
|
|
151
|
+
throw err;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
let outcome = partitionReasons.length !== 0 ? Outcome$ReventlessGwt.fail({
|
|
155
|
+
TAG: "PartitionUnresolved",
|
|
139
156
|
boundary: name,
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
157
|
+
reasons: partitionReasons
|
|
158
|
+
}) : (
|
|
159
|
+
scope.droppedCrossPartitionTagKeys.length !== 0 ? Outcome$ReventlessGwt.fail({
|
|
160
|
+
TAG: "ScopeDegraded",
|
|
161
|
+
boundary: name,
|
|
162
|
+
dropped: scope.droppedCrossPartitionTagKeys,
|
|
163
|
+
ambiguities: scope.ambiguities.map(param => param[0] + `: ` + param[1])
|
|
164
|
+
}) : Outcome$ReventlessGwt.pass
|
|
165
|
+
);
|
|
143
166
|
return recordOutcome(s, outcome);
|
|
144
167
|
}
|
|
145
168
|
|
|
@@ -159,7 +182,7 @@ function CommandStep(Spec) {
|
|
|
159
182
|
lastAggregateId: s.lastAggregateId
|
|
160
183
|
};
|
|
161
184
|
};
|
|
162
|
-
let scopeShape = DcbTag$Reventless.sliceShapeFromSchemas("", Spec.commandSchema, Spec.consumedEventSchema, Spec.eventSchema);
|
|
185
|
+
let scopeShape = DcbTag$Reventless.sliceShapeFromSchemas("", Spec.commandSchema, Spec.consumedEventSchema, Spec.eventSchema, undefined);
|
|
163
186
|
let set = new Set();
|
|
164
187
|
DcbTag$Reventless.extractCrossPartitionTagKeys(Spec.eventSchema).forEach(k => {
|
|
165
188
|
set.add(k);
|
package/src/Hint.res
CHANGED
|
@@ -100,6 +100,11 @@ let forMismatch = (~slice="<slice>", m: Outcome.mismatch): t =>
|
|
|
100
100
|
", ",
|
|
101
101
|
)}] were lost. A slice referencing another entity by one of these keys now decides against an empty history and rejects valid commands. Fix the named slice — usually a consumed arm declaring the id the slice is already partitioned by.`,
|
|
102
102
|
}
|
|
103
|
+
| PartitionUnresolved(_) => {
|
|
104
|
+
locus: `${slice} (DCB boundary)`,
|
|
105
|
+
branch: None,
|
|
106
|
+
message: "A slice in this boundary has no derivable partition key, or a @partitionTag names a key inference contradicts. Deploy and boot refuse the boundary. Follow the named reason: usually a consumed arm declaring the slice's own id, or an annotation to add or move.",
|
|
107
|
+
}
|
|
103
108
|
| Throw({error}) => {
|
|
104
109
|
locus: `${slice}`,
|
|
105
110
|
branch: None,
|
package/src/Hint.res.mjs
CHANGED
|
@@ -95,6 +95,12 @@ function forMismatch(sliceOpt, m) {
|
|
|
95
95
|
branch: undefined,
|
|
96
96
|
message: `A slice in this boundary has no derivable partition key, so the derived DCB scope was discarded for all of them and the cross-partition reads of [` + m.dropped.join(", ") + `] were lost. A slice referencing another entity by one of these keys now decides against an empty history and rejects valid commands. Fix the named slice — usually a consumed arm declaring the id the slice is already partitioned by.`
|
|
97
97
|
};
|
|
98
|
+
case "PartitionUnresolved" :
|
|
99
|
+
return {
|
|
100
|
+
locus: slice + ` (DCB boundary)`,
|
|
101
|
+
branch: undefined,
|
|
102
|
+
message: "A slice in this boundary has no derivable partition key, or a @partitionTag names a key inference contradicts. Deploy and boot refuse the boundary. Follow the named reason: usually a consumed arm declaring the slice's own id, or an annotation to add or move."
|
|
103
|
+
};
|
|
98
104
|
case "Throw" :
|
|
99
105
|
return {
|
|
100
106
|
locus: slice,
|
package/src/MismatchRender.res
CHANGED
|
@@ -76,6 +76,10 @@ let normalize = (m: Outcome.mismatch): normalized => {
|
|
|
76
76
|
Expected(`${boundary}: cross-partition reads [${dropped->Array.join(", ")}] derived`),
|
|
77
77
|
Actual(ambiguities->Array.join(" | ")),
|
|
78
78
|
]
|
|
79
|
+
| PartitionUnresolved({boundary, reasons}) => [
|
|
80
|
+
Expected(`${boundary}: every slice's partition key derived`),
|
|
81
|
+
Actual(reasons->Array.join(" | ")),
|
|
82
|
+
]
|
|
79
83
|
| Throw({error, stack}) => [Error(error), Stack(stack)]
|
|
80
84
|
}
|
|
81
85
|
{kind: Outcome.kindName(m), fields}
|
|
@@ -94,6 +94,18 @@ function normalize(m) {
|
|
|
94
94
|
}
|
|
95
95
|
];
|
|
96
96
|
break;
|
|
97
|
+
case "PartitionUnresolved" :
|
|
98
|
+
fields = [
|
|
99
|
+
{
|
|
100
|
+
TAG: "Expected",
|
|
101
|
+
_0: m.boundary + `: every slice's partition key derived`
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
TAG: "Actual",
|
|
105
|
+
_0: m.reasons.join(" | ")
|
|
106
|
+
}
|
|
107
|
+
];
|
|
108
|
+
break;
|
|
97
109
|
case "Throw" :
|
|
98
110
|
fields = [
|
|
99
111
|
{
|
package/src/Outcome.res
CHANGED
|
@@ -29,6 +29,10 @@ type mismatch =
|
|
|
29
29
|
// is exactly why it needs its own outcome: the defect is in the composition,
|
|
30
30
|
// and nothing that tests one slice can see it.
|
|
31
31
|
| ScopeDegraded({boundary: string, dropped: array<string>, ambiguities: array<string>})
|
|
32
|
+
// A DCB consistency boundary whose storage partition cannot be derived: a slice
|
|
33
|
+
// with no inferable key, or a `@partitionTag` inference contradicts. Deploy and
|
|
34
|
+
// boot refuse such a boundary, so its flow test fails with the same reasons.
|
|
35
|
+
| PartitionUnresolved({boundary: string, reasons: array<string>})
|
|
32
36
|
| Throw({error: string, stack: string})
|
|
33
37
|
|
|
34
38
|
type outcome = result<unit, mismatch>
|
|
@@ -48,6 +52,7 @@ let kindName = (m: mismatch) =>
|
|
|
48
52
|
| QueryRowsMismatch(_) => "QueryRowsMismatch"
|
|
49
53
|
| PublishedActionsMismatch(_) => "PublishedActionsMismatch"
|
|
50
54
|
| ScopeDegraded(_) => "ScopeDegraded"
|
|
55
|
+
| PartitionUnresolved(_) => "PartitionUnresolved"
|
|
51
56
|
| Throw(_) => "Throw"
|
|
52
57
|
}
|
|
53
58
|
|
|
@@ -103,5 +108,7 @@ let format = (m: mismatch) =>
|
|
|
103
108
|
`ScopeDegraded:\n boundary: ${boundary}\n dropped: ${dropped->Array.join(
|
|
104
109
|
", ",
|
|
105
110
|
)}\n cause: ${ambiguities->Array.join(" | ")}`
|
|
111
|
+
| PartitionUnresolved({boundary, reasons}) =>
|
|
112
|
+
`PartitionUnresolved:\n boundary: ${boundary}\n cause: ${reasons->Array.join(" | ")}`
|
|
106
113
|
| Throw({error, stack}) => `Throw: ${error}\n${stack}`
|
|
107
114
|
}
|
package/src/Outcome.res.mjs
CHANGED
|
@@ -31,6 +31,8 @@ function kindName(m) {
|
|
|
31
31
|
return "PublishedActionsMismatch";
|
|
32
32
|
case "ScopeDegraded" :
|
|
33
33
|
return "ScopeDegraded";
|
|
34
|
+
case "PartitionUnresolved" :
|
|
35
|
+
return "PartitionUnresolved";
|
|
34
36
|
case "Throw" :
|
|
35
37
|
return "Throw";
|
|
36
38
|
}
|
|
@@ -75,6 +77,8 @@ function format(m) {
|
|
|
75
77
|
return `PublishedActionsMismatch:\n expected: ` + stringifyJsonArray(m.expected) + `\n actual: ` + stringifyJsonArray(m.actual);
|
|
76
78
|
case "ScopeDegraded" :
|
|
77
79
|
return `ScopeDegraded:\n boundary: ` + m.boundary + `\n dropped: ` + m.dropped.join(", ") + `\n cause: ` + m.ambiguities.join(" | ");
|
|
80
|
+
case "PartitionUnresolved" :
|
|
81
|
+
return `PartitionUnresolved:\n boundary: ` + m.boundary + `\n cause: ` + m.reasons.join(" | ");
|
|
78
82
|
case "Throw" :
|
|
79
83
|
return `Throw: ` + m.error + `\n` + m.stack;
|
|
80
84
|
}
|