@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
|
@@ -20,6 +20,7 @@ import * as Reference$Reventless from "@reventlessdev/reventless-spec/src/compon
|
|
|
20
20
|
import * as Sensitive$Reventless from "@reventlessdev/reventless-spec/src/components/Sensitive.res.mjs";
|
|
21
21
|
import * as StorageRef$Reventless from "@reventlessdev/reventless-spec/src/semantic/StorageRef.res.mjs";
|
|
22
22
|
import * as CalendarDate$Reventless from "@reventlessdev/reventless-spec/src/semantic/CalendarDate.res.mjs";
|
|
23
|
+
import * as FieldDefault$Reventless from "@reventlessdev/reventless-spec/src/components/FieldDefault.res.mjs";
|
|
23
24
|
import * as StateAnnotations$Reventless from "@reventlessdev/reventless-spec/src/components/StateAnnotations.res.mjs";
|
|
24
25
|
import * as SuryToJsonSchema$ReventlessCore from "../../src/components/Api/SuryToJsonSchema.res.mjs";
|
|
25
26
|
|
|
@@ -1163,6 +1164,36 @@ globalThis.describe("SuryToJsonSchema:", () => {
|
|
|
1163
1164
|
globalThis.expect(nestedRequired.includes("street")).toBe(true);
|
|
1164
1165
|
});
|
|
1165
1166
|
});
|
|
1167
|
+
globalThis.describe("a field declaring a default:", () => {
|
|
1168
|
+
let defaultOf = (json, field) => Stdlib_Option.flatMap(getPropertyOf(json, field), s => getProperty(s, "default"));
|
|
1169
|
+
let json = SuryToJsonSchema$ReventlessCore.deriveObjectSchema(undefined, undefined, Sury.$schema(s => ({
|
|
1170
|
+
quantity: s.m(FieldDefault$Reventless.int(Sury.int, 1)),
|
|
1171
|
+
shippingMethod: s.m(FieldDefault$Reventless.string(Sury.string, "Standard")),
|
|
1172
|
+
plain: s.m(Sury.int)
|
|
1173
|
+
})));
|
|
1174
|
+
globalThis.test("carries it as `default`", () => {
|
|
1175
|
+
globalThis.expect(defaultOf(json, "quantity")).toEqual(1);
|
|
1176
|
+
});
|
|
1177
|
+
globalThis.test("keeps the shape the value actually has", () => {
|
|
1178
|
+
globalThis.expect(Stdlib_Option.flatMap(getPropertyOf(json, "quantity"), s => getProperty(s, "type"))).toEqual("integer");
|
|
1179
|
+
});
|
|
1180
|
+
globalThis.test("carries a string default the same way", () => {
|
|
1181
|
+
globalThis.expect(defaultOf(json, "shippingMethod")).toEqual("Standard");
|
|
1182
|
+
});
|
|
1183
|
+
globalThis.test("while a field declaring none says nothing", () => {
|
|
1184
|
+
globalThis.expect(defaultOf(json, "plain")).toBe(undefined);
|
|
1185
|
+
});
|
|
1186
|
+
globalThis.test("and reaches a field of a nested record", () => {
|
|
1187
|
+
let lineItem = Sury.$schema(s => ({
|
|
1188
|
+
productId: s.m(Sury.string),
|
|
1189
|
+
quantity: s.m(FieldDefault$Reventless.int(Sury.int, 1))
|
|
1190
|
+
}));
|
|
1191
|
+
let nested = SuryToJsonSchema$ReventlessCore.deriveObjectSchema(undefined, undefined, Sury.$schema(s => ({
|
|
1192
|
+
lineItems: s.m(Sury.array(lineItem))
|
|
1193
|
+
})));
|
|
1194
|
+
globalThis.expect(Stdlib_Option.flatMap(Stdlib_Option.flatMap(getPropertyOf(nested, "lineItems"), a => getProperty(a, "items")), i => defaultOf(i, "quantity"))).toEqual(1);
|
|
1195
|
+
});
|
|
1196
|
+
});
|
|
1166
1197
|
});
|
|
1167
1198
|
|
|
1168
1199
|
export {
|
|
@@ -31,8 +31,7 @@ describe("SchemaType classifies a named union", () => {
|
|
|
31
31
|
expect(fields->Dict.keysToArray)->toEqual(["point"])
|
|
32
32
|
| _ => expect("Located arm")->toBe("an ObjectRef")
|
|
33
33
|
}
|
|
34
|
-
| other =>
|
|
35
|
-
expect(other->Option.isSome ? "some other shape" : "nothing")->toBe("a TaggedUnion")
|
|
34
|
+
| other => expect(other->Option.isSome ? "some other shape" : "nothing")->toBe("a TaggedUnion")
|
|
36
35
|
}
|
|
37
36
|
})
|
|
38
37
|
|
|
@@ -58,8 +57,7 @@ describe("SchemaType classifies a named union", () => {
|
|
|
58
57
|
| [{path, reason}] =>
|
|
59
58
|
expect(path)->toBe("verdict")
|
|
60
59
|
expect(reason->String.includes("no name"))->toBe(true)
|
|
61
|
-
| found =>
|
|
62
|
-
expect(found->Array.length)->toBe(1)
|
|
60
|
+
| found => expect(found->Array.length)->toBe(1)
|
|
63
61
|
}
|
|
64
62
|
})
|
|
65
63
|
|
|
@@ -139,17 +137,16 @@ describe("GraphQL_FragmentGenerator emits a union", () => {
|
|
|
139
137
|
~specName="TaggedUnionUnnamed",
|
|
140
138
|
UnnamedSpec.stateSchema,
|
|
141
139
|
)
|
|
142
|
-
expect(
|
|
143
|
-
|
|
144
|
-
)
|
|
140
|
+
expect(typeDefFor(unnamed, "type Ordering_TuCase ")->String.includes("verdict: String!"))->toBe(
|
|
141
|
+
true,
|
|
142
|
+
)
|
|
145
143
|
expect(typesOf(unnamed)->Array.some(t => t->String.startsWith("union ")))->toBe(false)
|
|
146
144
|
})
|
|
147
145
|
})
|
|
148
146
|
|
|
149
147
|
// ── The JSON Schema ───────────────────────────────────────────────────────
|
|
150
148
|
|
|
151
|
-
let objAt = (json, key) =>
|
|
152
|
-
json->JSON.Decode.object->Option.flatMap(o => o->Dict.get(key))
|
|
149
|
+
let objAt = (json, key) => json->JSON.Decode.object->Option.flatMap(o => o->Dict.get(key))
|
|
153
150
|
|
|
154
151
|
describe("SuryToJsonSchema emits the arms as oneOf", () => {
|
|
155
152
|
let json = SuryToJsonSchema.deriveObjectSchema(CustomersSpec.stateSchema->S.castToUnknown)
|
|
@@ -166,15 +163,15 @@ describe("SuryToJsonSchema emits the arms as oneOf", () => {
|
|
|
166
163
|
})
|
|
167
164
|
|
|
168
165
|
testSync("one member per arm, each discriminated by its TAG const", () => {
|
|
169
|
-
let members =
|
|
170
|
-
objAt(field, "oneOf")->Option.flatMap(JSON.Decode.array)->Option.getOr([])
|
|
166
|
+
let members = objAt(field, "oneOf")->Option.flatMap(JSON.Decode.array)->Option.getOr([])
|
|
171
167
|
expect(members->Array.length)->toBe(3)
|
|
172
|
-
let tags = members->Array.filterMap(
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
168
|
+
let tags = members->Array.filterMap(
|
|
169
|
+
m =>
|
|
170
|
+
objAt(m, "properties")
|
|
171
|
+
->Option.flatMap(JSON.Decode.object)
|
|
172
|
+
->Option.flatMap(p => p->Dict.get("TAG"))
|
|
173
|
+
->Option.flatMap(t => objAt(t, "const"))
|
|
174
|
+
->Option.flatMap(JSON.Decode.string),
|
|
178
175
|
)
|
|
179
176
|
expect(tags)->toEqual(["Pending", "Located", "Unresolvable"])
|
|
180
177
|
})
|
|
@@ -186,13 +183,9 @@ describe("SuryToJsonSchema emits the arms as oneOf", () => {
|
|
|
186
183
|
objAt(field, "oneOf")
|
|
187
184
|
->Option.flatMap(JSON.Decode.array)
|
|
188
185
|
->Option.getOr([])
|
|
189
|
-
->Array.filterMap(
|
|
190
|
-
objAt(m, "x-reventless-union-member")->Option.flatMap(JSON.Decode.string)
|
|
186
|
+
->Array.filterMap(
|
|
187
|
+
m => objAt(m, "x-reventless-union-member")->Option.flatMap(JSON.Decode.string),
|
|
191
188
|
)
|
|
192
|
-
expect(names)->toEqual([
|
|
193
|
-
"GeolocationPending",
|
|
194
|
-
"GeolocationLocated",
|
|
195
|
-
"GeolocationUnresolvable",
|
|
196
|
-
])
|
|
189
|
+
expect(names)->toEqual(["GeolocationPending", "GeolocationLocated", "GeolocationUnresolvable"])
|
|
197
190
|
})
|
|
198
191
|
})
|
|
@@ -5,125 +5,159 @@ let _ = beforeEach(() => reset())
|
|
|
5
5
|
|
|
6
6
|
describe("CommandGenerator_Callback.generateCommand:", () => {
|
|
7
7
|
describe("zero-param command", () => {
|
|
8
|
-
testPromise(
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
8
|
+
testPromise(
|
|
9
|
+
"publishJsons called with plain string commandJson",
|
|
10
|
+
async () => {
|
|
11
|
+
let payload = makeZeroParamPayload(~id="agg-1", ~command="Create")
|
|
12
|
+
let _msgId = await TestGenerator.generateCommand(payload)->Effect.runPromise
|
|
13
|
+
let publishedCmd = capturedCmds.contents->Array.getUnsafe(0)
|
|
14
|
+
// Zero-param command serializes as a plain JSON string
|
|
15
|
+
expect(publishedCmd.commandJson)->toEqual(JSON.Encode.string("Create"))
|
|
16
|
+
},
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
testPromise(
|
|
20
|
+
"returns a Pending outcome with non-empty msgId",
|
|
21
|
+
async () => {
|
|
22
|
+
let payload = makeZeroParamPayload(~id="agg-1", ~command="Create")
|
|
23
|
+
let outcome = await TestGenerator.generateCommand(payload)->Effect.runPromise
|
|
24
|
+
let msgId = switch outcome {
|
|
25
|
+
| CommandTopic.Pending({msgId}) => msgId
|
|
26
|
+
| _ => ""
|
|
27
|
+
}
|
|
28
|
+
expect(msgId->String.length > 0)->toBe(true)
|
|
29
|
+
},
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
testPromise(
|
|
33
|
+
"meta.service equals AggregateSpec.name",
|
|
34
|
+
async () => {
|
|
35
|
+
let payload = makeZeroParamPayload(~id="agg-1", ~command="Create")
|
|
36
|
+
let _outcome = await TestGenerator.generateCommand(payload)->Effect.runPromise
|
|
37
|
+
let publishedCmd = capturedCmds.contents->Array.getUnsafe(0)
|
|
38
|
+
expect(publishedCmd.meta.service)->toBe(CmdGenAggSpec.name)
|
|
39
|
+
},
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
testPromise(
|
|
43
|
+
"meta.msgId equals meta.correlationId",
|
|
44
|
+
async () => {
|
|
45
|
+
let payload = makeZeroParamPayload(~id="agg-1", ~command="Create")
|
|
46
|
+
let outcome = await TestGenerator.generateCommand(payload)->Effect.runPromise
|
|
47
|
+
let publishedCmd = capturedCmds.contents->Array.getUnsafe(0)
|
|
48
|
+
let msgId = switch outcome {
|
|
49
|
+
| CommandTopic.Pending({msgId}) => msgId
|
|
50
|
+
| _ => ""
|
|
51
|
+
}
|
|
52
|
+
expect((publishedCmd.meta.msgId, publishedCmd.meta.correlationId))->toEqual((msgId, msgId))
|
|
53
|
+
},
|
|
54
|
+
)
|
|
43
55
|
})
|
|
44
56
|
|
|
45
57
|
describe("multi-param command", () => {
|
|
46
|
-
testPromise(
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
58
|
+
testPromise(
|
|
59
|
+
"publishJsons called with object commandJson {TAG, param}",
|
|
60
|
+
async () => {
|
|
61
|
+
let payload = makeOneParamPayload(
|
|
62
|
+
~id="agg-1",
|
|
63
|
+
~command="CreateWithName",
|
|
64
|
+
~paramName="name",
|
|
65
|
+
~paramValue="Widget",
|
|
66
|
+
)
|
|
67
|
+
let _msgId = await TestGenerator.generateCommand(payload)->Effect.runPromise
|
|
68
|
+
let publishedCmd = capturedCmds.contents->Array.getUnsafe(0)
|
|
69
|
+
// Multi-param command serializes as {TAG: "CreateWithName", name: "Widget"}
|
|
70
|
+
let cmdObj = publishedCmd.commandJson->JSON.Decode.object->Option.getOrThrow
|
|
71
|
+
expect(cmdObj->Dict.get("TAG"))->toEqual(Some(JSON.Encode.string("CreateWithName")))
|
|
72
|
+
expect(cmdObj->Dict.get("name"))->toEqual(Some(JSON.Encode.string("Widget")))
|
|
73
|
+
},
|
|
74
|
+
)
|
|
56
75
|
})
|
|
57
76
|
|
|
58
77
|
describe("schema validation failure", () => {
|
|
59
|
-
testPromise(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
78
|
+
testPromise(
|
|
79
|
+
"invalid command name throws an error",
|
|
80
|
+
async () => {
|
|
81
|
+
let payload = makeZeroParamPayload(~id="agg-1", ~command="NonExistentCommand")
|
|
82
|
+
let threw = ref(false)
|
|
83
|
+
switch await TestGenerator.generateCommand(payload)->Effect.runPromise {
|
|
84
|
+
| _ => ()
|
|
85
|
+
| exception _ => threw := true
|
|
86
|
+
}
|
|
87
|
+
expect(threw.contents)->toBe(true)
|
|
88
|
+
},
|
|
89
|
+
)
|
|
68
90
|
})
|
|
69
91
|
|
|
70
92
|
describe("StateChangeSlice envelope id derivation", () => {
|
|
71
|
-
testPromise(
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
)
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
93
|
+
testPromise(
|
|
94
|
+
"single @partitionTag: id derived from tagged field when args have no id",
|
|
95
|
+
async () => {
|
|
96
|
+
let payload = makeSlicePayload(
|
|
97
|
+
~command="CreateItem",
|
|
98
|
+
~args=Dict.fromArray([
|
|
99
|
+
("itemId", JSON.Encode.string("item-42")),
|
|
100
|
+
("name", JSON.Encode.string("Widget")),
|
|
101
|
+
]),
|
|
102
|
+
)
|
|
103
|
+
let outcome = await singleTagSliceGen(payload)->Effect.runPromise
|
|
104
|
+
let publishedCmd = capturedCmds.contents->Array.getUnsafe(0)
|
|
105
|
+
expect(publishedCmd.id)->toBe("item-42")
|
|
106
|
+
// sanity: outcome is well-formed
|
|
107
|
+
let msgId = switch outcome {
|
|
108
|
+
| CommandTopic.Pending({msgId}) => msgId
|
|
109
|
+
| _ => ""
|
|
110
|
+
}
|
|
111
|
+
expect(msgId->String.length > 0)->toBe(true)
|
|
112
|
+
},
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
testPromise(
|
|
116
|
+
"composite @compositePartitionTag: id is joined partition-key value",
|
|
117
|
+
async () => {
|
|
118
|
+
let payload = makeSlicePayload(
|
|
119
|
+
~command="DeployVersion",
|
|
120
|
+
~args=Dict.fromArray([
|
|
121
|
+
("environment", JSON.Encode.string("prod")),
|
|
122
|
+
("service", JSON.Encode.string("checkout")),
|
|
123
|
+
("version", JSON.Encode.string("1.2.3")),
|
|
124
|
+
]),
|
|
125
|
+
)
|
|
126
|
+
let _outcome = await compositeTagSliceGen(payload)->Effect.runPromise
|
|
127
|
+
let publishedCmd = capturedCmds.contents->Array.getUnsafe(0)
|
|
128
|
+
// Composite key joins position-0 value, then sep[0] = "-", then position-1 value
|
|
129
|
+
expect(publishedCmd.id)->toBe("prod-checkout")
|
|
130
|
+
},
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
testPromise(
|
|
134
|
+
"explicit id in args wins over derivation (regression check)",
|
|
135
|
+
async () => {
|
|
136
|
+
let payload = makeSlicePayload(
|
|
137
|
+
~command="CreateItem",
|
|
138
|
+
~args=Dict.fromArray([
|
|
139
|
+
("id", JSON.Encode.string("explicit-id")),
|
|
140
|
+
("itemId", JSON.Encode.string("item-99")),
|
|
141
|
+
("name", JSON.Encode.string("Gadget")),
|
|
142
|
+
]),
|
|
143
|
+
)
|
|
144
|
+
let _outcome = await singleTagSliceGen(payload)->Effect.runPromise
|
|
145
|
+
let publishedCmd = capturedCmds.contents->Array.getUnsafe(0)
|
|
146
|
+
expect(publishedCmd.id)->toBe("explicit-id")
|
|
147
|
+
},
|
|
148
|
+
)
|
|
118
149
|
})
|
|
119
150
|
|
|
120
151
|
describe("Aggregate envelope id (regression)", () => {
|
|
121
|
-
testPromise(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
152
|
+
testPromise(
|
|
153
|
+
"explicit id in args is preserved unchanged",
|
|
154
|
+
async () => {
|
|
155
|
+
let payload = makeZeroParamPayload(~id="agg-7", ~command="Create")
|
|
156
|
+
let _outcome = await TestGenerator.generateCommand(payload)->Effect.runPromise
|
|
157
|
+
let publishedCmd = capturedCmds.contents->Array.getUnsafe(0)
|
|
158
|
+
expect(publishedCmd.id)->toBe("agg-7")
|
|
159
|
+
},
|
|
160
|
+
)
|
|
127
161
|
})
|
|
128
162
|
|
|
129
163
|
// A transport hands over exactly what the caller supplied, and GraphQL lets a
|
|
@@ -137,64 +171,80 @@ describe("CommandGenerator_Callback.generateCommand:", () => {
|
|
|
137
171
|
let fieldOf = (cmd: Message.commandJson, name) =>
|
|
138
172
|
cmd.commandJson->JSON.Decode.object->Option.flatMap(o => o->Dict.get(name))
|
|
139
173
|
|
|
140
|
-
testPromise(
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
(
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
)
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
174
|
+
testPromise(
|
|
175
|
+
"is dropped, and the command decodes and publishes",
|
|
176
|
+
async () => {
|
|
177
|
+
let payload = storeItem(
|
|
178
|
+
~args=Dict.fromArray([
|
|
179
|
+
("itemId", JSON.Encode.string("item-1")),
|
|
180
|
+
("data", data),
|
|
181
|
+
("note", JSON.Null),
|
|
182
|
+
]),
|
|
183
|
+
)
|
|
184
|
+
let _outcome = await optionalFieldSliceGen(payload)->Effect.runPromise
|
|
185
|
+
let publishedCmd = capturedCmds.contents->Array.getUnsafe(0)
|
|
186
|
+
expect((capturedCmds.contents->Array.length, publishedCmd->fieldOf("note")))->toEqual((
|
|
187
|
+
1,
|
|
188
|
+
None,
|
|
189
|
+
))
|
|
190
|
+
},
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
testPromise(
|
|
194
|
+
"carrying a value is left exactly as sent",
|
|
195
|
+
async () => {
|
|
196
|
+
let payload = storeItem(
|
|
197
|
+
~args=Dict.fromArray([
|
|
198
|
+
("itemId", JSON.Encode.string("item-2")),
|
|
199
|
+
("data", data),
|
|
200
|
+
("note", JSON.Encode.string("gift wrap")),
|
|
201
|
+
]),
|
|
202
|
+
)
|
|
203
|
+
let _outcome = await optionalFieldSliceGen(payload)->Effect.runPromise
|
|
204
|
+
expect(capturedCmds.contents->Array.getUnsafe(0)->fieldOf("note"))->toEqual(
|
|
205
|
+
Some(JSON.Encode.string("gift wrap")),
|
|
206
|
+
)
|
|
207
|
+
},
|
|
208
|
+
)
|
|
166
209
|
|
|
167
210
|
// The control the drop is modelled on: absent and explicitly-null must
|
|
168
211
|
// reach the domain as the same command.
|
|
169
|
-
testPromise(
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
(
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
212
|
+
testPromise(
|
|
213
|
+
"and one simply omitted produce the same command",
|
|
214
|
+
async () => {
|
|
215
|
+
let withNull = storeItem(
|
|
216
|
+
~args=Dict.fromArray([
|
|
217
|
+
("itemId", JSON.Encode.string("item-3")),
|
|
218
|
+
("data", data),
|
|
219
|
+
("note", JSON.Null),
|
|
220
|
+
]),
|
|
221
|
+
)
|
|
222
|
+
let withoutKey = storeItem(
|
|
223
|
+
~args=Dict.fromArray([("itemId", JSON.Encode.string("item-3")), ("data", data)]),
|
|
224
|
+
)
|
|
225
|
+
let _ = await optionalFieldSliceGen(withNull)->Effect.runPromise
|
|
226
|
+
let _ = await optionalFieldSliceGen(withoutKey)->Effect.runPromise
|
|
227
|
+
let published = capturedCmds.contents
|
|
228
|
+
expect((published->Array.getUnsafe(0)).commandJson)->toEqual(
|
|
229
|
+
(published->Array.getUnsafe(1)).commandJson,
|
|
230
|
+
)
|
|
231
|
+
},
|
|
232
|
+
)
|
|
186
233
|
|
|
187
234
|
// Shallow on purpose. A null one level in is part of a value the caller
|
|
188
235
|
// sent, not an argument they left out — `data` is opaque JSON and its own
|
|
189
236
|
// nulls are data.
|
|
190
|
-
testPromise(
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
237
|
+
testPromise(
|
|
238
|
+
"nested inside a value is left alone",
|
|
239
|
+
async () => {
|
|
240
|
+
let nested = JSON.Object(Dict.fromArray([("kind", JSON.Null)]))
|
|
241
|
+
let payload = storeItem(
|
|
242
|
+
~args=Dict.fromArray([("itemId", JSON.Encode.string("item-4")), ("data", nested)]),
|
|
243
|
+
)
|
|
244
|
+
let _outcome = await optionalFieldSliceGen(payload)->Effect.runPromise
|
|
245
|
+
expect(capturedCmds.contents->Array.getUnsafe(0)->fieldOf("data"))->toEqual(Some(nested))
|
|
246
|
+
},
|
|
247
|
+
)
|
|
198
248
|
})
|
|
199
249
|
|
|
200
250
|
// A payload that does not decode describes the caller's own request, so a
|
|
@@ -207,26 +257,32 @@ describe("CommandGenerator_Callback.generateCommand:", () => {
|
|
|
207
257
|
~args=Dict.fromArray([("itemId", JSON.Encode.string("item-9"))]),
|
|
208
258
|
)
|
|
209
259
|
|
|
210
|
-
testPromise(
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
->Option.mapOr(false,
|
|
229
|
-
|
|
230
|
-
|
|
260
|
+
testPromise(
|
|
261
|
+
"is refused rather than published",
|
|
262
|
+
async () => {
|
|
263
|
+
let refused = switch await optionalFieldSliceGen(undecodable())->Effect.runPromise {
|
|
264
|
+
| _ => false
|
|
265
|
+
| exception _ => true
|
|
266
|
+
}
|
|
267
|
+
expect((refused, capturedCmds.contents->Array.length))->toEqual((true, 0))
|
|
268
|
+
},
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
testPromise(
|
|
272
|
+
"is marked as the caller's fault, naming the field",
|
|
273
|
+
async () => {
|
|
274
|
+
let failure = switch await optionalFieldSliceGen(undecodable())->Effect.runPromise {
|
|
275
|
+
| _ => None
|
|
276
|
+
| exception e => Some(e)
|
|
277
|
+
}
|
|
278
|
+
let marked = failure->Option.mapOr(false, Plugin_ResolverError.isCallerFault)
|
|
279
|
+
let saysWhy =
|
|
280
|
+
failure
|
|
281
|
+
->Option.flatMap(JsExn.fromException)
|
|
282
|
+
->Option.flatMap(JsExn.message)
|
|
283
|
+
->Option.mapOr(false, m => m->String.includes("data"))
|
|
284
|
+
expect((marked, saysWhy))->toEqual((true, true))
|
|
285
|
+
},
|
|
286
|
+
)
|
|
231
287
|
})
|
|
232
288
|
})
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
// ─────────────────────────────────────────────────────────────
|
|
3
2
|
// Aggregate spec for CommandGenerator tests
|
|
4
3
|
// ─────────────────────────────────────────────────────────────
|
|
@@ -13,7 +12,7 @@ module CmdGenAggSpec = {
|
|
|
13
12
|
type command =
|
|
14
13
|
| Create
|
|
15
14
|
| CreateWithName({name: string})
|
|
16
|
-
| Invalid
|
|
15
|
+
| Invalid // used for schema validation tests
|
|
17
16
|
|
|
18
17
|
@schema
|
|
19
18
|
type event =
|
|
@@ -21,7 +20,7 @@ module CmdGenAggSpec = {
|
|
|
21
20
|
| CreatedWithName({name: string})
|
|
22
21
|
|
|
23
22
|
@schema
|
|
24
|
-
type error =
|
|
23
|
+
type error = InvalidCommand
|
|
25
24
|
|
|
26
25
|
let moduleUrl: string = %raw(`import.meta.url`)
|
|
27
26
|
}
|
|
@@ -40,7 +39,10 @@ module CmdGenBehavior = {
|
|
|
40
39
|
|
|
41
40
|
let evolve = (_state: state, _event: CmdGenAggSpec.event): state => ()
|
|
42
41
|
|
|
43
|
-
let decide = (_state: state, command: CmdGenAggSpec.command): result<
|
|
42
|
+
let decide = (_state: state, command: CmdGenAggSpec.command): result<
|
|
43
|
+
array<CmdGenAggSpec.event>,
|
|
44
|
+
CmdGenAggSpec.error,
|
|
45
|
+
> =>
|
|
44
46
|
switch command {
|
|
45
47
|
| Create => Ok([CmdGenAggSpec.Created])
|
|
46
48
|
| CreateWithName({name}) => Ok([CmdGenAggSpec.CreatedWithName({name: name})])
|
|
@@ -89,11 +91,7 @@ type compositeTagCommand =
|
|
|
89
91
|
// nulls are data and must survive.
|
|
90
92
|
@schema
|
|
91
93
|
type optionalFieldCommand =
|
|
92
|
-
StoreItem({
|
|
93
|
-
itemId: @s.matches(Reventless.DcbTag.string) string,
|
|
94
|
-
data: JSON.t,
|
|
95
|
-
note?: string,
|
|
96
|
-
})
|
|
94
|
+
StoreItem({itemId: @s.matches(Reventless.DcbTag.string) string, data: JSON.t, note?: string})
|
|
97
95
|
|
|
98
96
|
let optionalFieldSliceGen = CommandGenerator_Callback.makeGenerateCommand(
|
|
99
97
|
~publishJsons=MockPublishSpec.publishJsons,
|
|
@@ -144,10 +142,7 @@ let makeOneParamPayload = (~id, ~command, ~paramName, ~paramValue): CommandGener
|
|
|
144
142
|
Obj.magic({
|
|
145
143
|
"command": command,
|
|
146
144
|
"arguments": Obj.magic(
|
|
147
|
-
Dict.fromArray([
|
|
148
|
-
("id", JSON.Encode.string(id)),
|
|
149
|
-
(paramName, JSON.Encode.string(paramValue)),
|
|
150
|
-
]),
|
|
145
|
+
Dict.fromArray([("id", JSON.Encode.string(id)), (paramName, JSON.Encode.string(paramValue))]),
|
|
151
146
|
),
|
|
152
147
|
"meta": {"ip": ["127.0.0.1"], "user": "test-user", "info": ""},
|
|
153
148
|
})
|