@reventlessdev/reventless-gwt 1.0.0-alpha.248 → 1.0.0-alpha.249
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 +14 -0
- package/README.md +1 -1
- package/package.json +7 -7
- package/src/.gwtignore +1 -1
- package/src/{Behavior_GWT.res → Decision_GWT.res} +25 -25
- package/src/{Behavior_GWT.res.mjs → Decision_GWT.res.mjs} +10 -10
- package/src/Delegate_GWT.res +1 -1
- package/src/EventMapping_GWT.res +7 -7
- package/src/EventMapping_GWT.res.mjs +3 -3
- package/src/Flow_GWT.res +14 -14
- package/src/Flow_GWT.res.mjs +6 -6
- package/src/Hint.res +1 -1
- package/src/Mapping_GWT.res +8 -8
- package/src/Mapping_GWT.res.mjs +6 -6
- package/src/Outcome.res +1 -1
- package/src/Query_GWT.res +1 -1
- package/src/RunnerHook.res +1 -1
- package/src/ScenarioRecorder.res +2 -2
- package/src/ScenarioRecorder.res.mjs +1 -1
- package/src/StateChange/ExternalAddCategorySlice.res +1 -1
- package/src/StateChange/WithFixtures.res +1 -1
- package/src/StateChange/WithManualOpen.res +1 -1
- package/tests/CallerTest.res +7 -7
- package/tests/CallerTest.res.mjs +8 -8
- package/tests/FlowAggregateGwtTest.res +6 -6
- package/tests/FlowAggregateGwtTest.res.mjs +6 -6
- package/tests/FlowCrossPluginGwtTest.res +8 -8
- package/tests/FlowCrossPluginGwtTest.res.mjs +8 -8
- package/tests/FlowGwtTest.res +4 -4
- package/tests/FlowGwtTest.res.mjs +4 -4
- package/tests/HintTest.res +2 -2
- package/tests/HintTest.res.mjs +1 -1
- package/tests/MappingGwtTest.res +5 -5
- package/tests/MappingGwtTest.res.mjs +6 -6
- package/tests/ScenarioRecorderTest.res +1 -1
- package/tests/StateChange/ExternalAddCategorySlice_GWT.res +3 -3
- package/tests/StateChange/ExternalAddCategorySlice_GWT.res.mjs +5 -5
- package/tests/StateChange/WithFixtures_GWT.res.mjs +5 -5
- package/tests/StateChange/WithManualOpen_GWT.res.mjs +5 -5
- package/tests/StateChangeSliceAuthorizeGwtTest.res +10 -10
- package/tests/StateChangeSliceAuthorizeGwtTest.res.mjs +14 -14
- package/tests/StateChangeSliceGwtTest.res +9 -9
- package/tests/StateChangeSliceGwtTest.res.mjs +12 -12
- /package/src/StateChange/{ExternalAddCategorySlice_Behavior.res → ExternalAddCategorySlice_Decision.res} +0 -0
- /package/src/StateChange/{ExternalAddCategorySlice_Behavior.res.mjs → ExternalAddCategorySlice_Decision.res.mjs} +0 -0
- /package/src/StateChange/{WithFixtures_Behavior.res → WithFixtures_Decision.res} +0 -0
- /package/src/StateChange/{WithFixtures_Behavior.res.mjs → WithFixtures_Decision.res.mjs} +0 -0
- /package/src/StateChange/{WithManualOpen_Behavior.res → WithManualOpen_Decision.res} +0 -0
- /package/src/StateChange/{WithManualOpen_Behavior.res.mjs → WithManualOpen_Decision.res.mjs} +0 -0
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.249 (2026-10-21)
|
|
7
|
+
|
|
8
|
+
* feat!: the write-side logic module is called the decision, and a codemod moves an app over ([cb573ff](https://github.com/ReventlessDev/reventless-core/commit/cb573ff5b928c7d7f3e75988dfaa65066f8c95eb))
|
|
9
|
+
|
|
10
|
+
### BREAKING CHANGES
|
|
11
|
+
|
|
12
|
+
* <Spec>_Behavior.res, @@reventless.behavior,
|
|
13
|
+
Reventless.Behavior.T, StateChangeSlice.Behavior, Behavior_GWT and
|
|
14
|
+
thenRefused no longer exist. Run `npx reventless-rename-to-decision` at an
|
|
15
|
+
app's root, then rebuild; comments are not rewritten. The PPX must be the
|
|
16
|
+
matching release.
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
6
20
|
# 1.0.0-alpha.248 (2026-10-19)
|
|
7
21
|
|
|
8
22
|
### Features
|
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ the `reventless-dev` CLI discovers and runs those tests across a workspace.
|
|
|
21
21
|
Every component kind has a functor that produces the same
|
|
22
22
|
`describe / test / given* / when* / then*` combinator surface:
|
|
23
23
|
|
|
24
|
-
- **`
|
|
24
|
+
- **`Decision_GWT`** — event-sourced decision tests for aggregates and
|
|
25
25
|
StateChangeSlices (`givenEvents → whenCmd → thenEmits`), including DCB
|
|
26
26
|
append-condition assertions.
|
|
27
27
|
- **`Projection_GWT`** / **`MultiSourceProjection_GWT`** — single-source
|
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.249",
|
|
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",
|
|
17
|
-
"@reventlessdev/rescript-effect": "1.0.0-alpha.
|
|
18
|
-
"@reventlessdev/
|
|
19
|
-
"@reventlessdev/
|
|
20
|
-
"@reventlessdev/
|
|
21
|
-
"@reventlessdev/
|
|
22
|
-
"@reventlessdev/reventless-spec": "3.0.0-alpha.
|
|
17
|
+
"@reventlessdev/rescript-effect": "1.0.0-alpha.1",
|
|
18
|
+
"@reventlessdev/rescript-jest": "1.0.0-alpha.13",
|
|
19
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.198",
|
|
20
|
+
"@reventlessdev/rescript-node": "2.0.0-alpha.23",
|
|
21
|
+
"@reventlessdev/reventless-core": "3.0.0-alpha.301",
|
|
22
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.170"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"rescript": "12.3.1",
|
package/src/.gwtignore
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Prunes this subtree from reventless-gwt discovery (test tree + component tree).
|
|
2
2
|
#
|
|
3
|
-
# These are the GWT DSL framework modules — `Automation_GWT`, `
|
|
3
|
+
# These are the GWT DSL framework modules — `Automation_GWT`, `Decision_GWT`,
|
|
4
4
|
# `Mapping_GWT`, etc. — i.e. the `*_GWT.Make` functors the test runner is built
|
|
5
5
|
# from. They are named with the same `_GWT` suffix that marks user test files, so
|
|
6
6
|
# discovery would otherwise list each one as a test, even though loading the
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
open ReventlessCore
|
|
2
2
|
|
|
3
|
-
// Plan 02 Phase 6b — unified
|
|
3
|
+
// Plan 02 Phase 6b — unified Decision GWT DSL covering both Aggregate and
|
|
4
4
|
// StateChangeSlice. Two entry points:
|
|
5
5
|
//
|
|
6
|
-
// * [Make(Spec,
|
|
7
|
-
// [consumedEvent]; [
|
|
6
|
+
// * [Make(Spec, Decision)] — StateChangeSlice form. [Spec] carries
|
|
7
|
+
// [consumedEvent]; [Decision.evolve]
|
|
8
8
|
// consumes it. DCB append-condition
|
|
9
9
|
// checks are wired in.
|
|
10
|
-
// * [MakeFromAggregate(Spec,
|
|
10
|
+
// * [MakeFromAggregate(Spec, Decision)] — Aggregate adapter. [Spec] is
|
|
11
11
|
// [Reventless.Aggregate.Spec],
|
|
12
|
-
// [
|
|
12
|
+
// [Decision] is [Reventless.Decision.T].
|
|
13
13
|
// No DCB semantics; the simpler
|
|
14
14
|
// [AggregateT] surface is returned.
|
|
15
15
|
//
|
|
@@ -19,7 +19,7 @@ open ReventlessCore
|
|
|
19
19
|
// optimistic-concurrency assertions.
|
|
20
20
|
//
|
|
21
21
|
// Aggregate users *cannot* use [Make] (their Spec doesn't carry [consumedEvent])
|
|
22
|
-
// and slice users *cannot* use [MakeFromAggregate] (their Spec/
|
|
22
|
+
// and slice users *cannot* use [MakeFromAggregate] (their Spec/Decision shape
|
|
23
23
|
// differs). Pick the matching entry point.
|
|
24
24
|
|
|
25
25
|
// ---------------------------------------------------------------------------
|
|
@@ -204,7 +204,7 @@ module Acting = (Spec: ActingSpec) => {
|
|
|
204
204
|
)
|
|
205
205
|
)
|
|
206
206
|
|
|
207
|
-
let
|
|
207
|
+
let thenForbidden = (~encEvents, ~decided: option<JSON.t>, events) =>
|
|
208
208
|
switch refusal.contents {
|
|
209
209
|
| Some(_) => Outcome.pass
|
|
210
210
|
| None =>
|
|
@@ -224,7 +224,7 @@ module Acting = (Spec: ActingSpec) => {
|
|
|
224
224
|
// Slice form — replaces the legacy `StateChangeSlice_GWT`.
|
|
225
225
|
// ---------------------------------------------------------------------------
|
|
226
226
|
|
|
227
|
-
module type
|
|
227
|
+
module type DecisionSpec = {
|
|
228
228
|
let name: string
|
|
229
229
|
|
|
230
230
|
@schema
|
|
@@ -243,8 +243,8 @@ module type BehaviorSpec = {
|
|
|
243
243
|
let authorizationOf: string => Reventless.Authorization.rule<role>
|
|
244
244
|
}
|
|
245
245
|
|
|
246
|
-
module type
|
|
247
|
-
module Spec:
|
|
246
|
+
module type Decision = {
|
|
247
|
+
module Spec: DecisionSpec
|
|
248
248
|
|
|
249
249
|
type state
|
|
250
250
|
let initialState: state
|
|
@@ -253,7 +253,7 @@ module type Behavior = {
|
|
|
253
253
|
}
|
|
254
254
|
|
|
255
255
|
module type T = {
|
|
256
|
-
module Spec:
|
|
256
|
+
module Spec: DecisionSpec
|
|
257
257
|
|
|
258
258
|
let describe: (string, unit => unit) => unit
|
|
259
259
|
let todo: string => unit
|
|
@@ -275,7 +275,7 @@ module type T = {
|
|
|
275
275
|
let thenError: (array<Spec.event>, Spec.error) => Outcome.outcome
|
|
276
276
|
// Refused before `decide`: by the command's rule, or because the caller does not
|
|
277
277
|
// own what it acts on.
|
|
278
|
-
let
|
|
278
|
+
let thenForbidden: array<Spec.event> => Outcome.outcome
|
|
279
279
|
|
|
280
280
|
// DCB optimistic-concurrency assertions.
|
|
281
281
|
let thenAppendsConditionedOn: (array<Spec.event>, Reventless.DcbTag.query) => Outcome.outcome
|
|
@@ -323,7 +323,7 @@ let encodeAppendCondition = (c: Reventless.DcbTag.appendCondition): JSON.t => {
|
|
|
323
323
|
}
|
|
324
324
|
|
|
325
325
|
// ---------------------------------------------------------------------------
|
|
326
|
-
// Shared assertion core for both
|
|
326
|
+
// Shared assertion core for both Decision GWT flavours.
|
|
327
327
|
//
|
|
328
328
|
// `Make` (slice/DCB) and `MakeFromAggregate` encode produced events / errors
|
|
329
329
|
// identically and compare them against the expectation the same way — only the
|
|
@@ -436,7 +436,7 @@ module AssertionCore = (Spec: CoreSpec) => {
|
|
|
436
436
|
}
|
|
437
437
|
}
|
|
438
438
|
|
|
439
|
-
module Make = (Spec:
|
|
439
|
+
module Make = (Spec: DecisionSpec, Decision: Decision with module Spec := Spec): (
|
|
440
440
|
T with module Spec = Spec
|
|
441
441
|
) => {
|
|
442
442
|
module Spec = Spec
|
|
@@ -448,7 +448,7 @@ module Make = (Spec: BehaviorSpec, Behavior: Behavior with module Spec := Spec):
|
|
|
448
448
|
ScenarioRecorder.around(~spec=Spec.name, name, body)
|
|
449
449
|
)
|
|
450
450
|
|
|
451
|
-
let currentState = consumed => consumed->Array.reduce(
|
|
451
|
+
let currentState = consumed => consumed->Array.reduce(Decision.initialState, Decision.evolve)
|
|
452
452
|
|
|
453
453
|
module Core = AssertionCore(Spec)
|
|
454
454
|
let errors = Core.errors
|
|
@@ -607,7 +607,7 @@ module Make = (Spec: BehaviorSpec, Behavior: Behavior with module Spec := Spec):
|
|
|
607
607
|
if Guard.refuses(history, command) {
|
|
608
608
|
[]
|
|
609
609
|
} else {
|
|
610
|
-
switch
|
|
610
|
+
switch Decision.decide(currentState(history), command) {
|
|
611
611
|
| Ok(events) => events
|
|
612
612
|
| Error(error) =>
|
|
613
613
|
errors := [error]
|
|
@@ -645,8 +645,8 @@ module Make = (Spec: BehaviorSpec, Behavior: Behavior with module Spec := Spec):
|
|
|
645
645
|
| None => Guard.unexpectedRefusal()
|
|
646
646
|
}
|
|
647
647
|
|
|
648
|
-
let
|
|
649
|
-
Guard.
|
|
648
|
+
let thenForbidden = events =>
|
|
649
|
+
Guard.thenForbidden(
|
|
650
650
|
~encEvents=Core.encEvents,
|
|
651
651
|
~decided=errors.contents->Array.get(0)->Option.map(Core.encError),
|
|
652
652
|
events,
|
|
@@ -751,7 +751,7 @@ module Make = (Spec: BehaviorSpec, Behavior: Behavior with module Spec := Spec):
|
|
|
751
751
|
// ---------------------------------------------------------------------------
|
|
752
752
|
|
|
753
753
|
// Minimal aggregate spec the DSL requires. Declared inline (rather than
|
|
754
|
-
// aliasing `Reventless.
|
|
754
|
+
// aliasing `Reventless.Decision.T`'s inner Spec) because that inner Spec
|
|
755
755
|
// only carries the three @schema types — no `name` — and ReScript's
|
|
756
756
|
// `with module Spec = X` can only equate the inner Spec to a concrete
|
|
757
757
|
// module, not constrain it to a module type that adds `name`.
|
|
@@ -799,10 +799,10 @@ module type AggregateT = {
|
|
|
799
799
|
let thenError: (array<Spec.event>, Spec.error) => Outcome.outcome
|
|
800
800
|
// Refused before `decide`: by the command's rule, or because the caller does not
|
|
801
801
|
// own what it acts on.
|
|
802
|
-
let
|
|
802
|
+
let thenForbidden: array<Spec.event> => Outcome.outcome
|
|
803
803
|
}
|
|
804
804
|
|
|
805
|
-
module MakeFromAggregate = (Spec: AggregateSpec,
|
|
805
|
+
module MakeFromAggregate = (Spec: AggregateSpec, Decision: Decision.T with module Spec = Spec): (
|
|
806
806
|
AggregateT with module Spec = Spec
|
|
807
807
|
) => {
|
|
808
808
|
module Spec = Spec
|
|
@@ -813,7 +813,7 @@ module MakeFromAggregate = (Spec: AggregateSpec, Behavior: Behavior.T with modul
|
|
|
813
813
|
ScenarioRecorder.around(~spec=Spec.name, name, body)
|
|
814
814
|
)
|
|
815
815
|
|
|
816
|
-
let currentState = events => events->Array.reduce(
|
|
816
|
+
let currentState = events => events->Array.reduce(Decision.initialState, Decision.evolve)
|
|
817
817
|
|
|
818
818
|
module Core = AssertionCore(Spec)
|
|
819
819
|
let errors = Core.errors
|
|
@@ -837,7 +837,7 @@ module MakeFromAggregate = (Spec: AggregateSpec, Behavior: Behavior.T with modul
|
|
|
837
837
|
if Guard.refuses(history, command) {
|
|
838
838
|
[]
|
|
839
839
|
} else {
|
|
840
|
-
switch
|
|
840
|
+
switch Decision.decide(currentState(history), command) {
|
|
841
841
|
| Ok(events) => events
|
|
842
842
|
| Error(error) =>
|
|
843
843
|
errors := [error]
|
|
@@ -906,8 +906,8 @@ module MakeFromAggregate = (Spec: AggregateSpec, Behavior: Behavior.T with modul
|
|
|
906
906
|
events->unlessRefused(Core.matchesError(_, expectedEvents, expectedError))
|
|
907
907
|
}
|
|
908
908
|
|
|
909
|
-
let
|
|
910
|
-
Guard.
|
|
909
|
+
let thenForbidden = events =>
|
|
910
|
+
Guard.thenForbidden(
|
|
911
911
|
~encEvents=Core.encEvents,
|
|
912
912
|
~decided=errors.contents->Array.get(0)->Option.map(Core.encError),
|
|
913
913
|
events,
|
|
@@ -156,7 +156,7 @@ function Acting(Spec) {
|
|
|
156
156
|
actual: encRefusal(r),
|
|
157
157
|
actualEvents: []
|
|
158
158
|
}));
|
|
159
|
-
let
|
|
159
|
+
let thenForbidden = (encEvents, decided, events) => {
|
|
160
160
|
let match = refusal.contents;
|
|
161
161
|
if (match !== undefined) {
|
|
162
162
|
return Outcome$ReventlessGwt.pass;
|
|
@@ -182,7 +182,7 @@ function Acting(Spec) {
|
|
|
182
182
|
refuses: refuses,
|
|
183
183
|
encRefusal: encRefusal,
|
|
184
184
|
unexpectedRefusal: unexpectedRefusal,
|
|
185
|
-
|
|
185
|
+
thenForbidden: thenForbidden
|
|
186
186
|
};
|
|
187
187
|
}
|
|
188
188
|
|
|
@@ -317,7 +317,7 @@ function AssertionCore(Spec) {
|
|
|
317
317
|
}
|
|
318
318
|
|
|
319
319
|
function Make(Spec) {
|
|
320
|
-
return
|
|
320
|
+
return Decision => {
|
|
321
321
|
let test = (name, body) => JestBind$ReventlessGwt.test(Spec.name, name, () => ScenarioRecorder$ReventlessGwt.around(Spec.name, name, body));
|
|
322
322
|
let errors = {
|
|
323
323
|
contents: []
|
|
@@ -527,7 +527,7 @@ function Make(Spec) {
|
|
|
527
527
|
if (refuses(history, cmd)) {
|
|
528
528
|
return [];
|
|
529
529
|
}
|
|
530
|
-
let events =
|
|
530
|
+
let events = Decision.decide(Stdlib_Array.reduce(history, Decision.initialState, Decision.evolve), cmd);
|
|
531
531
|
if (events.TAG === "Ok") {
|
|
532
532
|
return events._0;
|
|
533
533
|
}
|
|
@@ -549,7 +549,7 @@ function Make(Spec) {
|
|
|
549
549
|
return unexpectedRefusal();
|
|
550
550
|
}
|
|
551
551
|
};
|
|
552
|
-
let
|
|
552
|
+
let thenForbidden = events => {
|
|
553
553
|
let decided = Stdlib_Option.map(errors.contents[0], encError);
|
|
554
554
|
let match = refusal.contents;
|
|
555
555
|
if (match !== undefined) {
|
|
@@ -686,7 +686,7 @@ function Make(Spec) {
|
|
|
686
686
|
thenEventWithError: thenEventWithError,
|
|
687
687
|
thenEventsWithError: thenEventsWithError,
|
|
688
688
|
thenError: thenError,
|
|
689
|
-
|
|
689
|
+
thenForbidden: thenForbidden,
|
|
690
690
|
thenAppendsConditionedOn: thenAppendsConditionedOn,
|
|
691
691
|
thenAppendsConditionedOnExactly: thenAppendsConditionedOnExactly
|
|
692
692
|
};
|
|
@@ -694,7 +694,7 @@ function Make(Spec) {
|
|
|
694
694
|
}
|
|
695
695
|
|
|
696
696
|
function MakeFromAggregate(Spec) {
|
|
697
|
-
return
|
|
697
|
+
return Decision => {
|
|
698
698
|
let test = (name, body) => JestBind$ReventlessGwt.test(Spec.name, name, () => ScenarioRecorder$ReventlessGwt.around(Spec.name, name, body));
|
|
699
699
|
let errors = {
|
|
700
700
|
contents: []
|
|
@@ -837,7 +837,7 @@ function MakeFromAggregate(Spec) {
|
|
|
837
837
|
if (refuses(history, cmd)) {
|
|
838
838
|
return [];
|
|
839
839
|
}
|
|
840
|
-
let events =
|
|
840
|
+
let events = Decision.decide(Stdlib_Array.reduce(history, Decision.initialState, Decision.evolve), cmd);
|
|
841
841
|
if (events.TAG === "Ok") {
|
|
842
842
|
return events._0;
|
|
843
843
|
}
|
|
@@ -911,7 +911,7 @@ function MakeFromAggregate(Spec) {
|
|
|
911
911
|
expects(expectedEvents, Primitive_option.some(expectedError));
|
|
912
912
|
return unlessRefused(events, __x => matchesError(__x, expectedEvents, expectedError));
|
|
913
913
|
};
|
|
914
|
-
let
|
|
914
|
+
let thenForbidden = events => {
|
|
915
915
|
let decided = Stdlib_Option.map(errors.contents[0], encError);
|
|
916
916
|
let match = refusal.contents;
|
|
917
917
|
if (match !== undefined) {
|
|
@@ -944,7 +944,7 @@ function MakeFromAggregate(Spec) {
|
|
|
944
944
|
thenCompareEvents: thenCompareEvents,
|
|
945
945
|
thenEventsWithError: thenEventsWithError,
|
|
946
946
|
thenError: thenError,
|
|
947
|
-
|
|
947
|
+
thenForbidden: thenForbidden
|
|
948
948
|
};
|
|
949
949
|
};
|
|
950
950
|
}
|
package/src/Delegate_GWT.res
CHANGED
|
@@ -3,7 +3,7 @@ open ReventlessCore
|
|
|
3
3
|
// Delegate_GWT — GWT for ExtensionPoint mappings and Extension delegates.
|
|
4
4
|
//
|
|
5
5
|
// Both are pure input → published-actions translations and form the one
|
|
6
|
-
// cross-plugin
|
|
6
|
+
// cross-plugin behavioral seam (per `.claude/rules/app-developer.md`). Each
|
|
7
7
|
// mapping has TWO directions, and each adapter drives both:
|
|
8
8
|
//
|
|
9
9
|
// * an ExtensionPoint mapping turns an internal `Delegate` event into public
|
package/src/EventMapping_GWT.res
CHANGED
|
@@ -2,20 +2,20 @@ open ReventlessCore
|
|
|
2
2
|
|
|
3
3
|
// Stage 5 — `EventMapping_GWT` is now a thin alias over `Mapping_GWT`
|
|
4
4
|
// specialised for the Aggregate → Aggregate case. New tests should use
|
|
5
|
-
// `Mapping_GWT.Make` directly with `Mapping_GWT.
|
|
5
|
+
// `Mapping_GWT.Make` directly with `Mapping_GWT.FromDecision` adapters; this
|
|
6
6
|
// wrapper is retained for migration backward-compat with consumers that were
|
|
7
|
-
// already calling `EventMapping_GWT.Make(Source,
|
|
8
|
-
//
|
|
7
|
+
// already calling `EventMapping_GWT.Make(Source, SourceDecision, Target,
|
|
8
|
+
// TargetDecision, EventMapping)` before Stage 5 landed.
|
|
9
9
|
|
|
10
10
|
module Make = (
|
|
11
11
|
Source: Reventless.Aggregate.Spec,
|
|
12
|
-
|
|
12
|
+
SourceDecision: Decision.T with module Spec = Source,
|
|
13
13
|
Target: Reventless.Aggregate.Spec,
|
|
14
|
-
|
|
14
|
+
TargetDecision: Decision.T with module Spec = Target,
|
|
15
15
|
EventMapping: Reventless.EventMapping.T with module Source = Source and module Target = Target,
|
|
16
16
|
) => {
|
|
17
|
-
module AdaptedSource = Mapping_GWT.
|
|
18
|
-
module AdaptedTarget = Mapping_GWT.
|
|
17
|
+
module AdaptedSource = Mapping_GWT.FromDecision(Source, SourceDecision)
|
|
18
|
+
module AdaptedTarget = Mapping_GWT.FromDecision(Target, TargetDecision)
|
|
19
19
|
|
|
20
20
|
module BoundMapping = {
|
|
21
21
|
module Source = AdaptedSource
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
import * as Mapping_GWT$ReventlessGwt from "./Mapping_GWT.res.mjs";
|
|
4
4
|
|
|
5
5
|
function Make(Source) {
|
|
6
|
-
return
|
|
7
|
-
let AdaptedSource = Mapping_GWT$ReventlessGwt.
|
|
8
|
-
let AdaptedTarget = Mapping_GWT$ReventlessGwt.
|
|
6
|
+
return SourceDecision => (Target => (TargetDecision => (EventMapping => {
|
|
7
|
+
let AdaptedSource = Mapping_GWT$ReventlessGwt.FromDecision(Source)(SourceDecision);
|
|
8
|
+
let AdaptedTarget = Mapping_GWT$ReventlessGwt.FromDecision(Target)(TargetDecision);
|
|
9
9
|
let map = EventMapping.map;
|
|
10
10
|
let BoundMapping = {
|
|
11
11
|
Source: undefined,
|
package/src/Flow_GWT.res
CHANGED
|
@@ -13,15 +13,15 @@ open ReventlessCore
|
|
|
13
13
|
// each step folds the slice's own `decide`/`evolve`/`collect`/`project` over
|
|
14
14
|
// the shared log. The log is JSON-erased (each step (de)serialises through its
|
|
15
15
|
// own spec's schema, exactly as the runtime routes a DCB event log), and each
|
|
16
|
-
// command step
|
|
16
|
+
// command step honors DCB tag filtering so downstream deciders never see
|
|
17
17
|
// phantom cross-entity events.
|
|
18
18
|
//
|
|
19
19
|
// The chain is built from per-step functors (one per slice), threaded with
|
|
20
20
|
// pipe-first so it reads top-to-bottom:
|
|
21
21
|
//
|
|
22
|
-
// module Place = Flow_GWT.CommandStep(PlaceOrder,
|
|
22
|
+
// module Place = Flow_GWT.CommandStep(PlaceOrder, PlaceOrder_Decision)
|
|
23
23
|
// module Auto = Flow_GWT.AutomationSlice(AutoShipOrder, AutoShipOrder_Automation)
|
|
24
|
-
// module Ship = Flow_GWT.CommandStep(ShipOrder,
|
|
24
|
+
// module Ship = Flow_GWT.CommandStep(ShipOrder, ShipOrder_Decision)
|
|
25
25
|
// module View = Flow_GWT.ViewStep(Orders, Orders_Projection)
|
|
26
26
|
//
|
|
27
27
|
// start
|
|
@@ -279,8 +279,8 @@ let thenBoundaryScopeResolves = async (
|
|
|
279
279
|
// -- CommandStep: a StateChangeSlice / Aggregate decider ---------------------
|
|
280
280
|
|
|
281
281
|
module CommandStep = (
|
|
282
|
-
Spec:
|
|
283
|
-
|
|
282
|
+
Spec: Decision_GWT.DecisionSpec,
|
|
283
|
+
Decision: Decision_GWT.Decision with module Spec := Spec,
|
|
284
284
|
) => {
|
|
285
285
|
let consumedDecoder = Reventless.DcbDecode.makeDecoder(Spec.consumedEventSchema)
|
|
286
286
|
let consumedEventTypes = consumedDecoder.eventTypes
|
|
@@ -335,8 +335,8 @@ module CommandStep = (
|
|
|
335
335
|
~payloadTagKeys,
|
|
336
336
|
)
|
|
337
337
|
let history = decodeMatching(s.log, consumedDecoder, query)
|
|
338
|
-
let state = history->Array.reduce(
|
|
339
|
-
switch
|
|
338
|
+
let state = history->Array.reduce(Decision.initialState, Decision.evolve)
|
|
339
|
+
switch Decision.decide(state, command) {
|
|
340
340
|
| Ok(events) => {
|
|
341
341
|
...s,
|
|
342
342
|
log: s.log->appendEvents(events, Spec.eventSchema),
|
|
@@ -390,8 +390,8 @@ module CommandStep = (
|
|
|
390
390
|
|
|
391
391
|
// -- AggregateCommandStep: an Aggregate decider partitioned by aggregate-ID --
|
|
392
392
|
//
|
|
393
|
-
// Mirror of `CommandStep` for `Reventless.
|
|
394
|
-
//
|
|
393
|
+
// Mirror of `CommandStep` for `Reventless.Decision.T` (the aggregate-flavor
|
|
394
|
+
// decision). Differences from the slice form:
|
|
395
395
|
// * No `consumedEvent` distinction — aggregates fold their own `event`
|
|
396
396
|
// stream; the decoder uses `Spec.eventSchema`.
|
|
397
397
|
// * No DCB tag inference — `~id` partitions the history. `givenEvents` and
|
|
@@ -404,8 +404,8 @@ module CommandStep = (
|
|
|
404
404
|
// histories isolated (a `Place` on `o2` does not see the events of `o1`).
|
|
405
405
|
|
|
406
406
|
module AggregateCommandStep = (
|
|
407
|
-
Spec:
|
|
408
|
-
|
|
407
|
+
Spec: Decision_GWT.AggregateSpec,
|
|
408
|
+
Decision: Reventless.Decision.T with module Spec = Spec,
|
|
409
409
|
) => {
|
|
410
410
|
let aggregateDecoder = Reventless.DcbDecode.makeDecoder(Spec.eventSchema)
|
|
411
411
|
|
|
@@ -422,8 +422,8 @@ module AggregateCommandStep = (
|
|
|
422
422
|
let whenCommand = async (flowP: flow, ~id: string, command: Spec.command) => {
|
|
423
423
|
let s = await flowP
|
|
424
424
|
let history = decodeAggregateMatching(s.log, ~id, aggregateDecoder)
|
|
425
|
-
let state = history->Array.reduce(
|
|
426
|
-
switch
|
|
425
|
+
let state = history->Array.reduce(Decision.initialState, Decision.evolve)
|
|
426
|
+
switch Decision.decide(state, command) {
|
|
427
427
|
| Ok(events) => {
|
|
428
428
|
...s,
|
|
429
429
|
log: s.log->appendAggregateEvents(~id, events, Spec.eventSchema),
|
|
@@ -717,7 +717,7 @@ module ExtensionPointStep = (M: EPMapping.Mapping) => {
|
|
|
717
717
|
// Aggregate-style EP mappings address the source aggregate's identity via
|
|
718
718
|
// the first arg (e.g. catalog Product → ProductBecameAvailable uses it as
|
|
719
719
|
// `productId`). DCB upstreams don't set `lastAggregateId` and fall back to
|
|
720
|
-
// the synthetic `gwt-id` so existing DCB Flow tests keep their
|
|
720
|
+
// the synthetic `gwt-id` so existing DCB Flow tests keep their behavior.
|
|
721
721
|
let runMapping = async (sourceId: string, events: array<M.Delegate.event>): array<
|
|
722
722
|
M.ExtensionPoint.event,
|
|
723
723
|
> =>
|
package/src/Flow_GWT.res.mjs
CHANGED
|
@@ -171,7 +171,7 @@ async function thenBoundaryScopeResolves(flowP, name, slices) {
|
|
|
171
171
|
}
|
|
172
172
|
|
|
173
173
|
function CommandStep(Spec) {
|
|
174
|
-
return
|
|
174
|
+
return Decision => {
|
|
175
175
|
let consumedDecoder = DcbDecode$Reventless.makeDecoder(Spec.consumedEventSchema);
|
|
176
176
|
let consumedEventTypes = consumedDecoder.eventTypes;
|
|
177
177
|
let givenEvents = async (flowP, events) => {
|
|
@@ -207,8 +207,8 @@ function CommandStep(Spec) {
|
|
|
207
207
|
let s = await flowP;
|
|
208
208
|
let query = DcbTag$Reventless.buildQueryFromCommand(consumedEventTypes, Spec.commandSchema, command, tagKeysByEventType, crossPartitionTagKeys, payloadTagKeys);
|
|
209
209
|
let history = decodeMatching(s.log, consumedDecoder, query);
|
|
210
|
-
let state = Stdlib_Array.reduce(history,
|
|
211
|
-
let events =
|
|
210
|
+
let state = Stdlib_Array.reduce(history, Decision.initialState, Decision.evolve);
|
|
211
|
+
let events = Decision.decide(state, command);
|
|
212
212
|
if (events.TAG !== "Ok") {
|
|
213
213
|
return {
|
|
214
214
|
log: s.log,
|
|
@@ -288,7 +288,7 @@ function CommandStep(Spec) {
|
|
|
288
288
|
}
|
|
289
289
|
|
|
290
290
|
function AggregateCommandStep(Spec) {
|
|
291
|
-
return
|
|
291
|
+
return Decision => {
|
|
292
292
|
let aggregateDecoder = DcbDecode$Reventless.makeDecoder(Spec.eventSchema);
|
|
293
293
|
let givenEvents = async (flowP, id, events) => {
|
|
294
294
|
let s = await flowP;
|
|
@@ -306,8 +306,8 @@ function AggregateCommandStep(Spec) {
|
|
|
306
306
|
let whenCommand = async (flowP, id, command) => {
|
|
307
307
|
let s = await flowP;
|
|
308
308
|
let history = decodeAggregateMatching(s.log, id, aggregateDecoder);
|
|
309
|
-
let state = Stdlib_Array.reduce(history,
|
|
310
|
-
let events =
|
|
309
|
+
let state = Stdlib_Array.reduce(history, Decision.initialState, Decision.evolve);
|
|
310
|
+
let events = Decision.decide(state, command);
|
|
311
311
|
if (events.TAG !== "Ok") {
|
|
312
312
|
return {
|
|
313
313
|
log: s.log,
|
package/src/Hint.res
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// See `docs/analysis/given-when-then-specifications.md` §3.2 (locus table)
|
|
6
6
|
// and §3.3 (JSON `hint` field).
|
|
7
7
|
//
|
|
8
|
-
// `locus` is a dotted pointer like "
|
|
8
|
+
// `locus` is a dotted pointer like "CategoryDecision.decide" filled in by
|
|
9
9
|
// the DSL at `then*` call time when the slice module name is in scope, else a
|
|
10
10
|
// generic pointer. A `StateMismatch` names its fold, so a view's points at
|
|
11
11
|
// `project` rather than `evolve`.
|
package/src/Mapping_GWT.res
CHANGED
|
@@ -3,7 +3,7 @@ open ReventlessCore
|
|
|
3
3
|
// Stage 5 — cross-pattern mapping GWT.
|
|
4
4
|
//
|
|
5
5
|
// Generalises `EventMapping_GWT` so both the source and target of a mapping
|
|
6
|
-
// can be an Aggregate (
|
|
6
|
+
// can be an Aggregate (Decision) or a StateChangeSlice. Covers all four
|
|
7
7
|
// producer/consumer combinations: Aggr→Aggr, Aggr→DCB, DCB→Aggr, DCB→DCB.
|
|
8
8
|
// See `docs/plans/done/reventless-gwt.md` Stage 5 for the migration plan.
|
|
9
9
|
//
|
|
@@ -54,20 +54,20 @@ module type Mapping = {
|
|
|
54
54
|
|
|
55
55
|
// -- Adapter functors --------------------------------------------------------
|
|
56
56
|
|
|
57
|
-
// Adapter: any `Aggregate.Spec` + matching `
|
|
57
|
+
// Adapter: any `Aggregate.Spec` + matching `Decision.T` becomes a `GwtSource`.
|
|
58
58
|
// Aggregates evolve their own events, so `consumedEvent = event`.
|
|
59
|
-
module
|
|
59
|
+
module FromDecision = (
|
|
60
60
|
Spec: Reventless.Aggregate.Spec,
|
|
61
|
-
|
|
61
|
+
Decision: Decision.T with module Spec = Spec,
|
|
62
62
|
) => {
|
|
63
63
|
let name = Spec.name
|
|
64
64
|
module Id = Spec.Id
|
|
65
|
-
type state =
|
|
66
|
-
let initialState =
|
|
65
|
+
type state = Decision.state
|
|
66
|
+
let initialState = Decision.initialState
|
|
67
67
|
|
|
68
68
|
type consumedEvent = Spec.event
|
|
69
69
|
let consumedEventSchema = Spec.eventSchema
|
|
70
|
-
let evolve =
|
|
70
|
+
let evolve = Decision.evolve
|
|
71
71
|
|
|
72
72
|
type command = Spec.command
|
|
73
73
|
let commandSchema = Spec.commandSchema
|
|
@@ -78,7 +78,7 @@ module FromBehavior = (
|
|
|
78
78
|
type error = Spec.error
|
|
79
79
|
let errorSchema = Spec.errorSchema
|
|
80
80
|
|
|
81
|
-
let decide =
|
|
81
|
+
let decide = Decision.decide
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
// Adapter: a StateChangeSlice spec becomes a `GwtSource`/`GwtTarget`. DCB
|
package/src/Mapping_GWT.res.mjs
CHANGED
|
@@ -9,17 +9,17 @@ import * as Outcome$ReventlessGwt from "./Outcome.res.mjs";
|
|
|
9
9
|
import * as JestBind$ReventlessGwt from "./JestBind.res.mjs";
|
|
10
10
|
import * as Message$ReventlessCore from "@reventlessdev/reventless-core/src/Message.res.mjs";
|
|
11
11
|
|
|
12
|
-
function
|
|
13
|
-
return
|
|
12
|
+
function FromDecision(Spec) {
|
|
13
|
+
return Decision => ({
|
|
14
14
|
name: Spec.name,
|
|
15
15
|
Id: Spec.Id,
|
|
16
|
-
initialState:
|
|
16
|
+
initialState: Decision.initialState,
|
|
17
17
|
consumedEventSchema: Spec.eventSchema,
|
|
18
|
-
evolve:
|
|
18
|
+
evolve: Decision.evolve,
|
|
19
19
|
commandSchema: Spec.commandSchema,
|
|
20
20
|
eventSchema: Spec.eventSchema,
|
|
21
21
|
errorSchema: Spec.errorSchema,
|
|
22
|
-
decide:
|
|
22
|
+
decide: Decision.decide
|
|
23
23
|
});
|
|
24
24
|
}
|
|
25
25
|
|
|
@@ -313,7 +313,7 @@ function Make(M) {
|
|
|
313
313
|
}
|
|
314
314
|
|
|
315
315
|
export {
|
|
316
|
-
|
|
316
|
+
FromDecision,
|
|
317
317
|
FromStateChangeSlice,
|
|
318
318
|
Make,
|
|
319
319
|
}
|
package/src/Outcome.res
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// shape and §3.3 for the JSON rendering. Stage 7 adds sury-aware rendering and
|
|
9
9
|
// structural `fieldDiff`. Stage 4 added `AppendConditionMismatch`.
|
|
10
10
|
|
|
11
|
-
// The function whose fold produced a state: a
|
|
11
|
+
// The function whose fold produced a state: a decision's `evolve`, or a view's
|
|
12
12
|
// `project`. Absent means `Evolve`, so a harness that does not say keeps its hint.
|
|
13
13
|
type fold = Evolve | Project
|
|
14
14
|
|
package/src/Query_GWT.res
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
// `subIdConfig`), so both kinds of consumers use the same combinator set.
|
|
13
13
|
// Adapter functors `FromReadModel` and `FromStateViewSlice` lift their
|
|
14
14
|
// respective specs into the shared shape — mirroring how `Mapping_GWT` lifts
|
|
15
|
-
// `Aggregate.Spec`/`StateChangeSlice.Spec` via `
|
|
15
|
+
// `Aggregate.Spec`/`StateChangeSlice.Spec` via `FromDecision`/`FromStateChangeSlice`.
|
|
16
16
|
|
|
17
17
|
// -- Unified spec shape ------------------------------------------------------
|
|
18
18
|
|
package/src/RunnerHook.res
CHANGED
|
@@ -30,7 +30,7 @@ type sink = {
|
|
|
30
30
|
) => unit,
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
// The currently-registered sink, if any. `None` → plain Jest
|
|
33
|
+
// The currently-registered sink, if any. `None` → plain Jest behavior.
|
|
34
34
|
let current: ref<option<sink>> = ref(None)
|
|
35
35
|
|
|
36
36
|
let register = (s: sink) => current := Some(s)
|
package/src/ScenarioRecorder.res
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Decision scenarios as they run, written as sidecars `check-lifecycle` reads.
|
|
2
2
|
//
|
|
3
3
|
// The PPX writes a `.gwt.json` sidecar from a `_GWT.res` file's source, so it can only name what
|
|
4
4
|
// the source spells out: a literal constructor in a step. A suite written against a binding (a
|
|
@@ -103,7 +103,7 @@ let write = (~path: string, ~file: string, ~spec: string, scenarios: array<scena
|
|
|
103
103
|
// What `check-lifecycle` takes the file's component from.
|
|
104
104
|
("stem", JSON.String(spec)),
|
|
105
105
|
("file", JSON.String(file)),
|
|
106
|
-
("componentKind", JSON.String("
|
|
106
|
+
("componentKind", JSON.String("Decision")),
|
|
107
107
|
("recorded", JSON.Boolean(true)),
|
|
108
108
|
(
|
|
109
109
|
"scenarios",
|