@reventlessdev/reventless-gwt 1.0.0-alpha.219 → 1.0.0-alpha.221

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
+ # 1.0.0-alpha.221 (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
+
13
+ # 1.0.0-alpha.220 (2026-09-24)
14
+
15
+ ### Features
16
+
17
+ * **dcb:** a command reference no decision reads by stays out of the query ([3ae149a](https://github.com/ReventlessDev/reventless-core/commit/3ae149ae91ebab63413be57df5827c5fd4390160))
18
+
19
+
6
20
  # 1.0.0-alpha.219 (2026-09-23)
7
21
 
8
22
  **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.219",
3
+ "version": "1.0.0-alpha.221",
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-core": "3.0.0-alpha.274",
20
+ "@reventlessdev/reventless-infra": "3.0.0-alpha.174",
19
21
  "@reventlessdev/rescript-node": "2.0.0-alpha.15",
20
- "@reventlessdev/reventless-core": "3.0.0-alpha.272",
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.146"
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.89"
27
+ "@reventlessdev/reventless-ppx": "1.0.0-alpha.92"
28
28
  },
29
29
  "peerDependencies": {
30
30
  "rescript": "^12.3.1"
@@ -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)
@@ -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)
@@ -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 */