@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.
Files changed (209) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/package.json +11 -11
  3. package/src/Behavior.res +1 -4
  4. package/src/Message.res +17 -14
  5. package/src/Projection.res +51 -25
  6. package/src/RequestContext.res +8 -9
  7. package/src/ResourceAttribution.res +28 -14
  8. package/src/adapter/Adapter.res +48 -4
  9. package/src/adapter/EventLogProvisioning/EventLogProvisioning.res +5 -5
  10. package/src/adapter/Messaging/Messaging_Log_Backend.res +10 -7
  11. package/src/adapter/Messaging/Messaging_Log_Backend.res.mjs +2 -4
  12. package/src/adapter/Monitoring/Monitoring.res +16 -17
  13. package/src/adapter/QueryInterception/QueryInterception.res +0 -1
  14. package/src/adapter/Runtime/AggregateRuntime_Builder_Common.res +12 -5
  15. package/src/adapter/Runtime/EventCollectorRuntime_Builder_Single.res +11 -7
  16. package/src/adapter/Runtime/PluginRuntime_Builder_Micro.res +1 -7
  17. package/src/adapter/Runtime/PluginRuntime_Builder_Single.res +1 -7
  18. package/src/adapter/RuntimeExtension/RuntimeExtension.res +0 -1
  19. package/src/admin/Platform_Admin.res +17 -6
  20. package/src/admin/Platform_AdminApi.res +14 -9
  21. package/src/admin/Platform_Admin_Callback.res +1 -1
  22. package/src/admin/Platform_BakedManifest.res +34 -34
  23. package/src/admin/Platform_ComponentDefinitionsApi.res +9 -21
  24. package/src/admin/Platform_PluginStructuresApi.res +15 -12
  25. package/src/admin/UiFragmentRegistry/StateChange/UiFragmentRegistry.res +1 -1
  26. package/src/components/Aggregate/Aggregate.res +13 -12
  27. package/src/components/Aggregate/Aggregate_Builder.res +22 -21
  28. package/src/components/Aggregate/Aggregate_Callback.res +91 -86
  29. package/src/components/Api/ApiNoApiHelpers.res +1 -3
  30. package/src/components/Api/Api_Ids.res +0 -1
  31. package/src/components/Api/Api_Naming.res +41 -18
  32. package/src/components/Api/GraphQL_FragmentGenerator.res +42 -52
  33. package/src/components/Api/GraphQL_FragmentGenerator.res.mjs +11 -4
  34. package/src/components/Api/GraphQL_SchemaInspector.res +5 -3
  35. package/src/components/Api/GraphQL_Stitcher.res +9 -10
  36. package/src/components/Api/MCP_SchemaGenerator.res +8 -12
  37. package/src/components/Api/OwnerScopeDiagnostics.res +1 -2
  38. package/src/components/Api/QueryDbListQuery.res +13 -6
  39. package/src/components/Api/SchemaType.res +40 -20
  40. package/src/components/Api/SchemaType.res.mjs +16 -0
  41. package/src/components/Api/SuryToJsonSchema.res +18 -10
  42. package/src/components/Api/SuryToJsonSchema.res.mjs +135 -121
  43. package/src/components/AutomationSlice/AutomationSlice.res +4 -4
  44. package/src/components/AutomationSlice/AutomationSlice_Builder.res +15 -14
  45. package/src/components/AutomationSlice/AutomationSlice_Callback.res +3 -3
  46. package/src/components/Builder_Helpers.res +12 -23
  47. package/src/components/CommandGenerator/CommandGenerator.res +6 -1
  48. package/src/components/CommandGenerator/CommandGenerator_Builder.res +29 -21
  49. package/src/components/CommandGenerator/CommandGenerator_Callback.res +17 -21
  50. package/src/components/CommandTopic/CommandTopic.res +3 -3
  51. package/src/components/CommandTopic/CommandTopic_Builder.res +10 -10
  52. package/src/components/CommandTopic/CommandTopic_Callback.res +1 -4
  53. package/src/components/CommandTopic/CommandTopic_Helpers.res +1 -2
  54. package/src/components/Counter/Counter_Builder.res +1 -6
  55. package/src/components/Counter/Counter_Callback.res +3 -3
  56. package/src/components/Dcb/Dcb_Builder.res +970 -956
  57. package/src/components/DcbEventLog/DcbEventLog.res +4 -4
  58. package/src/components/DcbEventLog/DcbEventLog_Builder.res +0 -1
  59. package/src/components/DcbEventLog/DcbEventLog_Operations.res +17 -18
  60. package/src/components/EventCollector/EventCollector_Builder.res +2 -1
  61. package/src/components/EventLog/EventLog.res +5 -1
  62. package/src/components/EventLog/EventLog_Operations.res +12 -8
  63. package/src/components/EventMapper/EventMapper.res +8 -10
  64. package/src/components/EventMapper/EventMapper_Builder.res +4 -1
  65. package/src/components/EventMapper/EventMapper_Callback.res +46 -31
  66. package/src/components/EventTopic/EventTopic.res +3 -3
  67. package/src/components/EventTopic/EventTopic_Builder.res +1 -1
  68. package/src/components/Extension/Extension.res +4 -1
  69. package/src/components/Extension/Extension_Operations.res +11 -3
  70. package/src/components/ExtensionPoint/ExtensionPoint.res +3 -3
  71. package/src/components/ExtensionPoint/ExtensionPoint_Builder.res +9 -10
  72. package/src/components/ExtensionPoint/ExtensionPoint_Callback.res +2 -6
  73. package/src/components/ExtensionPoint/ExtensionPoint_Operations.res +13 -5
  74. package/src/components/Heartbeat/Heartbeat_Callback.res +3 -3
  75. package/src/components/InboundTranslationSlice/InboundTranslationSlice.res +4 -4
  76. package/src/components/InboundTranslationSlice/InboundTranslationSlice_Builder.res +7 -10
  77. package/src/components/OutboundTranslationSlice/OutboundTranslationSlice.res +4 -4
  78. package/src/components/OutboundTranslationSlice/OutboundTranslationSlice_Builder.res +15 -14
  79. package/src/components/QueryDb/QueryDb_Adapter.res +1 -3
  80. package/src/components/QueryDb/QueryDb_Operations.res +10 -10
  81. package/src/components/ReadModel/ReadModel.res +3 -3
  82. package/src/components/ReadModel/ReadModel_Builder.res +1 -2
  83. package/src/components/ReadModel/ReadModel_Callback.res +2 -1
  84. package/src/components/SideEffectHandler/SideEffectHandler_Callback.res +5 -2
  85. package/src/components/StateChangeSlice/StateChangeSlice.res +3 -3
  86. package/src/components/StateChangeSlice/StateChangeSlice_Builder.res +23 -4
  87. package/src/components/StateChangeSlice/StateChangeSlice_Callback.res +22 -6
  88. package/src/components/StateTopic.res +1 -3
  89. package/src/components/StateViewSlice/StateViewSlice.res +4 -4
  90. package/src/components/StateViewSlice/StateViewSlice_Builder.res +79 -68
  91. package/src/components/StateViewSlice/StateViewSlice_Callback.res +3 -1
  92. package/src/components/Task/Task.res +3 -3
  93. package/src/components/Task/Task_Adapter.res +4 -1
  94. package/src/components/Task/Task_Builder.res +24 -21
  95. package/src/plugin/api/PluginBaseFragment.res +9 -3
  96. package/src/plugin/component/Capability_Inference.res +8 -10
  97. package/src/plugin/component/Plugin.res +6 -6
  98. package/src/plugin/component/Plugin_Builder.res +54 -50
  99. package/src/plugin/component/Plugin_Callback.res +10 -6
  100. package/src/plugin/component/Plugin_Helpers.res +264 -230
  101. package/src/plugin/component/Plugin_Structure.res +370 -324
  102. package/src/plugin/component/Plugin_SubscriptionSchema.res +8 -6
  103. package/src/plugin/component/SchemaWalker.res +11 -12
  104. package/src/plugin/connect/PluginConnectExtension_Mapping.res +45 -50
  105. package/src/plugin/lifecycle/PluginBehavior.res +3 -4
  106. package/src/plugin/lifecycle/PluginSpec.res +1 -2
  107. package/src/plugin/lifecycle/PluginsProjection.res +16 -5
  108. package/src/plugin/lifecycle/PluginsReadModelSpec.res +0 -2
  109. package/src/util/CommandPublisher.res +9 -2
  110. package/src/util/CsvStream.res +3 -3
  111. package/src/util/Interstack.res +7 -15
  112. package/src/util/Interstack.resi +3 -3
  113. package/src/util/LogFormat.res +5 -9
  114. package/src/util/Logger.res +1 -2
  115. package/src/util/Lru.res +2 -1
  116. package/src/util/Lru.resi +2 -3
  117. package/src/util/ScheduleOps.res +24 -27
  118. package/src/util/StorePrefixCollision.res +6 -9
  119. package/src/util/Util_Promise.res +4 -1
  120. package/src/util/Util_Promise.res.mjs +1 -1
  121. package/tests/IdentityTest.res +166 -113
  122. package/tests/ResourceAttributionTest.res +28 -25
  123. package/tests/StorePrefixCollisionTest.res +2 -1
  124. package/tests/TaskBucketNamingTest.res +3 -2
  125. package/tests/adapter/EventLogProvisioningTest.res +51 -48
  126. package/tests/adapter/MonitoringTest.res +81 -38
  127. package/tests/adapter/QueryInterceptionTest.res +2 -2
  128. package/tests/adapter/RefusalVocabularyTest.res +31 -23
  129. package/tests/adapter/RuntimeExtensionHookReachTest.res +9 -8
  130. package/tests/adapter/RuntimeExtensionTest.res +5 -3
  131. package/tests/admin/AdminApiSchemaDriftTest.res +22 -16
  132. package/tests/admin/Platform_Admin_StructureTest.res +10 -11
  133. package/tests/admin/Platform_BakedManifestTest.res +31 -35
  134. package/tests/admin/Platform_ComponentDefinitionsApiTest.res +18 -15
  135. package/tests/admin/Platform_PluginStructuresApiTest.res +5 -3
  136. package/tests/admin/Platform_UIFragmentsApiTest.res +6 -3
  137. package/tests/aggregate/AggregateCacheTest.res +10 -12
  138. package/tests/aggregate/AggregateCallbackTest.res +75 -46
  139. package/tests/aggregate/AggregateCausationTest.res +19 -20
  140. package/tests/aggregate/AggregateConflictTest.res +41 -19
  141. package/tests/aggregate/AggregateFixtures.res +15 -5
  142. package/tests/aggregate/AggregateRejectionTest.res +97 -80
  143. package/tests/aggregate/AggregateSnapshotTest.res +44 -16
  144. package/tests/api/Api_IdsTest.res +10 -4
  145. package/tests/api/BrandedMarkerCompositionTest.res +48 -36
  146. package/tests/api/GeolocationSemanticTest.res +3 -3
  147. package/tests/api/GraphQL_FragmentGeneratorTest.res +66 -54
  148. package/tests/api/OwnerScopeDiagnosticsTest.res +31 -27
  149. package/tests/api/StorageRefFieldsTest.res +30 -18
  150. package/tests/api/SuryToJsonSchemaTest.res +1338 -981
  151. package/tests/api/SuryToJsonSchemaTest.res.mjs +31 -0
  152. package/tests/api/TaggedUnionSchemaTest.res +17 -24
  153. package/tests/commandgenerator/CommandGeneratorCallbackTest.res +235 -179
  154. package/tests/commandgenerator/CommandGeneratorFixtures.res +8 -13
  155. package/tests/commandgenerator/OwnerStampingTest.res +91 -76
  156. package/tests/commandtopic/CommandOutcomeHookTest.res +198 -155
  157. package/tests/commandtopic/CommandTopicCallbackFixtures.res +5 -4
  158. package/tests/commandtopic/CommandTopicCallbackTest.res +61 -43
  159. package/tests/commandtopic/CommandTopicHelpersRejectionTest.res +70 -55
  160. package/tests/counter/CounterCallbackTest.res +40 -34
  161. package/tests/counter/CounterFixtures.res +4 -4
  162. package/tests/dcb/DcbDecodeTest.res +115 -84
  163. package/tests/dcb/DcbEventLogOperationsTest.res +194 -143
  164. package/tests/dcb/DcbEventLogSchemaTest.res +13 -13
  165. package/tests/dcb/DcbFixtures.res +4 -5
  166. package/tests/dcb/DcbScopeInferenceTest.res +173 -114
  167. package/tests/dcb/DcbStateChangeSliceTest.res +391 -262
  168. package/tests/dcb/DcbTagTest.res +22 -11
  169. package/tests/dcb/DcbValidationTest.res +288 -218
  170. package/tests/eventlog/EventLogFixtures.res +4 -4
  171. package/tests/eventlog/EventLogOperationsTest.res +102 -67
  172. package/tests/eventlog/EventLogRetryTest.res +104 -65
  173. package/tests/eventmapper/EventMapperCallbackTest.res +118 -83
  174. package/tests/eventmapper/EventMapperFixtures.res +15 -23
  175. package/tests/eventmapper/EventMapperTest.res +54 -38
  176. package/tests/eventtopic/EventTopicFixtures.res +3 -4
  177. package/tests/eventtopic/EventTopicOperationsTest.res +41 -29
  178. package/tests/extensionpoint/ExtensionPointCallbackTest.res +58 -45
  179. package/tests/extensionpoint/ExtensionPointFixtures.res +16 -20
  180. package/tests/extensionpoint/ExtensionPointOperationsTest.res +70 -49
  181. package/tests/extensionpoint/PluginDisconnectScheduleTest.res +2 -1
  182. package/tests/logger/LogFormatTest.res +104 -82
  183. package/tests/logger/LoggerLazyTest.res +21 -12
  184. package/tests/message/MessageTest.res +96 -93
  185. package/tests/message/MetaEnvelopeTest.res +26 -30
  186. package/tests/plugin/HeartbeatDisconnectGraceTest.res +31 -25
  187. package/tests/plugin/ManifestVisibilityTest.res +20 -12
  188. package/tests/plugin/PluginDefinitionScalars.res +1 -2
  189. package/tests/plugin/PluginLifecycleCorpusTest.res +15 -12
  190. package/tests/plugin/PluginSpecExperiment.res +0 -1
  191. package/tests/plugin/PluginStructureTest.res +1533 -1030
  192. package/tests/plugin/PluginStructureTest.res.mjs +6 -0
  193. package/tests/plugin/Plugin_EventQuerySchemaTest.res +21 -10
  194. package/tests/plugin/SchemaWalkerTest.res +116 -70
  195. package/tests/plugin/StateChange/PsReserveStock.res +4 -4
  196. package/tests/plugin/StateView/PsAnnotatedView.res +6 -4
  197. package/tests/plugin/StateView/PsAnnotatedView.res.mjs +7 -3
  198. package/tests/plugin/StateView/PsCustomersView.res +4 -4
  199. package/tests/plugin/StateView/PsShipmentsView.res +3 -2
  200. package/tests/projection/LifecycleTrailTest.res +1 -4
  201. package/tests/projection/ProjectionOptimizeTest.res +13 -9
  202. package/tests/querydb/QueryDbFixtures.res +0 -1
  203. package/tests/querydb/QueryDbOperationsTest.res +99 -64
  204. package/tests/querydb/TaggedUnionStampTest.res +10 -8
  205. package/tests/sideeffecthandler/SideEffectHandlerCallbackTest.res +50 -35
  206. package/tests/sideeffecthandler/SideEffectHandlerFixtures.res +4 -10
  207. package/tests/util/CommandPublisherTest.res +0 -1
  208. package/tests/util/CsvStreamTest.res +3 -3
  209. 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
- typeDefFor(unnamed, "type Ordering_TuCase ")->String.includes("verdict: String!"),
144
- )->toBe(true)
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(m =>
173
- objAt(m, "properties")
174
- ->Option.flatMap(JSON.Decode.object)
175
- ->Option.flatMap(p => p->Dict.get("TAG"))
176
- ->Option.flatMap(t => objAt(t, "const"))
177
- ->Option.flatMap(JSON.Decode.string)
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(m =>
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("publishJsons called with plain string commandJson", async () => {
9
- let payload = makeZeroParamPayload(~id="agg-1", ~command="Create")
10
- let _msgId = await TestGenerator.generateCommand(payload)->Effect.runPromise
11
- let publishedCmd = capturedCmds.contents->Array.getUnsafe(0)
12
- // Zero-param command serializes as a plain JSON string
13
- expect(publishedCmd.commandJson)->toEqual(JSON.Encode.string("Create"))
14
- })
15
-
16
- testPromise("returns a Pending outcome with non-empty msgId", async () => {
17
- let payload = makeZeroParamPayload(~id="agg-1", ~command="Create")
18
- let outcome = await TestGenerator.generateCommand(payload)->Effect.runPromise
19
- let msgId = switch outcome {
20
- | CommandTopic.Pending({msgId}) => msgId
21
- | _ => ""
22
- }
23
- expect(msgId->String.length > 0)->toBe(true)
24
- })
25
-
26
- testPromise("meta.service equals AggregateSpec.name", async () => {
27
- let payload = makeZeroParamPayload(~id="agg-1", ~command="Create")
28
- let _outcome = await TestGenerator.generateCommand(payload)->Effect.runPromise
29
- let publishedCmd = capturedCmds.contents->Array.getUnsafe(0)
30
- expect(publishedCmd.meta.service)->toBe(CmdGenAggSpec.name)
31
- })
32
-
33
- testPromise("meta.msgId equals meta.correlationId", async () => {
34
- let payload = makeZeroParamPayload(~id="agg-1", ~command="Create")
35
- let outcome = await TestGenerator.generateCommand(payload)->Effect.runPromise
36
- let publishedCmd = capturedCmds.contents->Array.getUnsafe(0)
37
- let msgId = switch outcome {
38
- | CommandTopic.Pending({msgId}) => msgId
39
- | _ => ""
40
- }
41
- expect((publishedCmd.meta.msgId, publishedCmd.meta.correlationId))->toEqual((msgId, msgId))
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("publishJsons called with object commandJson {TAG, param}", async () => {
47
- let payload =
48
- makeOneParamPayload(~id="agg-1", ~command="CreateWithName", ~paramName="name", ~paramValue="Widget")
49
- let _msgId = await TestGenerator.generateCommand(payload)->Effect.runPromise
50
- let publishedCmd = capturedCmds.contents->Array.getUnsafe(0)
51
- // Multi-param command serializes as {TAG: "CreateWithName", name: "Widget"}
52
- let cmdObj = publishedCmd.commandJson->JSON.Decode.object->Option.getOrThrow
53
- expect(cmdObj->Dict.get("TAG"))->toEqual(Some(JSON.Encode.string("CreateWithName")))
54
- expect(cmdObj->Dict.get("name"))->toEqual(Some(JSON.Encode.string("Widget")))
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("invalid command name throws an error", async () => {
60
- let payload = makeZeroParamPayload(~id="agg-1", ~command="NonExistentCommand")
61
- let threw = ref(false)
62
- switch await TestGenerator.generateCommand(payload)->Effect.runPromise {
63
- | _ => ()
64
- | exception _ => threw := true
65
- }
66
- expect(threw.contents)->toBe(true)
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("single @partitionTag: id derived from tagged field when args have no id", async () => {
72
- let payload = makeSlicePayload(
73
- ~command="CreateItem",
74
- ~args=Dict.fromArray([
75
- ("itemId", JSON.Encode.string("item-42")),
76
- ("name", JSON.Encode.string("Widget")),
77
- ]),
78
- )
79
- let outcome = await singleTagSliceGen(payload)->Effect.runPromise
80
- let publishedCmd = capturedCmds.contents->Array.getUnsafe(0)
81
- expect(publishedCmd.id)->toBe("item-42")
82
- // sanity: outcome is well-formed
83
- let msgId = switch outcome {
84
- | CommandTopic.Pending({msgId}) => msgId
85
- | _ => ""
86
- }
87
- expect(msgId->String.length > 0)->toBe(true)
88
- })
89
-
90
- testPromise("composite @compositePartitionTag: id is joined partition-key value", async () => {
91
- let payload = makeSlicePayload(
92
- ~command="DeployVersion",
93
- ~args=Dict.fromArray([
94
- ("environment", JSON.Encode.string("prod")),
95
- ("service", JSON.Encode.string("checkout")),
96
- ("version", JSON.Encode.string("1.2.3")),
97
- ]),
98
- )
99
- let _outcome = await compositeTagSliceGen(payload)->Effect.runPromise
100
- let publishedCmd = capturedCmds.contents->Array.getUnsafe(0)
101
- // Composite key joins position-0 value, then sep[0] = "-", then position-1 value
102
- expect(publishedCmd.id)->toBe("prod-checkout")
103
- })
104
-
105
- testPromise("explicit id in args wins over derivation (regression check)", async () => {
106
- let payload = makeSlicePayload(
107
- ~command="CreateItem",
108
- ~args=Dict.fromArray([
109
- ("id", JSON.Encode.string("explicit-id")),
110
- ("itemId", JSON.Encode.string("item-99")),
111
- ("name", JSON.Encode.string("Gadget")),
112
- ]),
113
- )
114
- let _outcome = await singleTagSliceGen(payload)->Effect.runPromise
115
- let publishedCmd = capturedCmds.contents->Array.getUnsafe(0)
116
- expect(publishedCmd.id)->toBe("explicit-id")
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("explicit id in args is preserved unchanged", async () => {
122
- let payload = makeZeroParamPayload(~id="agg-7", ~command="Create")
123
- let _outcome = await TestGenerator.generateCommand(payload)->Effect.runPromise
124
- let publishedCmd = capturedCmds.contents->Array.getUnsafe(0)
125
- expect(publishedCmd.id)->toBe("agg-7")
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("is dropped, and the command decodes and publishes", async () => {
141
- let payload = storeItem(
142
- ~args=Dict.fromArray([
143
- ("itemId", JSON.Encode.string("item-1")),
144
- ("data", data),
145
- ("note", JSON.Null),
146
- ]),
147
- )
148
- let _outcome = await optionalFieldSliceGen(payload)->Effect.runPromise
149
- let publishedCmd = capturedCmds.contents->Array.getUnsafe(0)
150
- expect((capturedCmds.contents->Array.length, publishedCmd->fieldOf("note")))
151
- ->toEqual((1, None))
152
- })
153
-
154
- testPromise("carrying a value is left exactly as sent", async () => {
155
- let payload = storeItem(
156
- ~args=Dict.fromArray([
157
- ("itemId", JSON.Encode.string("item-2")),
158
- ("data", data),
159
- ("note", JSON.Encode.string("gift wrap")),
160
- ]),
161
- )
162
- let _outcome = await optionalFieldSliceGen(payload)->Effect.runPromise
163
- expect(capturedCmds.contents->Array.getUnsafe(0)->fieldOf("note"))
164
- ->toEqual(Some(JSON.Encode.string("gift wrap")))
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("and one simply omitted produce the same command", async () => {
170
- let withNull = storeItem(
171
- ~args=Dict.fromArray([
172
- ("itemId", JSON.Encode.string("item-3")),
173
- ("data", data),
174
- ("note", JSON.Null),
175
- ]),
176
- )
177
- let withoutKey = storeItem(
178
- ~args=Dict.fromArray([("itemId", JSON.Encode.string("item-3")), ("data", data)]),
179
- )
180
- let _ = await optionalFieldSliceGen(withNull)->Effect.runPromise
181
- let _ = await optionalFieldSliceGen(withoutKey)->Effect.runPromise
182
- let published = capturedCmds.contents
183
- expect((published->Array.getUnsafe(0)).commandJson)
184
- ->toEqual((published->Array.getUnsafe(1)).commandJson)
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("nested inside a value is left alone", async () => {
191
- let nested = JSON.Object(Dict.fromArray([("kind", JSON.Null)]))
192
- let payload = storeItem(
193
- ~args=Dict.fromArray([("itemId", JSON.Encode.string("item-4")), ("data", nested)]),
194
- )
195
- let _outcome = await optionalFieldSliceGen(payload)->Effect.runPromise
196
- expect(capturedCmds.contents->Array.getUnsafe(0)->fieldOf("data"))->toEqual(Some(nested))
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("is refused rather than published", async () => {
211
- let refused = switch await optionalFieldSliceGen(undecodable())->Effect.runPromise {
212
- | _ => false
213
- | exception _ => true
214
- }
215
- expect((refused, capturedCmds.contents->Array.length))->toEqual((true, 0))
216
- })
217
-
218
- testPromise("is marked as the caller's fault, naming the field", async () => {
219
- let failure = switch await optionalFieldSliceGen(undecodable())->Effect.runPromise {
220
- | _ => None
221
- | exception e => Some(e)
222
- }
223
- let marked = failure->Option.mapOr(false, Plugin_ResolverError.isCallerFault)
224
- let saysWhy =
225
- failure
226
- ->Option.flatMap(JsExn.fromException)
227
- ->Option.flatMap(JsExn.message)
228
- ->Option.mapOr(false, m => m->String.includes("data"))
229
- expect((marked, saysWhy))->toEqual((true, true))
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 // used for schema validation tests
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 = | InvalidCommand
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<array<CmdGenAggSpec.event>, CmdGenAggSpec.error> =>
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
  })