@reventlessdev/reventless-core 3.0.0-alpha.259 → 3.0.0-alpha.261
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 +15 -0
- package/package.json +11 -11
- package/src/Behavior.res +1 -4
- package/src/Message.res +17 -14
- package/src/Projection.res +51 -25
- package/src/RequestContext.res +8 -9
- package/src/ResourceAttribution.res +28 -14
- package/src/adapter/Adapter.res +48 -4
- package/src/adapter/EventLogProvisioning/EventLogProvisioning.res +5 -5
- package/src/adapter/Messaging/Messaging_Log_Backend.res +10 -7
- package/src/adapter/Messaging/Messaging_Log_Backend.res.mjs +2 -4
- package/src/adapter/Monitoring/Monitoring.res +16 -17
- package/src/adapter/QueryInterception/QueryInterception.res +0 -1
- package/src/adapter/Runtime/AggregateRuntime_Builder_Common.res +12 -5
- package/src/adapter/Runtime/EventCollectorRuntime_Builder_Single.res +11 -7
- package/src/adapter/Runtime/PluginRuntime_Builder_Micro.res +1 -7
- package/src/adapter/Runtime/PluginRuntime_Builder_Single.res +1 -7
- package/src/adapter/RuntimeExtension/RuntimeExtension.res +0 -1
- package/src/admin/Platform_Admin.res +17 -6
- package/src/admin/Platform_AdminApi.res +14 -9
- package/src/admin/Platform_Admin_Callback.res +1 -1
- package/src/admin/Platform_BakedManifest.res +34 -34
- package/src/admin/Platform_ComponentDefinitionsApi.res +9 -21
- package/src/admin/Platform_PluginStructuresApi.res +15 -12
- package/src/admin/UiFragmentRegistry/StateChange/UiFragmentRegistry.res +1 -1
- package/src/components/Aggregate/Aggregate.res +13 -12
- package/src/components/Aggregate/Aggregate_Builder.res +22 -21
- package/src/components/Aggregate/Aggregate_Callback.res +91 -86
- package/src/components/Api/ApiNoApiHelpers.res +1 -3
- package/src/components/Api/Api_Ids.res +0 -1
- package/src/components/Api/Api_Naming.res +41 -18
- package/src/components/Api/GraphQL_FragmentGenerator.res +42 -52
- package/src/components/Api/GraphQL_FragmentGenerator.res.mjs +11 -4
- package/src/components/Api/GraphQL_SchemaInspector.res +5 -3
- package/src/components/Api/GraphQL_Stitcher.res +9 -10
- package/src/components/Api/MCP_SchemaGenerator.res +8 -12
- package/src/components/Api/OwnerScopeDiagnostics.res +1 -2
- package/src/components/Api/QueryDbListQuery.res +13 -6
- package/src/components/Api/SchemaType.res +40 -20
- package/src/components/Api/SchemaType.res.mjs +16 -0
- package/src/components/Api/SuryToJsonSchema.res +18 -10
- package/src/components/Api/SuryToJsonSchema.res.mjs +135 -121
- package/src/components/AutomationSlice/AutomationSlice.res +4 -4
- package/src/components/AutomationSlice/AutomationSlice_Builder.res +15 -14
- package/src/components/AutomationSlice/AutomationSlice_Callback.res +3 -3
- package/src/components/Builder_Helpers.res +12 -23
- package/src/components/CommandGenerator/CommandGenerator.res +6 -1
- package/src/components/CommandGenerator/CommandGenerator_Builder.res +29 -21
- package/src/components/CommandGenerator/CommandGenerator_Callback.res +17 -21
- package/src/components/CommandTopic/CommandTopic.res +3 -3
- package/src/components/CommandTopic/CommandTopic_Builder.res +10 -10
- package/src/components/CommandTopic/CommandTopic_Callback.res +1 -4
- package/src/components/CommandTopic/CommandTopic_Helpers.res +1 -2
- package/src/components/Counter/Counter_Builder.res +1 -6
- package/src/components/Counter/Counter_Callback.res +3 -3
- package/src/components/Dcb/Dcb_Builder.res +970 -956
- package/src/components/DcbEventLog/DcbEventLog.res +4 -4
- package/src/components/DcbEventLog/DcbEventLog_Builder.res +0 -1
- package/src/components/DcbEventLog/DcbEventLog_Operations.res +17 -18
- package/src/components/EventCollector/EventCollector_Builder.res +2 -1
- package/src/components/EventLog/EventLog.res +5 -1
- package/src/components/EventLog/EventLog_Operations.res +12 -8
- package/src/components/EventMapper/EventMapper.res +8 -10
- package/src/components/EventMapper/EventMapper_Builder.res +4 -1
- package/src/components/EventMapper/EventMapper_Callback.res +46 -31
- package/src/components/EventTopic/EventTopic.res +3 -3
- package/src/components/EventTopic/EventTopic_Builder.res +1 -1
- package/src/components/Extension/Extension.res +4 -1
- package/src/components/Extension/Extension_Operations.res +11 -3
- package/src/components/ExtensionPoint/ExtensionPoint.res +3 -3
- package/src/components/ExtensionPoint/ExtensionPoint_Builder.res +9 -10
- package/src/components/ExtensionPoint/ExtensionPoint_Callback.res +2 -6
- package/src/components/ExtensionPoint/ExtensionPoint_Operations.res +13 -5
- package/src/components/Heartbeat/Heartbeat_Callback.res +3 -3
- package/src/components/InboundTranslationSlice/InboundTranslationSlice.res +4 -4
- package/src/components/InboundTranslationSlice/InboundTranslationSlice_Builder.res +7 -10
- package/src/components/OutboundTranslationSlice/OutboundTranslationSlice.res +4 -4
- package/src/components/OutboundTranslationSlice/OutboundTranslationSlice_Builder.res +15 -14
- package/src/components/QueryDb/QueryDb_Adapter.res +1 -3
- package/src/components/QueryDb/QueryDb_Operations.res +10 -10
- package/src/components/ReadModel/ReadModel.res +3 -3
- package/src/components/ReadModel/ReadModel_Builder.res +1 -2
- package/src/components/ReadModel/ReadModel_Callback.res +2 -1
- package/src/components/SideEffectHandler/SideEffectHandler_Callback.res +5 -2
- package/src/components/StateChangeSlice/StateChangeSlice.res +3 -3
- package/src/components/StateChangeSlice/StateChangeSlice_Builder.res +23 -4
- package/src/components/StateChangeSlice/StateChangeSlice_Callback.res +22 -6
- package/src/components/StateTopic.res +1 -3
- package/src/components/StateViewSlice/StateViewSlice.res +4 -4
- package/src/components/StateViewSlice/StateViewSlice_Builder.res +79 -68
- package/src/components/StateViewSlice/StateViewSlice_Callback.res +3 -1
- package/src/components/Task/Task.res +3 -3
- package/src/components/Task/Task_Adapter.res +4 -1
- package/src/components/Task/Task_Builder.res +24 -21
- package/src/plugin/api/PluginBaseFragment.res +9 -3
- package/src/plugin/component/Capability_Inference.res +8 -10
- package/src/plugin/component/Plugin.res +6 -6
- package/src/plugin/component/Plugin_Builder.res +54 -50
- package/src/plugin/component/Plugin_Callback.res +10 -6
- package/src/plugin/component/Plugin_Helpers.res +264 -230
- package/src/plugin/component/Plugin_Structure.res +370 -324
- package/src/plugin/component/Plugin_SubscriptionSchema.res +8 -6
- package/src/plugin/component/SchemaWalker.res +11 -12
- package/src/plugin/connect/PluginConnectExtension_Mapping.res +45 -50
- package/src/plugin/lifecycle/PluginBehavior.res +3 -4
- package/src/plugin/lifecycle/PluginSpec.res +1 -2
- package/src/plugin/lifecycle/PluginsProjection.res +16 -5
- package/src/plugin/lifecycle/PluginsReadModelSpec.res +0 -2
- package/src/util/CommandPublisher.res +9 -2
- package/src/util/CsvStream.res +3 -3
- package/src/util/Interstack.res +7 -15
- package/src/util/Interstack.resi +3 -3
- package/src/util/LogFormat.res +5 -9
- package/src/util/Logger.res +1 -2
- package/src/util/Lru.res +2 -1
- package/src/util/Lru.resi +2 -3
- package/src/util/ScheduleOps.res +24 -27
- package/src/util/StorePrefixCollision.res +6 -9
- package/src/util/Util_Promise.res +4 -1
- package/src/util/Util_Promise.res.mjs +1 -1
- package/tests/IdentityTest.res +166 -113
- package/tests/ResourceAttributionTest.res +28 -25
- package/tests/StorePrefixCollisionTest.res +2 -1
- package/tests/TaskBucketNamingTest.res +3 -2
- package/tests/adapter/EventLogProvisioningTest.res +51 -48
- package/tests/adapter/MonitoringTest.res +81 -38
- package/tests/adapter/QueryInterceptionTest.res +2 -2
- package/tests/adapter/RefusalVocabularyTest.res +31 -23
- package/tests/adapter/RuntimeExtensionHookReachTest.res +9 -8
- package/tests/adapter/RuntimeExtensionTest.res +5 -3
- package/tests/admin/AdminApiSchemaDriftTest.res +22 -16
- package/tests/admin/Platform_Admin_StructureTest.res +10 -11
- package/tests/admin/Platform_BakedManifestTest.res +31 -35
- package/tests/admin/Platform_ComponentDefinitionsApiTest.res +18 -15
- package/tests/admin/Platform_PluginStructuresApiTest.res +5 -3
- package/tests/admin/Platform_UIFragmentsApiTest.res +6 -3
- package/tests/aggregate/AggregateCacheTest.res +10 -12
- package/tests/aggregate/AggregateCallbackTest.res +75 -46
- package/tests/aggregate/AggregateCausationTest.res +19 -20
- package/tests/aggregate/AggregateConflictTest.res +41 -19
- package/tests/aggregate/AggregateFixtures.res +15 -5
- package/tests/aggregate/AggregateRejectionTest.res +97 -80
- package/tests/aggregate/AggregateSnapshotTest.res +44 -16
- package/tests/api/Api_IdsTest.res +10 -4
- package/tests/api/BrandedMarkerCompositionTest.res +48 -36
- package/tests/api/GeolocationSemanticTest.res +3 -3
- package/tests/api/GraphQL_FragmentGeneratorTest.res +66 -54
- package/tests/api/OwnerScopeDiagnosticsTest.res +31 -27
- package/tests/api/StorageRefFieldsTest.res +30 -18
- package/tests/api/SuryToJsonSchemaTest.res +1338 -981
- package/tests/api/SuryToJsonSchemaTest.res.mjs +31 -0
- package/tests/api/TaggedUnionSchemaTest.res +17 -24
- package/tests/commandgenerator/CommandGeneratorCallbackTest.res +235 -179
- package/tests/commandgenerator/CommandGeneratorFixtures.res +8 -13
- package/tests/commandgenerator/OwnerStampingTest.res +91 -76
- package/tests/commandtopic/CommandOutcomeHookTest.res +198 -155
- package/tests/commandtopic/CommandTopicCallbackFixtures.res +5 -4
- package/tests/commandtopic/CommandTopicCallbackTest.res +61 -43
- package/tests/commandtopic/CommandTopicHelpersRejectionTest.res +70 -55
- package/tests/counter/CounterCallbackTest.res +40 -34
- package/tests/counter/CounterFixtures.res +4 -4
- package/tests/dcb/DcbDecodeTest.res +115 -84
- package/tests/dcb/DcbEventLogOperationsTest.res +194 -143
- package/tests/dcb/DcbEventLogSchemaTest.res +13 -13
- package/tests/dcb/DcbFixtures.res +4 -5
- package/tests/dcb/DcbScopeInferenceTest.res +173 -114
- package/tests/dcb/DcbStateChangeSliceTest.res +391 -262
- package/tests/dcb/DcbTagTest.res +22 -11
- package/tests/dcb/DcbValidationTest.res +288 -218
- package/tests/eventlog/EventLogFixtures.res +4 -4
- package/tests/eventlog/EventLogOperationsTest.res +102 -67
- package/tests/eventlog/EventLogRetryTest.res +104 -65
- package/tests/eventmapper/EventMapperCallbackTest.res +118 -83
- package/tests/eventmapper/EventMapperFixtures.res +15 -23
- package/tests/eventmapper/EventMapperTest.res +54 -38
- package/tests/eventtopic/EventTopicFixtures.res +3 -4
- package/tests/eventtopic/EventTopicOperationsTest.res +41 -29
- package/tests/extensionpoint/ExtensionPointCallbackTest.res +58 -45
- package/tests/extensionpoint/ExtensionPointFixtures.res +16 -20
- package/tests/extensionpoint/ExtensionPointOperationsTest.res +70 -49
- package/tests/extensionpoint/PluginDisconnectScheduleTest.res +2 -1
- package/tests/logger/LogFormatTest.res +104 -82
- package/tests/logger/LoggerLazyTest.res +21 -12
- package/tests/message/MessageTest.res +96 -93
- package/tests/message/MetaEnvelopeTest.res +26 -30
- package/tests/plugin/HeartbeatDisconnectGraceTest.res +31 -25
- package/tests/plugin/ManifestVisibilityTest.res +20 -12
- package/tests/plugin/PluginDefinitionScalars.res +1 -2
- package/tests/plugin/PluginLifecycleCorpusTest.res +15 -12
- package/tests/plugin/PluginSpecExperiment.res +0 -1
- package/tests/plugin/PluginStructureTest.res +1533 -1030
- package/tests/plugin/PluginStructureTest.res.mjs +6 -0
- package/tests/plugin/Plugin_EventQuerySchemaTest.res +21 -10
- package/tests/plugin/SchemaWalkerTest.res +116 -70
- package/tests/plugin/StateChange/PsReserveStock.res +4 -4
- package/tests/plugin/StateView/PsAnnotatedView.res +6 -4
- package/tests/plugin/StateView/PsAnnotatedView.res.mjs +7 -3
- package/tests/plugin/StateView/PsCustomersView.res +4 -4
- package/tests/plugin/StateView/PsShipmentsView.res +3 -2
- package/tests/projection/LifecycleTrailTest.res +1 -4
- package/tests/projection/ProjectionOptimizeTest.res +13 -9
- package/tests/querydb/QueryDbFixtures.res +0 -1
- package/tests/querydb/QueryDbOperationsTest.res +99 -64
- package/tests/querydb/TaggedUnionStampTest.res +10 -8
- package/tests/sideeffecthandler/SideEffectHandlerCallbackTest.res +50 -35
- package/tests/sideeffecthandler/SideEffectHandlerFixtures.res +4 -10
- package/tests/util/CommandPublisherTest.res +0 -1
- package/tests/util/CsvStreamTest.res +3 -3
- package/tests/util/LruTest.res +84 -56
|
@@ -34,109 +34,126 @@ let uninstallChannels = () => {
|
|
|
34
34
|
|
|
35
35
|
describe("Aggregate decide-rejection propagation:", () => {
|
|
36
36
|
describe("single rejected command", () => {
|
|
37
|
-
testPromise(
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
testPromise(
|
|
38
|
+
"Create on existing aggregate reports Rejected with errorCode",
|
|
39
|
+
async () => {
|
|
40
|
+
// Seed: aggregate already created → second Create rejects with AlreadyExists.
|
|
41
|
+
let _ = await Stream.fromIterable([
|
|
41
42
|
makeTopicItem("ref-seed", AggSpec.Create({name: "Widget"})),
|
|
42
|
-
])
|
|
43
|
+
])
|
|
44
|
+
->TestHandler.handleCommands
|
|
45
|
+
->Effect.runPromise
|
|
43
46
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
await Stream.fromIterable([
|
|
47
|
+
installChannels()
|
|
48
|
+
let results = await Stream.fromIterable([
|
|
47
49
|
makeTopicItem("ref-1", AggSpec.Create({name: "Widget"})),
|
|
48
|
-
])
|
|
49
|
-
|
|
50
|
+
])
|
|
51
|
+
->TestHandler.handleCommands
|
|
52
|
+
->Effect.runPromise
|
|
53
|
+
uninstallChannels()
|
|
50
54
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
55
|
+
// Producer sees Rejected on the channel; SQS sees Ok (delete — don't redeliver).
|
|
56
|
+
expect(results)->toEqual([Ok("ref-1")])
|
|
57
|
+
expect(capturedRejections.contents->Array.length)->toBe(1)
|
|
58
|
+
let (ref, info) = capturedRejections.contents->Array.getUnsafe(0)
|
|
59
|
+
expect(ref)->toEqual("ref-1")
|
|
60
|
+
expect(info.errorCode)->toEqual("AlreadyExists")
|
|
61
|
+
// No event was appended.
|
|
62
|
+
expect(mock.getAll()->Array.length)->toBe(1)
|
|
63
|
+
},
|
|
64
|
+
)
|
|
60
65
|
})
|
|
61
66
|
|
|
62
67
|
describe("mixed batch", () => {
|
|
63
|
-
testPromise(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
68
|
+
testPromise(
|
|
69
|
+
"rejected command does not cancel surviving commands",
|
|
70
|
+
async () => {
|
|
71
|
+
// Seed.
|
|
72
|
+
let _ = await Stream.fromIterable([
|
|
67
73
|
makeTopicItem(~aggId="agg-1", "ref-seed", AggSpec.Create({name: "Widget"})),
|
|
68
|
-
])
|
|
74
|
+
])
|
|
75
|
+
->TestHandler.handleCommands
|
|
76
|
+
->Effect.runPromise
|
|
69
77
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
await Stream.fromIterable([
|
|
78
|
+
installChannels()
|
|
79
|
+
// Batch on the same aggregate: Rename (ok) → Create (rejects) → Rename (ok)
|
|
80
|
+
let results = await Stream.fromIterable([
|
|
74
81
|
makeTopicItem(~aggId="agg-1", "ref-1", AggSpec.Rename({newName: "First"})),
|
|
75
82
|
makeTopicItem(~aggId="agg-1", "ref-2", AggSpec.Create({name: "Boom"})),
|
|
76
83
|
makeTopicItem(~aggId="agg-1", "ref-3", AggSpec.Rename({newName: "Last"})),
|
|
77
|
-
])
|
|
78
|
-
|
|
84
|
+
])
|
|
85
|
+
->TestHandler.handleCommands
|
|
86
|
+
->Effect.runPromise
|
|
87
|
+
uninstallChannels()
|
|
79
88
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
89
|
+
// All three references map to Ok (rejected ones still Ok — SQS deletes).
|
|
90
|
+
expect(results->Array.length)->toBe(3)
|
|
91
|
+
results->Array.forEach(
|
|
92
|
+
r =>
|
|
93
|
+
switch r {
|
|
94
|
+
| Ok(_) => ()
|
|
95
|
+
| Error(_) => expect("expected all Ok")->toEqual("got Error")
|
|
96
|
+
},
|
|
97
|
+
)
|
|
98
|
+
// Two Renamed events appended (the rejected Create produced none).
|
|
99
|
+
// Seed = 1, plus 2 Renamed = 3 stored.
|
|
100
|
+
expect(mock.getAll()->Array.length)->toBe(3)
|
|
101
|
+
// ref-2 reported as Rejected with AlreadyExists.
|
|
102
|
+
let rejected = capturedRejections.contents
|
|
103
|
+
expect(rejected->Array.length)->toBe(1)
|
|
104
|
+
let (rRef, rInfo) = rejected->Array.getUnsafe(0)
|
|
105
|
+
expect(rRef)->toEqual("ref-2")
|
|
106
|
+
expect(rInfo.errorCode)->toEqual("AlreadyExists")
|
|
107
|
+
// ref-1 and ref-3 reported as Accepted (eventCount > 0 against the appended batch).
|
|
108
|
+
let accepted = capturedAccepts.contents
|
|
109
|
+
expect(accepted->Array.length)->toBe(2)
|
|
110
|
+
},
|
|
111
|
+
)
|
|
101
112
|
})
|
|
102
113
|
|
|
103
114
|
describe("all-reject batch", () => {
|
|
104
|
-
testPromise(
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
115
|
+
testPromise(
|
|
116
|
+
"3 rejecting commands → no append, all reported Rejected, no Accepted",
|
|
117
|
+
async () => {
|
|
118
|
+
// No seed → empty history. Rename on a NotCreated aggregate rejects with NotFound.
|
|
119
|
+
installChannels()
|
|
120
|
+
let results = await Stream.fromIterable([
|
|
109
121
|
makeTopicItem(~aggId="agg-x", "ref-1", AggSpec.Rename({newName: "A"})),
|
|
110
122
|
makeTopicItem(~aggId="agg-x", "ref-2", AggSpec.Rename({newName: "B"})),
|
|
111
123
|
makeTopicItem(~aggId="agg-x", "ref-3", AggSpec.Rename({newName: "C"})),
|
|
112
|
-
])
|
|
113
|
-
|
|
124
|
+
])
|
|
125
|
+
->TestHandler.handleCommands
|
|
126
|
+
->Effect.runPromise
|
|
127
|
+
uninstallChannels()
|
|
114
128
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
129
|
+
expect(results)->toEqual([Ok("ref-1"), Ok("ref-2"), Ok("ref-3")])
|
|
130
|
+
// No events appended.
|
|
131
|
+
expect(mock.getAll()->Array.length)->toBe(0)
|
|
132
|
+
// All three reported as Rejected.
|
|
133
|
+
expect(capturedRejections.contents->Array.length)->toBe(3)
|
|
134
|
+
capturedRejections.contents->Array.forEach(
|
|
135
|
+
((_ref, info)) => expect(info.errorCode)->toEqual("NotFound"),
|
|
136
|
+
)
|
|
137
|
+
// No Accepted callbacks fired (when nothing is appended and nothing is OK).
|
|
138
|
+
expect(capturedAccepts.contents->Array.length)->toBe(0)
|
|
139
|
+
},
|
|
140
|
+
)
|
|
126
141
|
})
|
|
127
142
|
|
|
128
143
|
describe("payload-less variant errors", () => {
|
|
129
|
-
testPromise(
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
144
|
+
testPromise(
|
|
145
|
+
"errorDetail is empty string for unit-payload variants",
|
|
146
|
+
async () => {
|
|
147
|
+
// AggSpec.error variants are all payload-less (NotFound | AlreadyExists).
|
|
148
|
+
installChannels()
|
|
149
|
+
let _ = await Stream.fromIterable([makeTopicItem("ref-1", AggSpec.Rename({newName: "X"}))])
|
|
150
|
+
->TestHandler.handleCommands
|
|
151
|
+
->Effect.runPromise
|
|
152
|
+
uninstallChannels()
|
|
137
153
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
154
|
+
let (_ref, info) = capturedRejections.contents->Array.getUnsafe(0)
|
|
155
|
+
expect(info.errorDetail)->toEqual("")
|
|
156
|
+
},
|
|
157
|
+
)
|
|
141
158
|
})
|
|
142
159
|
})
|
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
|
|
13
13
|
open JestGlobals
|
|
14
14
|
|
|
15
|
-
|
|
16
15
|
module AggSpec = {
|
|
17
16
|
module Id = Reventless.Id.StringPure
|
|
18
17
|
let name = "SnapshotTestAggregate"
|
|
@@ -24,7 +23,7 @@ module AggSpec = {
|
|
|
24
23
|
type event = Added({item: string})
|
|
25
24
|
|
|
26
25
|
@schema
|
|
27
|
-
type error =
|
|
26
|
+
type error = Never
|
|
28
27
|
|
|
29
28
|
let moduleUrl: string = %raw(`import.meta.url`)
|
|
30
29
|
}
|
|
@@ -161,7 +160,11 @@ let mock = makeMockEL()
|
|
|
161
160
|
|
|
162
161
|
module OuterEventLog = EventLog
|
|
163
162
|
|
|
164
|
-
module MakeOps = (
|
|
163
|
+
module MakeOps = (
|
|
164
|
+
B: {
|
|
165
|
+
let name: string
|
|
166
|
+
},
|
|
167
|
+
) => {
|
|
165
168
|
module Spec = AggSpec
|
|
166
169
|
module EventLog = {
|
|
167
170
|
module Spec = {
|
|
@@ -178,11 +181,11 @@ module MakeOps = (B: {let name: string}) => {
|
|
|
178
181
|
) => promise<result<unit, EventLog.appendError>>,
|
|
179
182
|
replay: string => promise<array<AggSpec.event>>,
|
|
180
183
|
replayStream: (string, ~fromSeq: int=?) => Stream.t<AggSpec.event, string, unit>,
|
|
181
|
-
appendStream: (
|
|
182
|
-
|
|
184
|
+
appendStream: (
|
|
185
|
+
int,
|
|
183
186
|
string,
|
|
184
|
-
unit
|
|
185
|
-
>,
|
|
187
|
+
Stream.t<AggSpec.event, string, unit>,
|
|
188
|
+
) => Effect.t<unit, string, unit>,
|
|
186
189
|
latestSnapshot: string => promise<result<option<EventLog.snapshot>, string>>,
|
|
187
190
|
writeSnapshot: (string, EventLog.snapshot) => promise<result<unit, string>>,
|
|
188
191
|
}
|
|
@@ -199,7 +202,9 @@ module MakeOps = (B: {let name: string}) => {
|
|
|
199
202
|
}
|
|
200
203
|
}
|
|
201
204
|
|
|
202
|
-
module TestOps = MakeOps({
|
|
205
|
+
module TestOps = MakeOps({
|
|
206
|
+
let name = "SnapshotMockEventLog"
|
|
207
|
+
})
|
|
203
208
|
module TestHandler = Aggregate_Callback.Make(AggSpec, TestBehavior, TestOps)
|
|
204
209
|
|
|
205
210
|
// A second behavior with snapshots DISABLED, sharing the same mock, to prove
|
|
@@ -225,7 +230,9 @@ module PlainBehavior = {
|
|
|
225
230
|
}
|
|
226
231
|
}
|
|
227
232
|
|
|
228
|
-
module PlainOps = MakeOps({
|
|
233
|
+
module PlainOps = MakeOps({
|
|
234
|
+
let name = "PlainMockEventLog"
|
|
235
|
+
})
|
|
229
236
|
module PlainHandler = Aggregate_Callback.Make(AggSpec, PlainBehavior, PlainOps)
|
|
230
237
|
|
|
231
238
|
let makeItem = (~id="agg-1", reference, command): CommandTopic.topicItem<
|
|
@@ -253,7 +260,10 @@ describe("Aggregate_Callback — snapshots:", () => {
|
|
|
253
260
|
testPromise("writes a snapshot each time an interval boundary is crossed", async () => {
|
|
254
261
|
// 25 single-event commands → boundaries at 10 and 20 (not 25).
|
|
255
262
|
for i in 1 to 25 {
|
|
256
|
-
let _ = await run(
|
|
263
|
+
let _ = await run(
|
|
264
|
+
~handler=TestHandler.handleCommands,
|
|
265
|
+
AggSpec.Add({item: `i${i->Int.toString}`}),
|
|
266
|
+
)
|
|
257
267
|
}
|
|
258
268
|
let _ = await tick()
|
|
259
269
|
expect(mock.snapshotWriteCount.contents)->toBe(2)
|
|
@@ -266,7 +276,10 @@ describe("Aggregate_Callback — snapshots:", () => {
|
|
|
266
276
|
|
|
267
277
|
testPromise("cold replay seeds from the snapshot and reads only the delta", async () => {
|
|
268
278
|
for i in 1 to 12 {
|
|
269
|
-
let _ = await run(
|
|
279
|
+
let _ = await run(
|
|
280
|
+
~handler=TestHandler.handleCommands,
|
|
281
|
+
AggSpec.Add({item: `i${i->Int.toString}`}),
|
|
282
|
+
)
|
|
270
283
|
}
|
|
271
284
|
let _ = await tick()
|
|
272
285
|
// A snapshot exists at seq 10. Evict the in-process cache to force a cold read.
|
|
@@ -282,7 +295,10 @@ describe("Aggregate_Callback — snapshots:", () => {
|
|
|
282
295
|
|
|
283
296
|
testPromise("state seeded from snapshot equals a full-replay fold", async () => {
|
|
284
297
|
for i in 1 to 15 {
|
|
285
|
-
let _ = await run(
|
|
298
|
+
let _ = await run(
|
|
299
|
+
~handler=TestHandler.handleCommands,
|
|
300
|
+
AggSpec.Add({item: `x${i->Int.toString}`}),
|
|
301
|
+
)
|
|
286
302
|
}
|
|
287
303
|
let _ = await tick()
|
|
288
304
|
// Snapshot-seeded cold read (snapshot at seq 10, delta 10..15).
|
|
@@ -292,7 +308,10 @@ describe("Aggregate_Callback — snapshots:", () => {
|
|
|
292
308
|
// Force a boundary to capture the seeded state as a fresh snapshot: add up to
|
|
293
309
|
// seq 20 and compare its item count to the true event count.
|
|
294
310
|
for i in 16 to 20 {
|
|
295
|
-
let _ = await run(
|
|
311
|
+
let _ = await run(
|
|
312
|
+
~handler=TestHandler.handleCommands,
|
|
313
|
+
AggSpec.Add({item: `x${i->Int.toString}`}),
|
|
314
|
+
)
|
|
296
315
|
}
|
|
297
316
|
let _ = await tick()
|
|
298
317
|
switch mock.getSnapshot("agg-1") {
|
|
@@ -312,7 +331,10 @@ describe("Aggregate_Callback — snapshots:", () => {
|
|
|
312
331
|
|
|
313
332
|
testPromise("schema-drift snapshot is ignored → full replay from 0", async () => {
|
|
314
333
|
for i in 1 to 12 {
|
|
315
|
-
let _ = await run(
|
|
334
|
+
let _ = await run(
|
|
335
|
+
~handler=TestHandler.handleCommands,
|
|
336
|
+
AggSpec.Add({item: `i${i->Int.toString}`}),
|
|
337
|
+
)
|
|
316
338
|
}
|
|
317
339
|
let _ = await tick()
|
|
318
340
|
// Corrupt the stored snapshot's hash to simulate a state-shape change.
|
|
@@ -329,7 +351,10 @@ describe("Aggregate_Callback — snapshots:", () => {
|
|
|
329
351
|
|
|
330
352
|
testPromise("snapshot write failure does not fail the command", async () => {
|
|
331
353
|
for i in 1 to 9 {
|
|
332
|
-
let _ = await run(
|
|
354
|
+
let _ = await run(
|
|
355
|
+
~handler=TestHandler.handleCommands,
|
|
356
|
+
AggSpec.Add({item: `i${i->Int.toString}`}),
|
|
357
|
+
)
|
|
333
358
|
}
|
|
334
359
|
// The 10th append crosses the boundary; make its snapshot write fail.
|
|
335
360
|
mock.failNextWrite := true
|
|
@@ -344,7 +369,10 @@ describe("Aggregate_Callback — snapshots:", () => {
|
|
|
344
369
|
|
|
345
370
|
testPromise("disabled aggregate does zero snapshot reads and writes", async () => {
|
|
346
371
|
for i in 1 to 12 {
|
|
347
|
-
let _ = await run(
|
|
372
|
+
let _ = await run(
|
|
373
|
+
~handler=PlainHandler.handleCommands,
|
|
374
|
+
AggSpec.Add({item: `i${i->Int.toString}`}),
|
|
375
|
+
)
|
|
348
376
|
}
|
|
349
377
|
let _ = await tick()
|
|
350
378
|
PlainHandler.resetCache()
|
|
@@ -40,8 +40,9 @@ describe("Api_Ids.encode / decode", () => {
|
|
|
40
40
|
|
|
41
41
|
describe("Api_Ids.alternateKey — the fallback a door tries on a miss", () => {
|
|
42
42
|
testSync("a global id offers the storage key inside it", () =>
|
|
43
|
-
expect(
|
|
44
|
-
|
|
43
|
+
expect(
|
|
44
|
+
Api_Ids.alternateKey(Api_Ids.encode(~typeName="Catalog_Product", ~localId="p-1")),
|
|
45
|
+
)->toEqual(Some("p-1"))
|
|
45
46
|
)
|
|
46
47
|
|
|
47
48
|
testSync("a plain key offers nothing, so the lookup is never retried", () =>
|
|
@@ -83,8 +84,13 @@ describe("stamping an id onto a row leaves the stored row alone", () => {
|
|
|
83
84
|
JSON.Encode.object(obj)
|
|
84
85
|
}
|
|
85
86
|
let idOf = j =>
|
|
86
|
-
j
|
|
87
|
-
|
|
87
|
+
j
|
|
88
|
+
->JSON.Decode.object
|
|
89
|
+
->Option.flatMap(d => d->Dict.get("id"))
|
|
90
|
+
->Option.flatMap(JSON.Decode.string)
|
|
91
|
+
expect(idOf(stamped))->toEqual(
|
|
92
|
+
Some(Api_Ids.encode(~typeName="Ordering_Customer", ~localId="cust-42")),
|
|
93
|
+
)
|
|
88
94
|
// The stored row still carries its storage key.
|
|
89
95
|
expect(idOf(row))->toEqual(Some("cust-42"))
|
|
90
96
|
})
|
|
@@ -21,54 +21,66 @@ describe("a field marker on a branded semantic:", () => {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
describe("@owner on an Email field:", () => {
|
|
24
|
-
testSync(
|
|
25
|
-
|
|
24
|
+
testSync(
|
|
25
|
+
"marks the field as the owner",
|
|
26
|
+
() =>
|
|
27
|
+
expect(fieldOf("email")->Option.mapOr(false, Reventless.Owner.isFieldOwner))->toBe(true),
|
|
26
28
|
)
|
|
27
29
|
|
|
28
|
-
testSync(
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
testSync(
|
|
31
|
+
"keeps the email semantic",
|
|
32
|
+
() =>
|
|
33
|
+
expect(
|
|
34
|
+
fieldOf("email")
|
|
35
|
+
->Option.flatMap(Reventless.Semantic.getFrom)
|
|
36
|
+
->Option.map(s => s.id),
|
|
37
|
+
)->toEqual(Some("email")),
|
|
34
38
|
)
|
|
35
39
|
|
|
36
40
|
// The regression a substituting fix would land, and it lands in silence:
|
|
37
41
|
// the row still scopes to its owner, and the field accepts anything.
|
|
38
|
-
testSync(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
switch
|
|
43
|
-
|
|
|
44
|
-
|
|
42
|
+
testSync(
|
|
43
|
+
"still rejects what is not an address",
|
|
44
|
+
() => {
|
|
45
|
+
let parses = raw =>
|
|
46
|
+
switch fieldOf("email") {
|
|
47
|
+
| Some(schema) =>
|
|
48
|
+
switch raw->JSON.Encode.string->S.parseOrThrow(~to=schema) {
|
|
49
|
+
| _ => true
|
|
50
|
+
| exception _ => false
|
|
51
|
+
}
|
|
52
|
+
| None => false
|
|
45
53
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
})
|
|
54
|
+
expect((parses("buyer@example.com"), parses("buyer")))->toEqual((true, false))
|
|
55
|
+
},
|
|
56
|
+
)
|
|
50
57
|
})
|
|
51
58
|
|
|
52
59
|
describe("@displayName on a DateTime field:", () => {
|
|
53
|
-
testSync(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
60
|
+
testSync(
|
|
61
|
+
"names the row by it",
|
|
62
|
+
() =>
|
|
63
|
+
expect(
|
|
64
|
+
S.Metadata.get(stateSchema, ~id=Reventless.DisplayName.displayNameId)->Option.map(
|
|
65
|
+
d => d.fields,
|
|
66
|
+
),
|
|
67
|
+
)->toEqual(Some(["openedAt"])),
|
|
59
68
|
)
|
|
60
69
|
|
|
61
|
-
testSync(
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
switch
|
|
66
|
-
|
|
|
67
|
-
|
|
70
|
+
testSync(
|
|
71
|
+
"and the instant grammar survives",
|
|
72
|
+
() => {
|
|
73
|
+
let parses = raw =>
|
|
74
|
+
switch fieldOf("openedAt") {
|
|
75
|
+
| Some(schema) =>
|
|
76
|
+
switch raw->JSON.Encode.string->S.parseOrThrow(~to=schema) {
|
|
77
|
+
| _ => true
|
|
78
|
+
| exception _ => false
|
|
79
|
+
}
|
|
80
|
+
| None => false
|
|
68
81
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
})
|
|
82
|
+
expect((parses("2026-03-02T09:00:00Z"), parses("tomorrow")))->toEqual((true, false))
|
|
83
|
+
},
|
|
84
|
+
)
|
|
73
85
|
})
|
|
74
86
|
})
|
|
@@ -101,8 +101,8 @@ describe("the Geolocation semantic emits its three member types", () => {
|
|
|
101
101
|
})
|
|
102
102
|
|
|
103
103
|
testSync("the field is typed as the union", () => {
|
|
104
|
-
expect(
|
|
105
|
-
|
|
106
|
-
)
|
|
104
|
+
expect(
|
|
105
|
+
typeDefFor("type SemanticGeoCustomer")->String.includes("geolocation: Geolocation"),
|
|
106
|
+
)->toBe(true)
|
|
107
107
|
})
|
|
108
108
|
})
|