@reventlessdev/reventless-gwt 1.0.0-alpha.219 → 1.0.0-alpha.220
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,13 @@
|
|
|
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.220 (2026-09-24)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **dcb:** a command reference no decision reads by stays out of the query ([3ae149a](https://github.com/ReventlessDev/reventless-core/commit/3ae149ae91ebab63413be57df5827c5fd4390160))
|
|
11
|
+
|
|
12
|
+
|
|
6
13
|
# 1.0.0-alpha.219 (2026-09-23)
|
|
7
14
|
|
|
8
15
|
**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.220",
|
|
4
4
|
"description": "Given-When-Then DSLs and test harness for Reventless slice testing",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"jest": {
|
|
@@ -16,15 +16,15 @@
|
|
|
16
16
|
"sury": "11.0.0",
|
|
17
17
|
"@reventlessdev/rescript-effect": "0.1.0-alpha.34",
|
|
18
18
|
"@reventlessdev/rescript-jest": "1.0.0-alpha.11",
|
|
19
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.173",
|
|
20
|
+
"@reventlessdev/reventless-core": "3.0.0-alpha.273",
|
|
19
21
|
"@reventlessdev/rescript-node": "2.0.0-alpha.15",
|
|
20
|
-
"@reventlessdev/reventless-
|
|
21
|
-
"@reventlessdev/reventless-infra": "3.0.0-alpha.172",
|
|
22
|
-
"@reventlessdev/reventless-spec": "3.0.0-alpha.144"
|
|
22
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.145"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"rescript": "12.3.1",
|
|
26
26
|
"sury-ppx": "11.0.0",
|
|
27
|
-
"@reventlessdev/reventless-ppx": "1.0.0-alpha.
|
|
27
|
+
"@reventlessdev/reventless-ppx": "1.0.0-alpha.92"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
30
|
"rescript": "^12.3.1"
|
package/src/Behavior_GWT.res
CHANGED
|
@@ -280,6 +280,14 @@ module Make = (Spec: BehaviorSpec, Behavior: Behavior with module Spec := Spec):
|
|
|
280
280
|
// emitted event (payload, not a read key) is narrowed out of a cross-partition
|
|
281
281
|
// clause — the same dead-clause removal the runtime threads.
|
|
282
282
|
let tagKeysByEventType = Reventless.DcbScopeInference.infer([scopeShape]).tagKeysByEventType
|
|
283
|
+
// Command keys this slice does not decide by stay out of the query (rule 4),
|
|
284
|
+
// judged against the partition the slice resolves to on its own.
|
|
285
|
+
let payloadTagKeys = scopeShape->Reventless.DcbTag.commandPayloadTagKeys(
|
|
286
|
+
~partitionTag=Reventless.DcbScopeInference.resolvePartitions([scopeShape]).partitionBySlice
|
|
287
|
+
->Dict.get("")
|
|
288
|
+
->Option.map(key => Reventless.DcbTag.Simple({key: key})),
|
|
289
|
+
~crossPartitionTagKeys,
|
|
290
|
+
)
|
|
283
291
|
|
|
284
292
|
// Reachability guard inputs. The event types this slice *emits* are its own
|
|
285
293
|
// history — read with composite AND clauses, which is fine. A *foreign* event
|
|
@@ -338,6 +346,7 @@ module Make = (Spec: BehaviorSpec, Behavior: Behavior with module Spec := Spec):
|
|
|
338
346
|
~value=command,
|
|
339
347
|
~tagKeysByEventType,
|
|
340
348
|
~crossPartitionTagKeys,
|
|
349
|
+
~payloadTagKeys,
|
|
341
350
|
)
|
|
342
351
|
let condition: Reventless.DcbTag.appendCondition = {query: query}
|
|
343
352
|
derivedCondition := Some(condition)
|
package/src/Behavior_GWT.res.mjs
CHANGED
|
@@ -205,6 +205,12 @@ function Make(Spec) {
|
|
|
205
205
|
});
|
|
206
206
|
let crossPartitionTagKeys = Array.from(set.values()).toSorted(Primitive_string.compare);
|
|
207
207
|
let tagKeysByEventType = DcbScopeInference$Reventless.infer([scopeShape]).tagKeysByEventType;
|
|
208
|
+
let payloadTagKeys = DcbTag$Reventless.commandPayloadTagKeys(scopeShape, Stdlib_Option.map(DcbScopeInference$Reventless.resolvePartitions([scopeShape]).partitionBySlice[""], key => ({
|
|
209
|
+
TAG: "Simple",
|
|
210
|
+
_0: {
|
|
211
|
+
key: key
|
|
212
|
+
}
|
|
213
|
+
})), crossPartitionTagKeys);
|
|
208
214
|
let emittedEventTypes = DcbDecode$Reventless.makeDecoder(Spec.eventSchema).eventTypes;
|
|
209
215
|
let consumedTagKeysByType = DcbTag$Reventless.extractTagKeysByEventType(Spec.consumedEventSchema);
|
|
210
216
|
let queryTagKeys = query => query.flatMap(qi => Stdlib_Option.mapOr(qi.tags, [], tags => tags.map(t => t.key)));
|
|
@@ -241,7 +247,7 @@ function Make(Spec) {
|
|
|
241
247
|
let whenCmd = (history, cmd) => {
|
|
242
248
|
errors.contents = [];
|
|
243
249
|
appendConditionFailure.contents = undefined;
|
|
244
|
-
let query = DcbTag$Reventless.buildQueryFromCommand(consumedEventTypes, Spec.commandSchema, cmd, tagKeysByEventType, crossPartitionTagKeys);
|
|
250
|
+
let query = DcbTag$Reventless.buildQueryFromCommand(consumedEventTypes, Spec.commandSchema, cmd, tagKeysByEventType, crossPartitionTagKeys, payloadTagKeys);
|
|
245
251
|
let condition = {
|
|
246
252
|
query: query
|
|
247
253
|
};
|
package/src/Flow_GWT.res
CHANGED
|
@@ -312,6 +312,14 @@ module CommandStep = (
|
|
|
312
312
|
Array.fromIterator(set->Set.values)->Array.toSorted((a, b) => String.compare(a, b))
|
|
313
313
|
}
|
|
314
314
|
let tagKeysByEventType = Reventless.DcbScopeInference.infer([scopeShape]).tagKeysByEventType
|
|
315
|
+
// Command keys this slice does not decide by stay out of the query (rule 4),
|
|
316
|
+
// judged against the partition the slice resolves to on its own.
|
|
317
|
+
let payloadTagKeys = scopeShape->Reventless.DcbTag.commandPayloadTagKeys(
|
|
318
|
+
~partitionTag=Reventless.DcbScopeInference.resolvePartitions([scopeShape]).partitionBySlice
|
|
319
|
+
->Dict.get("")
|
|
320
|
+
->Option.map(key => Reventless.DcbTag.Simple({key: key})),
|
|
321
|
+
~crossPartitionTagKeys,
|
|
322
|
+
)
|
|
315
323
|
|
|
316
324
|
let whenCommand = async (flowP: flow, command: Spec.command) => {
|
|
317
325
|
let s = await flowP
|
|
@@ -321,6 +329,7 @@ module CommandStep = (
|
|
|
321
329
|
~value=command,
|
|
322
330
|
~tagKeysByEventType,
|
|
323
331
|
~crossPartitionTagKeys,
|
|
332
|
+
~payloadTagKeys,
|
|
324
333
|
)
|
|
325
334
|
let history = decodeMatching(s.log, consumedDecoder, query)
|
|
326
335
|
let state = history->Array.reduce(Behavior.initialState, Behavior.evolve)
|
package/src/Flow_GWT.res.mjs
CHANGED
|
@@ -192,9 +192,15 @@ function CommandStep(Spec) {
|
|
|
192
192
|
});
|
|
193
193
|
let crossPartitionTagKeys = Array.from(set.values()).toSorted(Primitive_string.compare);
|
|
194
194
|
let tagKeysByEventType = DcbScopeInference$Reventless.infer([scopeShape]).tagKeysByEventType;
|
|
195
|
+
let payloadTagKeys = DcbTag$Reventless.commandPayloadTagKeys(scopeShape, Stdlib_Option.map(DcbScopeInference$Reventless.resolvePartitions([scopeShape]).partitionBySlice[""], key => ({
|
|
196
|
+
TAG: "Simple",
|
|
197
|
+
_0: {
|
|
198
|
+
key: key
|
|
199
|
+
}
|
|
200
|
+
})), crossPartitionTagKeys);
|
|
195
201
|
let whenCommand = async (flowP, command) => {
|
|
196
202
|
let s = await flowP;
|
|
197
|
-
let query = DcbTag$Reventless.buildQueryFromCommand(consumedEventTypes, Spec.commandSchema, command, tagKeysByEventType, crossPartitionTagKeys);
|
|
203
|
+
let query = DcbTag$Reventless.buildQueryFromCommand(consumedEventTypes, Spec.commandSchema, command, tagKeysByEventType, crossPartitionTagKeys, payloadTagKeys);
|
|
198
204
|
let history = decodeMatching(s.log, consumedDecoder, query);
|
|
199
205
|
let state = Stdlib_Array.reduce(history, Behavior.initialState, Behavior.evolve);
|
|
200
206
|
let events = Behavior.decide(state, command);
|
|
@@ -265,6 +271,7 @@ function CommandStep(Spec) {
|
|
|
265
271
|
scopeShape: scopeShape,
|
|
266
272
|
crossPartitionTagKeys: crossPartitionTagKeys,
|
|
267
273
|
tagKeysByEventType: tagKeysByEventType,
|
|
274
|
+
payloadTagKeys: payloadTagKeys,
|
|
268
275
|
whenCommand: whenCommand,
|
|
269
276
|
thenEvents: thenEvents,
|
|
270
277
|
thenEvent: thenEvent,
|
|
@@ -159,3 +159,83 @@ MissingTagGwt.describe("MissingTag slice implicit check", () => {
|
|
|
159
159
|
}),
|
|
160
160
|
)
|
|
161
161
|
})
|
|
162
|
+
|
|
163
|
+
// A command naming a second thing only as information: `carrierId` is tagged by
|
|
164
|
+
// its name, but no decision is made per carrier. Its tag stays on the command and
|
|
165
|
+
// out of the query, so the order's own history — which never carries the carrier —
|
|
166
|
+
// is still selected.
|
|
167
|
+
module ShipOrderSlice = {
|
|
168
|
+
let name = "ShipOrder"
|
|
169
|
+
|
|
170
|
+
@schema
|
|
171
|
+
type consumedEvent =
|
|
172
|
+
| OrderPlaced({orderId: @s.matches(Reventless.DcbTag.string) string})
|
|
173
|
+
| OrderShipped({orderId: @s.matches(Reventless.DcbTag.string) string})
|
|
174
|
+
|
|
175
|
+
@schema
|
|
176
|
+
type command =
|
|
177
|
+
| ShipOrder({
|
|
178
|
+
orderId: @s.matches(Reventless.DcbTag.string) string,
|
|
179
|
+
carrierId: @s.matches(Reventless.DcbTag.string) string,
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
@schema
|
|
183
|
+
type error = OrderNotPlaced | AlreadyShipped
|
|
184
|
+
|
|
185
|
+
// Seen alone the slice has no chapter to say that two ids on its event mean
|
|
186
|
+
// "the order", so the partition is named, as `@partitionTag` would.
|
|
187
|
+
@schema
|
|
188
|
+
type event =
|
|
189
|
+
| OrderShipped({
|
|
190
|
+
orderId: @s.matches(Reventless.DcbTag.partition) string,
|
|
191
|
+
carrierId: @s.matches(Reventless.DcbTag.string) string,
|
|
192
|
+
})
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
module ShipOrderBehavior = {
|
|
196
|
+
module Spec = ShipOrderSlice
|
|
197
|
+
|
|
198
|
+
type state = {placed: bool, shipped: bool}
|
|
199
|
+
let initialState: state = {placed: false, shipped: false}
|
|
200
|
+
|
|
201
|
+
let evolve = (state: state, event: Spec.consumedEvent): state =>
|
|
202
|
+
switch event {
|
|
203
|
+
| Spec.OrderPlaced(_) => {...state, placed: true}
|
|
204
|
+
| Spec.OrderShipped(_) => {...state, shipped: true}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
let decide = (state: state, cmd: Spec.command): result<array<Spec.event>, Spec.error> =>
|
|
208
|
+
switch cmd {
|
|
209
|
+
| Spec.ShipOrder({orderId, carrierId}) =>
|
|
210
|
+
if !state.placed {
|
|
211
|
+
Error(Spec.OrderNotPlaced)
|
|
212
|
+
} else if state.shipped {
|
|
213
|
+
Error(Spec.AlreadyShipped)
|
|
214
|
+
} else {
|
|
215
|
+
Ok([Spec.OrderShipped({orderId, carrierId})])
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
module ShipOrderGwt = Behavior_GWT.Make(ShipOrderSlice, ShipOrderBehavior)
|
|
221
|
+
|
|
222
|
+
ShipOrderGwt.describe("ShipOrder — a command reference no decision reads by", () => {
|
|
223
|
+
ShipOrderGwt.test("reads the order's history without the carrier", () =>
|
|
224
|
+
ShipOrderGwt.givenEvents([ShipOrderSlice.OrderPlaced({orderId: "o1"})])
|
|
225
|
+
->ShipOrderGwt.whenCmd(ShipOrderSlice.ShipOrder({orderId: "o1", carrierId: "c1"}))
|
|
226
|
+
->ShipOrderGwt.thenEvent(ShipOrderSlice.OrderShipped({orderId: "o1", carrierId: "c1"}))
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
ShipOrderGwt.test("the append condition is the order alone", () =>
|
|
230
|
+
ShipOrderGwt.givenEvents([ShipOrderSlice.OrderPlaced({orderId: "o1"})])
|
|
231
|
+
->ShipOrderGwt.whenCmd(ShipOrderSlice.ShipOrder({orderId: "o1", carrierId: "c1"}))
|
|
232
|
+
->ShipOrderGwt.thenAppendsConditionedOnExactly({
|
|
233
|
+
query: [
|
|
234
|
+
{
|
|
235
|
+
eventTypes: ["OrderPlaced", "OrderShipped"],
|
|
236
|
+
tags: [{key: "orderId", value: "o1"}],
|
|
237
|
+
},
|
|
238
|
+
],
|
|
239
|
+
})
|
|
240
|
+
)
|
|
241
|
+
})
|
|
@@ -260,6 +260,147 @@ MissingTagGwt.describe("MissingTag slice implicit check", () => {
|
|
|
260
260
|
}));
|
|
261
261
|
});
|
|
262
262
|
|
|
263
|
+
let consumedEventSchema$2 = Sury.union([
|
|
264
|
+
Sury.$schema(s => ({
|
|
265
|
+
TAG: "OrderPlaced",
|
|
266
|
+
orderId: s.m(DcbTag$Reventless.string)
|
|
267
|
+
})),
|
|
268
|
+
Sury.$schema(s => ({
|
|
269
|
+
TAG: "OrderShipped",
|
|
270
|
+
orderId: s.m(DcbTag$Reventless.string)
|
|
271
|
+
}))
|
|
272
|
+
]);
|
|
273
|
+
|
|
274
|
+
let commandSchema$2 = Sury.$schema(s => ({
|
|
275
|
+
TAG: "ShipOrder",
|
|
276
|
+
orderId: s.m(DcbTag$Reventless.string),
|
|
277
|
+
carrierId: s.m(DcbTag$Reventless.string)
|
|
278
|
+
}));
|
|
279
|
+
|
|
280
|
+
let errorSchema$2 = Sury.union([
|
|
281
|
+
Sury.literal("OrderNotPlaced"),
|
|
282
|
+
Sury.literal("AlreadyShipped")
|
|
283
|
+
]);
|
|
284
|
+
|
|
285
|
+
let eventSchema$2 = Sury.$schema(s => ({
|
|
286
|
+
TAG: "OrderShipped",
|
|
287
|
+
orderId: s.m(DcbTag$Reventless.partition),
|
|
288
|
+
carrierId: s.m(DcbTag$Reventless.string)
|
|
289
|
+
}));
|
|
290
|
+
|
|
291
|
+
function commandAuthorization$2(param) {
|
|
292
|
+
return "AllowAuthenticated";
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function commandTransition$2(param) {
|
|
296
|
+
return "Undeclared";
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
let traits$2 = [];
|
|
300
|
+
|
|
301
|
+
let ShipOrderSlice = {
|
|
302
|
+
name: "ShipOrder",
|
|
303
|
+
consumedEventSchema: consumedEventSchema$2,
|
|
304
|
+
commandSchema: commandSchema$2,
|
|
305
|
+
errorSchema: errorSchema$2,
|
|
306
|
+
eventSchema: eventSchema$2,
|
|
307
|
+
commandAuthorization: commandAuthorization$2,
|
|
308
|
+
commandTransition: commandTransition$2,
|
|
309
|
+
traits: traits$2,
|
|
310
|
+
readConsistency: "EscalateOnRetry"
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
let initialState$1 = {
|
|
314
|
+
placed: false,
|
|
315
|
+
shipped: false
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
function evolve$2(state, event) {
|
|
319
|
+
if (event.TAG === "OrderPlaced") {
|
|
320
|
+
return {
|
|
321
|
+
placed: true,
|
|
322
|
+
shipped: state.shipped
|
|
323
|
+
};
|
|
324
|
+
} else {
|
|
325
|
+
return {
|
|
326
|
+
placed: state.placed,
|
|
327
|
+
shipped: true
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function decide$2(state, cmd) {
|
|
333
|
+
if (state.placed) {
|
|
334
|
+
if (state.shipped) {
|
|
335
|
+
return {
|
|
336
|
+
TAG: "Error",
|
|
337
|
+
_0: "AlreadyShipped"
|
|
338
|
+
};
|
|
339
|
+
} else {
|
|
340
|
+
return {
|
|
341
|
+
TAG: "Ok",
|
|
342
|
+
_0: [{
|
|
343
|
+
TAG: "OrderShipped",
|
|
344
|
+
orderId: cmd.orderId,
|
|
345
|
+
carrierId: cmd.carrierId
|
|
346
|
+
}]
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
} else {
|
|
350
|
+
return {
|
|
351
|
+
TAG: "Error",
|
|
352
|
+
_0: "OrderNotPlaced"
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
let ShipOrderBehavior = {
|
|
358
|
+
Spec: undefined,
|
|
359
|
+
initialState: initialState$1,
|
|
360
|
+
evolve: evolve$2,
|
|
361
|
+
decide: decide$2
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
let ShipOrderGwt = Behavior_GWT$ReventlessGwt.Make(ShipOrderSlice)({
|
|
365
|
+
initialState: initialState$1,
|
|
366
|
+
evolve: evolve$2,
|
|
367
|
+
decide: decide$2
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
ShipOrderGwt.describe("ShipOrder — a command reference no decision reads by", () => {
|
|
371
|
+
ShipOrderGwt.test("reads the order's history without the carrier", () => ShipOrderGwt.thenEvent(ShipOrderGwt.whenCmd(ShipOrderGwt.givenEvents([{
|
|
372
|
+
TAG: "OrderPlaced",
|
|
373
|
+
orderId: "o1"
|
|
374
|
+
}]), {
|
|
375
|
+
TAG: "ShipOrder",
|
|
376
|
+
orderId: "o1",
|
|
377
|
+
carrierId: "c1"
|
|
378
|
+
}), {
|
|
379
|
+
TAG: "OrderShipped",
|
|
380
|
+
orderId: "o1",
|
|
381
|
+
carrierId: "c1"
|
|
382
|
+
}));
|
|
383
|
+
ShipOrderGwt.test("the append condition is the order alone", () => ShipOrderGwt.thenAppendsConditionedOnExactly(ShipOrderGwt.whenCmd(ShipOrderGwt.givenEvents([{
|
|
384
|
+
TAG: "OrderPlaced",
|
|
385
|
+
orderId: "o1"
|
|
386
|
+
}]), {
|
|
387
|
+
TAG: "ShipOrder",
|
|
388
|
+
orderId: "o1",
|
|
389
|
+
carrierId: "c1"
|
|
390
|
+
}), {
|
|
391
|
+
query: [{
|
|
392
|
+
eventTypes: [
|
|
393
|
+
"OrderPlaced",
|
|
394
|
+
"OrderShipped"
|
|
395
|
+
],
|
|
396
|
+
tags: [{
|
|
397
|
+
key: "orderId",
|
|
398
|
+
value: "o1"
|
|
399
|
+
}]
|
|
400
|
+
}]
|
|
401
|
+
}));
|
|
402
|
+
});
|
|
403
|
+
|
|
263
404
|
let Spec = include.Spec;
|
|
264
405
|
|
|
265
406
|
let todo = include.todo;
|
|
@@ -292,5 +433,8 @@ export {
|
|
|
292
433
|
MissingTagSlice,
|
|
293
434
|
MissingTagBehavior,
|
|
294
435
|
MissingTagGwt,
|
|
436
|
+
ShipOrderSlice,
|
|
437
|
+
ShipOrderBehavior,
|
|
438
|
+
ShipOrderGwt,
|
|
295
439
|
}
|
|
296
440
|
/* consumedEventSchema Not a pure module */
|