@reventlessdev/reventless-core 3.0.0-alpha.299 → 3.0.0-alpha.301
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/README.md +3 -3
- package/package.json +10 -10
- package/src/{Behavior.res → Decision.res} +1 -1
- package/src/Message.res +1 -1
- package/src/ResourceAttribution.res +8 -8
- package/src/adapter/Auth/Auth_ActiveRole.res +1 -1
- package/src/adapter/Auth/Auth_RefusalVocabulary.res +1 -1
- package/src/adapter/EventLogProvisioning/EventLogProvisioning.res +1 -1
- package/src/adapter/RuntimeExtension/RuntimeExtension.res +3 -3
- package/src/admin/Platform_BakedManifest.res +1 -1
- package/src/admin/Platform_PluginStructuresApi.res +1 -1
- package/src/admin/UiFragmentRegistry/StateChange/{UiFragmentRegistry_Behavior.res → UiFragmentRegistry_Decision.res} +1 -1
- package/src/admin/UiFragmentRegistry/StateChange/{UiFragmentRegistry_Behavior.res.mjs → UiFragmentRegistry_Decision.res.mjs} +1 -1
- package/src/components/Aggregate/Aggregate_Builder.res +2 -2
- package/src/components/Aggregate/Aggregate_Builder.res.mjs +2 -2
- package/src/components/Aggregate/Aggregate_Callback.res +17 -17
- package/src/components/Aggregate/Aggregate_Callback.res.mjs +11 -11
- package/src/components/Api/ApiNoApiHelpers.res +1 -1
- package/src/components/Api/GraphQL_FragmentGenerator.res +1 -1
- package/src/components/Api/SchemaType.res +1 -1
- package/src/components/CommandTopic/CommandTopic_Helpers.res +4 -4
- package/src/components/EventLog/EventLog_Operations.res +2 -2
- package/src/components/StateChangeSlice/StateChangeSlice.res +1 -1
- package/src/components/StateChangeSlice/StateChangeSlice_Builder.res +3 -3
- package/src/components/StateChangeSlice/StateChangeSlice_Builder.res.mjs +3 -3
- package/src/components/StateChangeSlice/StateChangeSlice_Callback.res +9 -9
- package/src/components/StateChangeSlice/StateChangeSlice_Callback.res.mjs +4 -4
- package/src/plugin/component/Plugin_Structure.res +1 -1
- package/src/plugin/lifecycle/{PluginBehavior.res → PluginDecision.res} +4 -4
- package/src/plugin/lifecycle/{PluginBehavior.res.mjs → PluginDecision.res.mjs} +1 -1
- package/tests/aggregate/AggregateCacheTest.res +4 -4
- package/tests/aggregate/AggregateCacheTest.res.mjs +2 -2
- package/tests/aggregate/AggregateCallbackTest.res +2 -2
- package/tests/aggregate/AggregateCallbackTest.res.mjs +1 -1
- package/tests/aggregate/AggregateConflictTest.res +4 -4
- package/tests/aggregate/AggregateConflictTest.res.mjs +2 -2
- package/tests/aggregate/AggregateFixtures.res +5 -5
- package/tests/aggregate/AggregateFixtures.res.mjs +2 -2
- package/tests/aggregate/AggregateRejectionTest.res +1 -1
- package/tests/aggregate/AggregateSnapshotTest.res +7 -7
- package/tests/aggregate/AggregateSnapshotTest.res.mjs +4 -4
- package/tests/api/PsInternalFieldsReadModel.res +1 -1
- package/tests/api/TaggedUnionSchemaTest.res +1 -1
- package/tests/commandgenerator/CommandGeneratorCallbackTest.res +1 -1
- package/tests/commandgenerator/CommandGeneratorFixtures.res +2 -2
- package/tests/commandgenerator/CommandGeneratorFixtures.res.mjs +2 -2
- package/tests/commandgenerator/OwnerActingTest.res +4 -4
- package/tests/commandgenerator/OwnerActingTest.res.mjs +4 -4
- package/tests/commandgenerator/OwnerStampingTest.res +1 -1
- package/tests/commandtopic/CommandOutcomeHookTest.res +2 -2
- package/tests/commandtopic/CommandOutcomeHookTest.res.mjs +3 -3
- package/tests/dcb/DcbStateChangeSliceTest.res +5 -5
- package/tests/dcb/DcbStateChangeSliceTest.res.mjs +5 -5
- package/tests/plugin/PluginStructureAccessTest.res +1 -1
- package/tests/plugin/PluginStructureAccessTest.res.mjs +2 -2
- package/tests/plugin/PluginStructureTest.res +15 -15
- package/tests/plugin/PluginStructureTest.res.mjs +20 -20
- package/tests/projection/LifecycleTrailTest.res +1 -1
- /package/src/{Behavior.res.mjs → Decision.res.mjs} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
module type T = {
|
|
2
2
|
module Spec: Reventless.StateChangeSlice.Spec
|
|
3
|
-
module
|
|
3
|
+
module Decision: Reventless.StateChangeSlice.Decision with module Spec := Spec
|
|
4
4
|
let handleCommands: (
|
|
5
5
|
~tagKeysByEventType: Dict.t<array<string>>=?,
|
|
6
6
|
~crossPartitionTagKeys: array<string>=?,
|
|
@@ -21,10 +21,10 @@ module type T = {
|
|
|
21
21
|
|
|
22
22
|
module Make = (
|
|
23
23
|
Spec: Reventless.StateChangeSlice.Spec,
|
|
24
|
-
|
|
25
|
-
): (T with module Spec = Spec and module
|
|
24
|
+
Decision: Reventless.StateChangeSlice.Decision with module Spec := Spec,
|
|
25
|
+
): (T with module Spec = Spec and module Decision := Decision) => {
|
|
26
26
|
module Spec = Spec
|
|
27
|
-
module
|
|
27
|
+
module Decision = Decision
|
|
28
28
|
|
|
29
29
|
let comp = `StateChangeSlice(${Spec.name})`
|
|
30
30
|
|
|
@@ -164,7 +164,7 @@ module Make = (
|
|
|
164
164
|
// sees the event that recorded them.
|
|
165
165
|
let projectionCache: Lru.t<
|
|
166
166
|
string,
|
|
167
|
-
(
|
|
167
|
+
(Decision.state, option<Reventless.DcbTag.sequencePosition>, array<string>),
|
|
168
168
|
> = Lru.make(~capacity=projectionCacheCapacity)
|
|
169
169
|
|
|
170
170
|
let resetCache = () => projectionCache->Lru.clear
|
|
@@ -174,7 +174,7 @@ module Make = (
|
|
|
174
174
|
// model — seeded from the projection cache when warm, so only events after
|
|
175
175
|
// the cached head are read (delta), else the full history (cold)
|
|
176
176
|
// 2. Decodes raw events using consumedEventSchema
|
|
177
|
-
// 3. Calls
|
|
177
|
+
// 3. Calls Decision.decide to produce new events
|
|
178
178
|
// 4. Encodes produced events to raw and appends with a condition
|
|
179
179
|
// 5. On conflict, retries from step 1 (up to maxRetries)
|
|
180
180
|
let handleSingleCommand = (
|
|
@@ -303,7 +303,7 @@ module Make = (
|
|
|
303
303
|
let rec attempt = (~retries) => {
|
|
304
304
|
let (baseState, afterPos, baseOwners) = switch seed.contents {
|
|
305
305
|
| Some((state, head, owners)) => (state, head, owners)
|
|
306
|
-
| None => (
|
|
306
|
+
| None => (Decision.initialState, None, [])
|
|
307
307
|
}
|
|
308
308
|
let cacheHit = seed.contents->Option.isSome
|
|
309
309
|
|
|
@@ -347,7 +347,7 @@ module Make = (
|
|
|
347
347
|
(dm, _pos, reads, owners),
|
|
348
348
|
(event, position, eventType, eventId, (ownerFields, data)),
|
|
349
349
|
) => (
|
|
350
|
-
|
|
350
|
+
Decision.evolve(dm, event),
|
|
351
351
|
Some(position),
|
|
352
352
|
reads->Array.concat([
|
|
353
353
|
switch eventId {
|
|
@@ -406,7 +406,7 @@ module Make = (
|
|
|
406
406
|
isDefect ? EffectLogger.logError(~comp, line) : EffectLogger.logWarn(~comp, line)
|
|
407
407
|
)->Effect.map(_ => Ok("rejected"))
|
|
408
408
|
| None =>
|
|
409
|
-
switch
|
|
409
|
+
switch Decision.decide(state, command'.command) {
|
|
410
410
|
| Ok(newEvents) if newEvents->Array.length == 0 =>
|
|
411
411
|
// No append, but the read snapshot is valid — cache it for the next command.
|
|
412
412
|
cachePut((state, headPosition, owners))
|
|
@@ -21,7 +21,7 @@ import * as EffectLogger$ReventlessCore from "../../util/EffectLogger.res.mjs";
|
|
|
21
21
|
import * as CommandTopic_Helpers$ReventlessCore from "../CommandTopic/CommandTopic_Helpers.res.mjs";
|
|
22
22
|
|
|
23
23
|
function Make(Spec) {
|
|
24
|
-
return
|
|
24
|
+
return Decision => {
|
|
25
25
|
let comp = `StateChangeSlice(` + Spec.name + `)`;
|
|
26
26
|
let decoder = DcbDecode$Reventless.makeDecoder(Spec.consumedEventSchema);
|
|
27
27
|
let queryEventTypes = decoder.eventTypes;
|
|
@@ -155,7 +155,7 @@ function Make(Spec) {
|
|
|
155
155
|
match[1],
|
|
156
156
|
match[2]
|
|
157
157
|
] : [
|
|
158
|
-
|
|
158
|
+
Decision.initialState,
|
|
159
159
|
undefined,
|
|
160
160
|
[]
|
|
161
161
|
];
|
|
@@ -202,7 +202,7 @@ function Make(Spec) {
|
|
|
202
202
|
let eventId = param$1[3];
|
|
203
203
|
let eventType = param$1[2];
|
|
204
204
|
return [
|
|
205
|
-
|
|
205
|
+
Decision.evolve(param[0], param$1[0]),
|
|
206
206
|
param$1[1],
|
|
207
207
|
param[2].concat([eventId !== undefined ? LogFormat$ReventlessCore.bold(eventType) + `(` + eventId + `)` : LogFormat$ReventlessCore.bold(eventType)]),
|
|
208
208
|
OwnerScope$Reventless.recordOwners(param[3], match[0], match[1])
|
|
@@ -237,7 +237,7 @@ function Make(Spec) {
|
|
|
237
237
|
_0: "rejected"
|
|
238
238
|
}));
|
|
239
239
|
}
|
|
240
|
-
let newEvents =
|
|
240
|
+
let newEvents = Decision.decide(state, command$p.command);
|
|
241
241
|
if (newEvents.TAG === "Ok") {
|
|
242
242
|
let newEvents$1 = newEvents._0;
|
|
243
243
|
if (newEvents$1.length === 0) {
|
|
@@ -74,7 +74,7 @@ type retiredCheck =
|
|
|
74
74
|
// says whether that name is a case of the field's enum). A name the enum does not
|
|
75
75
|
// declare is a failure — the predicate then matches no row and every row stays
|
|
76
76
|
// visible. A `value` on a field that is not the lifecycle is only a warning: a
|
|
77
|
-
//
|
|
77
|
+
// modeling judgement rather than a wrong name.
|
|
78
78
|
let checkRetiredValue = (~entityName: string, stateSchema: S.t<unknown>): retiredCheck =>
|
|
79
79
|
switch retiredFromStateSchema(stateSchema) {
|
|
80
80
|
// The boolean form names no state, so there is nothing to compare. Not a skip.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
@@reventless.
|
|
1
|
+
@@reventless.decision(PluginSpec)
|
|
2
2
|
|
|
3
3
|
open Reventless.Plugin
|
|
4
4
|
|
|
@@ -30,9 +30,9 @@ type state = {
|
|
|
30
30
|
|
|
31
31
|
let initialState = {current: None, known: Dict.make()}
|
|
32
32
|
|
|
33
|
-
// The @@reventless.
|
|
34
|
-
// this framework-internal
|
|
35
|
-
//
|
|
33
|
+
// The @@reventless.decision snapshot injection is gated to Aggregate/ folders;
|
|
34
|
+
// this framework-internal decision lives under plugin/lifecycle, so the
|
|
35
|
+
// Decision.T field is satisfied manually. The admin Plugin aggregate's history
|
|
36
36
|
// is short (lifecycle transitions), so persisted snapshots would buy nothing.
|
|
37
37
|
let snapshot = None
|
|
38
38
|
|
|
@@ -382,7 +382,7 @@ let snapshot;
|
|
|
382
382
|
|
|
383
383
|
let atomicCounter;
|
|
384
384
|
|
|
385
|
-
let moduleUrl = "@reventlessdev/reventless-core/src/plugin/lifecycle/
|
|
385
|
+
let moduleUrl = "@reventlessdev/reventless-core/src/plugin/lifecycle/PluginDecision.res.mjs";
|
|
386
386
|
|
|
387
387
|
export {
|
|
388
388
|
Spec,
|
|
@@ -33,14 +33,14 @@ module AggSpec = {
|
|
|
33
33
|
let moduleUrl: string = %raw(`import.meta.url`)
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
module
|
|
36
|
+
module TestDecision = {
|
|
37
37
|
module Spec = AggSpec
|
|
38
38
|
type state = {names: array<string>}
|
|
39
39
|
|
|
40
40
|
let initialState = {names: []}
|
|
41
41
|
|
|
42
|
-
// Hand-written
|
|
43
|
-
//
|
|
42
|
+
// Hand-written decision (no @@reventless.decision PPX) — satisfy the
|
|
43
|
+
// Decision.T snapshot field manually.
|
|
44
44
|
let snapshot = None
|
|
45
45
|
|
|
46
46
|
let moduleUrl: string = %raw(`import.meta.url`)
|
|
@@ -176,7 +176,7 @@ module TestOps = {
|
|
|
176
176
|
}
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
-
module TestHandler = Aggregate_Callback.Make(AggSpec,
|
|
179
|
+
module TestHandler = Aggregate_Callback.Make(AggSpec, TestDecision, TestOps)
|
|
180
180
|
|
|
181
181
|
let makeTopicItem = (reference, command): CommandTopic.topicItem<
|
|
182
182
|
Message.command'<string, AggSpec.command>,
|
|
@@ -98,7 +98,7 @@ function decide(state, command) {
|
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
let
|
|
101
|
+
let TestDecision = {
|
|
102
102
|
Spec: undefined,
|
|
103
103
|
initialState: initialState,
|
|
104
104
|
snapshot: undefined,
|
|
@@ -430,7 +430,7 @@ let OuterEventLog;
|
|
|
430
430
|
|
|
431
431
|
export {
|
|
432
432
|
AggSpec,
|
|
433
|
-
|
|
433
|
+
TestDecision,
|
|
434
434
|
testMeta,
|
|
435
435
|
makeMockEL,
|
|
436
436
|
mock,
|
|
@@ -40,11 +40,11 @@ describe("Aggregate_Callback.handleCommands:", () => {
|
|
|
40
40
|
)
|
|
41
41
|
})
|
|
42
42
|
|
|
43
|
-
describe("
|
|
43
|
+
describe("decision returns no events", () => {
|
|
44
44
|
testPromise(
|
|
45
45
|
"Rename on new aggregate returns Ok with no event stored",
|
|
46
46
|
async () => {
|
|
47
|
-
// Rename on empty history:
|
|
47
|
+
// Rename on empty history: Decision.decide(NotCreated, Rename) returns Error(NotFound)
|
|
48
48
|
let results = await Stream.fromIterable([
|
|
49
49
|
makeTopicItem("ref-1", AggSpec.Rename({newName: "Gadget"})),
|
|
50
50
|
])
|
|
@@ -50,7 +50,7 @@ globalThis.describe("Aggregate_Callback.handleCommands:", () => {
|
|
|
50
50
|
]);
|
|
51
51
|
});
|
|
52
52
|
});
|
|
53
|
-
globalThis.describe("
|
|
53
|
+
globalThis.describe("decision returns no events", () => {
|
|
54
54
|
globalThis.test("Rename on new aggregate returns Ok with no event stored", async () => {
|
|
55
55
|
let results = await Effect.runPromise(AggregateFixtures$ReventlessCore.TestHandler.handleCommands(Stream.fromIterable([AggregateFixtures$ReventlessCore.makeTopicItem(undefined, "ref-1", {
|
|
56
56
|
TAG: "Rename",
|
|
@@ -19,14 +19,14 @@ module AggSpec = {
|
|
|
19
19
|
let moduleUrl: string = %raw(`import.meta.url`)
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
module
|
|
22
|
+
module TestDecision = {
|
|
23
23
|
module Spec = AggSpec
|
|
24
24
|
type state = {name: string}
|
|
25
25
|
|
|
26
26
|
let initialState = {name: ""}
|
|
27
27
|
|
|
28
|
-
// Hand-written
|
|
29
|
-
//
|
|
28
|
+
// Hand-written decision (no @@reventless.decision PPX) — satisfy the
|
|
29
|
+
// Decision.T snapshot field manually.
|
|
30
30
|
let snapshot = None
|
|
31
31
|
|
|
32
32
|
let moduleUrl: string = %raw(`import.meta.url`)
|
|
@@ -151,7 +151,7 @@ module TestOps = {
|
|
|
151
151
|
}
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
module TestHandler = Aggregate_Callback.Make(AggSpec,
|
|
154
|
+
module TestHandler = Aggregate_Callback.Make(AggSpec, TestDecision, TestOps)
|
|
155
155
|
|
|
156
156
|
let testMeta: Message.meta = {
|
|
157
157
|
service: "ConflictTestAggregate",
|
|
@@ -66,7 +66,7 @@ function decide(_state, command) {
|
|
|
66
66
|
};
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
let
|
|
69
|
+
let TestDecision = {
|
|
70
70
|
Spec: undefined,
|
|
71
71
|
initialState: initialState,
|
|
72
72
|
snapshot: undefined,
|
|
@@ -384,7 +384,7 @@ let OuterEventLog;
|
|
|
384
384
|
|
|
385
385
|
export {
|
|
386
386
|
AggSpec,
|
|
387
|
-
|
|
387
|
+
TestDecision,
|
|
388
388
|
makeMockEL,
|
|
389
389
|
mock,
|
|
390
390
|
OuterEventLog,
|
|
@@ -25,10 +25,10 @@ module AggSpec = {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
// ─────────────────────────────────────────────────────────────
|
|
28
|
-
//
|
|
28
|
+
// Decision
|
|
29
29
|
// ─────────────────────────────────────────────────────────────
|
|
30
30
|
|
|
31
|
-
module
|
|
31
|
+
module TestDecision = {
|
|
32
32
|
module Spec = AggSpec
|
|
33
33
|
|
|
34
34
|
@schema
|
|
@@ -36,8 +36,8 @@ module TestBehavior = {
|
|
|
36
36
|
|
|
37
37
|
let initialState = NotCreated
|
|
38
38
|
|
|
39
|
-
// Hand-written
|
|
40
|
-
//
|
|
39
|
+
// Hand-written decision (no @@reventless.decision PPX) — satisfy the
|
|
40
|
+
// Decision.T snapshot field manually.
|
|
41
41
|
let snapshot = None
|
|
42
42
|
|
|
43
43
|
let moduleUrl: string = %raw(`import.meta.url`)
|
|
@@ -167,7 +167,7 @@ module TestOps = {
|
|
|
167
167
|
// Callback handler under test
|
|
168
168
|
// ─────────────────────────────────────────────────────────────
|
|
169
169
|
|
|
170
|
-
module TestHandler = Aggregate_Callback.Make(AggSpec,
|
|
170
|
+
module TestHandler = Aggregate_Callback.Make(AggSpec, TestDecision, TestOps)
|
|
171
171
|
|
|
172
172
|
// ─────────────────────────────────────────────────────────────
|
|
173
173
|
// Test helpers
|
|
@@ -114,7 +114,7 @@ function decide(state, command) {
|
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
let
|
|
117
|
+
let TestDecision = {
|
|
118
118
|
Spec: undefined,
|
|
119
119
|
stateSchema: stateSchema,
|
|
120
120
|
initialState: "NotCreated",
|
|
@@ -306,7 +306,7 @@ let OuterEventLog;
|
|
|
306
306
|
|
|
307
307
|
export {
|
|
308
308
|
AggSpec,
|
|
309
|
-
|
|
309
|
+
TestDecision,
|
|
310
310
|
makeMockEL,
|
|
311
311
|
mock,
|
|
312
312
|
OuterEventLog,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Tests for the Aggregate decide-rejection propagation path.
|
|
2
|
-
// Verifies that domain rejections from
|
|
2
|
+
// Verifies that domain rejections from Decision.decide:
|
|
3
3
|
// 1. Surface to producers via CommandTopic_Helpers.rejectedResultChannel (with errorCode + detail)
|
|
4
4
|
// 2. Return Ok(reference) so async (SQS) consumers delete the message — domain rejections don't redeliver
|
|
5
5
|
// 3. Don't cancel surviving commands inside the same batch
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Step 5 of docs/plans/done/aggregate-snapshotting.md — the Aggregate_Callback
|
|
2
|
-
// snapshot wiring: a snapshot-enabled
|
|
2
|
+
// snapshot wiring: a snapshot-enabled decision seeds cold replays from the
|
|
3
3
|
// latest persisted snapshot (hash-gated) and writes a fresh one every
|
|
4
4
|
// `interval` events, fire-and-forget. Snapshots never affect correctness — the
|
|
5
5
|
// OCC append is the only consistency primitive — so a drifted/absent snapshot
|
|
@@ -28,7 +28,7 @@ module AggSpec = {
|
|
|
28
28
|
let moduleUrl: string = %raw(`import.meta.url`)
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
module
|
|
31
|
+
module TestDecision = {
|
|
32
32
|
module Spec = AggSpec
|
|
33
33
|
|
|
34
34
|
@schema
|
|
@@ -205,11 +205,11 @@ module MakeOps = (
|
|
|
205
205
|
module TestOps = MakeOps({
|
|
206
206
|
let name = "SnapshotMockEventLog"
|
|
207
207
|
})
|
|
208
|
-
module TestHandler = Aggregate_Callback.Make(AggSpec,
|
|
208
|
+
module TestHandler = Aggregate_Callback.Make(AggSpec, TestDecision, TestOps)
|
|
209
209
|
|
|
210
|
-
// A second
|
|
210
|
+
// A second decision with snapshots DISABLED, sharing the same mock, to prove
|
|
211
211
|
// disabled aggregates do zero snapshot traffic.
|
|
212
|
-
module
|
|
212
|
+
module PlainDecision = {
|
|
213
213
|
module Spec = AggSpec
|
|
214
214
|
|
|
215
215
|
@schema
|
|
@@ -233,7 +233,7 @@ module PlainBehavior = {
|
|
|
233
233
|
module PlainOps = MakeOps({
|
|
234
234
|
let name = "PlainMockEventLog"
|
|
235
235
|
})
|
|
236
|
-
module PlainHandler = Aggregate_Callback.Make(AggSpec,
|
|
236
|
+
module PlainHandler = Aggregate_Callback.Make(AggSpec, PlainDecision, PlainOps)
|
|
237
237
|
|
|
238
238
|
let makeItem = (~id="agg-1", reference, command): CommandTopic.topicItem<
|
|
239
239
|
Message.command'<string, AggSpec.command>,
|
|
@@ -318,7 +318,7 @@ describe("Aggregate_Callback — snapshots:", () => {
|
|
|
318
318
|
| Some(snap) =>
|
|
319
319
|
expect(snap.seqNr)->toBe(20)
|
|
320
320
|
// 20 items folded through the seeded state — no double-count, no gap.
|
|
321
|
-
let decoded = snap.state->Reventless.Util_Sury.fromJson(
|
|
321
|
+
let decoded = snap.state->Reventless.Util_Sury.fromJson(TestDecision.stateSchema)
|
|
322
322
|
expect(decoded.items->Array.length)->toBe(20)
|
|
323
323
|
| None => expect("expected snapshot at 20")->toBe("none")
|
|
324
324
|
}
|
|
@@ -88,7 +88,7 @@ function decide(_state, command) {
|
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
let
|
|
91
|
+
let TestDecision = {
|
|
92
92
|
Spec: undefined,
|
|
93
93
|
stateSchema: stateSchema,
|
|
94
94
|
initialState: initialState,
|
|
@@ -369,7 +369,7 @@ function decide$1(_state, command) {
|
|
|
369
369
|
}
|
|
370
370
|
}
|
|
371
371
|
|
|
372
|
-
let
|
|
372
|
+
let PlainDecision = {
|
|
373
373
|
Spec: undefined,
|
|
374
374
|
stateSchema: stateSchema$1,
|
|
375
375
|
initialState: initialState$1,
|
|
@@ -642,7 +642,7 @@ let OuterEventLog;
|
|
|
642
642
|
|
|
643
643
|
export {
|
|
644
644
|
AggSpec,
|
|
645
|
-
|
|
645
|
+
TestDecision,
|
|
646
646
|
testMeta,
|
|
647
647
|
makeMockEL,
|
|
648
648
|
mock,
|
|
@@ -650,7 +650,7 @@ export {
|
|
|
650
650
|
MakeOps,
|
|
651
651
|
TestOps,
|
|
652
652
|
TestHandler,
|
|
653
|
-
|
|
653
|
+
PlainDecision,
|
|
654
654
|
PlainOps,
|
|
655
655
|
PlainHandler,
|
|
656
656
|
makeItem,
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
//
|
|
5
5
|
// A spec file rather than a module inside the test, and named `*ReadModel*`
|
|
6
6
|
// deliberately: the annotation machinery runs under `@@reventless.spec` AND only
|
|
7
|
-
// for a file the PPX
|
|
7
|
+
// for a file the PPX recognizes as a read model or state view. That is also the
|
|
8
8
|
// honest scope for `@internal` — it describes a read model's state.
|
|
9
9
|
|
|
10
10
|
@@reventless.spec("InternalFieldsReadModel")
|
|
@@ -129,7 +129,7 @@ describe("GraphQL_FragmentGenerator emits a union", () => {
|
|
|
129
129
|
expect(row->String.includes("history: [Geolocation!]!"))->toBe(true)
|
|
130
130
|
})
|
|
131
131
|
|
|
132
|
-
// Unchanged
|
|
132
|
+
// Unchanged behavior, pinned: without a name there is no type to emit, and
|
|
133
133
|
// inventing one would publish a contract nothing can stamp a `__typename` for.
|
|
134
134
|
testSync("an unnamed union stays a String", () => {
|
|
135
135
|
let unnamed = fragmentFor(
|
|
@@ -207,7 +207,7 @@ describe("CommandGenerator_Callback.generateCommand:", () => {
|
|
|
207
207
|
},
|
|
208
208
|
)
|
|
209
209
|
|
|
210
|
-
// The control the drop is
|
|
210
|
+
// The control the drop is modeled on: absent and explicitly-null must
|
|
211
211
|
// reach the domain as the same command.
|
|
212
212
|
testPromise(
|
|
213
213
|
"and one simply omitted produce the same command",
|
|
@@ -26,10 +26,10 @@ module CmdGenAggSpec = {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
// ─────────────────────────────────────────────────────────────
|
|
29
|
-
//
|
|
29
|
+
// Decision
|
|
30
30
|
// ─────────────────────────────────────────────────────────────
|
|
31
31
|
|
|
32
|
-
module
|
|
32
|
+
module CmdGenDecision = {
|
|
33
33
|
module Spec = CmdGenAggSpec
|
|
34
34
|
type state = unit
|
|
35
35
|
|
|
@@ -80,7 +80,7 @@ function decide(_state, command) {
|
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
let
|
|
83
|
+
let CmdGenDecision = {
|
|
84
84
|
Spec: undefined,
|
|
85
85
|
moduleUrl: moduleUrl$1,
|
|
86
86
|
initialState: undefined,
|
|
@@ -212,7 +212,7 @@ function reset() {
|
|
|
212
212
|
|
|
213
213
|
export {
|
|
214
214
|
CmdGenAggSpec,
|
|
215
|
-
|
|
215
|
+
CmdGenDecision,
|
|
216
216
|
capturedCmds,
|
|
217
217
|
MockPublishSpec,
|
|
218
218
|
TestGenerator,
|
|
@@ -54,7 +54,7 @@ module OrderSlice = {
|
|
|
54
54
|
| OrderNotFound
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
module
|
|
57
|
+
module OrderDecision = {
|
|
58
58
|
type state = {placed: bool, cancelled: bool}
|
|
59
59
|
let initialState = {placed: false, cancelled: false}
|
|
60
60
|
let moduleUrl = OrderSlice.moduleUrl
|
|
@@ -83,7 +83,7 @@ module OrderBehavior = {
|
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
module OrderHandler = StateChangeSlice_Callback.Make(OrderSlice,
|
|
86
|
+
module OrderHandler = StateChangeSlice_Callback.Make(OrderSlice, OrderDecision)
|
|
87
87
|
|
|
88
88
|
let dcbMock = DcbFixtures.makeMockStorage()
|
|
89
89
|
|
|
@@ -128,7 +128,7 @@ module AccountSpec = {
|
|
|
128
128
|
let moduleUrl: string = %raw(`import.meta.url`)
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
-
module
|
|
131
|
+
module AccountDecision = {
|
|
132
132
|
module Spec = AccountSpec
|
|
133
133
|
|
|
134
134
|
@schema
|
|
@@ -210,7 +210,7 @@ module AccountOps = {
|
|
|
210
210
|
}
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
-
module AccountHandler = Aggregate_Callback.Make(AccountSpec,
|
|
213
|
+
module AccountHandler = Aggregate_Callback.Make(AccountSpec, AccountDecision, AccountOps)
|
|
214
214
|
|
|
215
215
|
// ─── Callers ────────────────────────────────────────────────────────────────
|
|
216
216
|
|
|
@@ -175,7 +175,7 @@ function decide(state, command) {
|
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
-
let
|
|
178
|
+
let OrderDecision = {
|
|
179
179
|
initialState: initialState,
|
|
180
180
|
moduleUrl: moduleUrl,
|
|
181
181
|
evolve: evolve,
|
|
@@ -352,7 +352,7 @@ function decide$1(state, command) {
|
|
|
352
352
|
}
|
|
353
353
|
}
|
|
354
354
|
|
|
355
|
-
let
|
|
355
|
+
let AccountDecision = {
|
|
356
356
|
Spec: undefined,
|
|
357
357
|
stateSchema: stateSchema,
|
|
358
358
|
initialState: "NotOpen",
|
|
@@ -978,7 +978,7 @@ let OuterEventLog;
|
|
|
978
978
|
|
|
979
979
|
export {
|
|
980
980
|
OrderSlice,
|
|
981
|
-
|
|
981
|
+
OrderDecision,
|
|
982
982
|
OrderHandler,
|
|
983
983
|
dcbMock,
|
|
984
984
|
DcbOps,
|
|
@@ -986,7 +986,7 @@ export {
|
|
|
986
986
|
dcbEventLog,
|
|
987
987
|
orderPartition,
|
|
988
988
|
AccountSpec,
|
|
989
|
-
|
|
989
|
+
AccountDecision,
|
|
990
990
|
accountEvents,
|
|
991
991
|
snapshotReads,
|
|
992
992
|
OuterEventLog,
|
|
@@ -224,7 +224,7 @@ describe("the schema a generator is built with must answer for its commands:", (
|
|
|
224
224
|
let _ = await permissive(
|
|
225
225
|
placeOrder(~customerId="cust-B", ~identity=cognito(~userId="cust-A", ~groups=["User"])),
|
|
226
226
|
)->Effect.runPromise
|
|
227
|
-
// Not the
|
|
227
|
+
// Not the behavior we want anywhere — recorded so that a change making the
|
|
228
228
|
// permissive path stamp correctly is a deliberate one, and so the contrast
|
|
229
229
|
// with the case below cannot be read as incidental.
|
|
230
230
|
expect(lastPublished()->fieldOf("customerId"))->toEqual(Some(str("cust-B")))
|
|
@@ -25,7 +25,7 @@ let dcbEventLog: DcbEventLog.operations = {
|
|
|
25
25
|
appendStream: EventLogOps.appendStream,
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
module
|
|
28
|
+
module SliceDecision = {
|
|
29
29
|
type state = DcbFixtures.TestCommandSpec.state
|
|
30
30
|
let initialState = DcbFixtures.TestCommandSpec.initialState
|
|
31
31
|
let evolve = DcbFixtures.TestCommandSpec.evolve
|
|
@@ -33,7 +33,7 @@ module SliceBehavior = {
|
|
|
33
33
|
let moduleUrl = DcbFixtures.TestCommandSpec.moduleUrl
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
module SliceHandler = StateChangeSlice_Callback.Make(DcbFixtures.TestCommandSpec,
|
|
36
|
+
module SliceHandler = StateChangeSlice_Callback.Make(DcbFixtures.TestCommandSpec, SliceDecision)
|
|
37
37
|
|
|
38
38
|
let sliceItem = (reference, command): CommandTopic.topicItem<
|
|
39
39
|
Message.command'<Reventless.Id.String.t, DcbFixtures.TestCommandSpec.command>,
|
|
@@ -49,7 +49,7 @@ let decide = DcbFixtures$ReventlessCore.TestCommandSpec.decide;
|
|
|
49
49
|
|
|
50
50
|
let moduleUrl = DcbFixtures$ReventlessCore.TestCommandSpec.moduleUrl;
|
|
51
51
|
|
|
52
|
-
let
|
|
52
|
+
let SliceDecision = {
|
|
53
53
|
initialState: initialState,
|
|
54
54
|
evolve: evolve,
|
|
55
55
|
decide: decide,
|
|
@@ -67,7 +67,7 @@ let SliceHandler = StateChangeSlice_Callback$ReventlessCore.Make({
|
|
|
67
67
|
commandTransition: DcbFixtures$ReventlessCore.TestCommandSpec.commandTransition,
|
|
68
68
|
traits: DcbFixtures$ReventlessCore.TestCommandSpec.traits,
|
|
69
69
|
readConsistency: DcbFixtures$ReventlessCore.TestCommandSpec.readConsistency
|
|
70
|
-
})(
|
|
70
|
+
})(SliceDecision);
|
|
71
71
|
|
|
72
72
|
function sliceItem(reference, command) {
|
|
73
73
|
return {
|
|
@@ -373,7 +373,7 @@ export {
|
|
|
373
373
|
TestDcbOps,
|
|
374
374
|
EventLogOps,
|
|
375
375
|
dcbEventLog,
|
|
376
|
-
|
|
376
|
+
SliceDecision,
|
|
377
377
|
SliceHandler,
|
|
378
378
|
sliceItem,
|
|
379
379
|
runSlice,
|
|
@@ -18,7 +18,7 @@ let testDcbEventLog: DcbEventLog.operations = {
|
|
|
18
18
|
appendStream: EventLogOps.appendStream,
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
module
|
|
21
|
+
module TestCommandDecision = {
|
|
22
22
|
type state = DcbFixtures.TestCommandSpec.state
|
|
23
23
|
let initialState = DcbFixtures.TestCommandSpec.initialState
|
|
24
24
|
let evolve = DcbFixtures.TestCommandSpec.evolve
|
|
@@ -27,10 +27,10 @@ module TestCommandBehavior = {
|
|
|
27
27
|
}
|
|
28
28
|
module TestHandler = StateChangeSlice_Callback.Make(
|
|
29
29
|
DcbFixtures.TestCommandSpec,
|
|
30
|
-
|
|
30
|
+
TestCommandDecision,
|
|
31
31
|
)
|
|
32
32
|
|
|
33
|
-
// Per-slice `readConsistency` overrides: same types/
|
|
33
|
+
// Per-slice `readConsistency` overrides: same types/decision as TestCommandSpec,
|
|
34
34
|
// only the build-time consistency mode differs. The default (TestCommandSpec) is
|
|
35
35
|
// `EscalateOnRetry`; these force strong / eventual on every attempt.
|
|
36
36
|
module StrongSpec = {
|
|
@@ -41,8 +41,8 @@ module EventualSpec = {
|
|
|
41
41
|
include DcbFixtures.TestCommandSpec
|
|
42
42
|
let readConsistency = Reventless.ReadConsistency.AlwaysEventual
|
|
43
43
|
}
|
|
44
|
-
module StrongHandler = StateChangeSlice_Callback.Make(StrongSpec,
|
|
45
|
-
module EventualHandler = StateChangeSlice_Callback.Make(EventualSpec,
|
|
44
|
+
module StrongHandler = StateChangeSlice_Callback.Make(StrongSpec, TestCommandDecision)
|
|
45
|
+
module EventualHandler = StateChangeSlice_Callback.Make(EventualSpec, TestCommandDecision)
|
|
46
46
|
|
|
47
47
|
let makeTopicItem = (reference, command): CommandTopic.topicItem<
|
|
48
48
|
Message.command'<Reventless.Id.String.t, DcbFixtures.TestCommandSpec.command>,
|