@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
@@ -1555,6 +1555,12 @@ globalThis.describe("Plugin_Structure.make — Phase 2 graph fields", () => {
1555
1555
  globalThis.test("@sensitive reaches an event variant's payload, not only state", () => {
1556
1556
  globalThis.expect(Sensitive$Reventless.variantFieldNames(PsAnnotatedView$ReventlessCore.consumedEventSchema, "ItemRecorded")).toEqual(["contact"]);
1557
1557
  });
1558
+ globalThis.test("@default flows through the PPX to JSON Schema's `default`", () => {
1559
+ globalThis.expect(Stdlib_Option.flatMap(getPropertyOf(annotatedSchema, "quantity"), s => getProperty(s, "default"))).toEqual(1);
1560
+ });
1561
+ globalThis.test("@default leaves the field's own shape alone", () => {
1562
+ globalThis.expect(Stdlib_Option.flatMap(Stdlib_Option.flatMap(getPropertyOf(annotatedSchema, "quantity"), s => getProperty(s, "type")), Stdlib_JSON.Decode.string)).toBe("integer");
1563
+ });
1558
1564
  globalThis.test("@metric flows through the PPX to x-reventless-metric {aggregate,label}", () => {
1559
1565
  let metricObj = Stdlib_Option.flatMap(getPropertyOf(annotatedSchema, "total"), s => getProperty(s, "x-reventless-metric"));
1560
1566
  globalThis.expect([
@@ -52,13 +52,13 @@ describe("Plugin_EventQuerySchema.generate", () => {
52
52
  ),
53
53
  )
54
54
  expect(sdl->String.includes("type EventMeta {"))->toBe(true)
55
- ["user", "time", "service", "msgId", "correlationId", "causationId"]->Array.forEach(f =>
56
- expect(sdl->String.includes(f))->toBe(true)
55
+ ["user", "time", "service", "msgId", "correlationId", "causationId"]->Array.forEach(
56
+ f => expect(sdl->String.includes(f))->toBe(true),
57
57
  )
58
58
  // Transport / tracing detail and the cross-cutting context bag must not
59
59
  // leave the server — see the module comment.
60
- ["ip:", "traceparent", "schemaVersion", "headers"]->Array.forEach(f =>
61
- expect(sdl->String.includes(f))->toBe(false)
60
+ ["ip:", "traceparent", "schemaVersion", "headers"]->Array.forEach(
61
+ f => expect(sdl->String.includes(f))->toBe(false),
62
62
  )
63
63
  })
64
64
 
@@ -70,9 +70,14 @@ describe("Plugin_EventQuerySchema.generate", () => {
70
70
  ),
71
71
  )
72
72
  expect(sdl->String.includes("type Ordering_OrderEventRecord {"))->toBe(true)
73
- ["position: String!", "eventType: String!", "payload: AWSJSON!", "tags: [EventTag!]!", "meta: EventMeta!", "recordedAt: String!"]->Array.forEach(
74
- f => expect(sdl->String.includes(f))->toBe(true),
75
- )
73
+ [
74
+ "position: String!",
75
+ "eventType: String!",
76
+ "payload: AWSJSON!",
77
+ "tags: [EventTag!]!",
78
+ "meta: EventMeta!",
79
+ "recordedAt: String!",
80
+ ]->Array.forEach(f => expect(sdl->String.includes(f))->toBe(true))
76
81
  })
77
82
 
78
83
  testSync("filter offers the entity shortcut and the precise tag pair", () => {
@@ -83,9 +88,15 @@ describe("Plugin_EventQuerySchema.generate", () => {
83
88
  ),
84
89
  )
85
90
  expect(sdl->String.includes("input Ordering_OrderEventHistoryFilter {"))->toBe(true)
86
- ["entityId: ID", "tagKey: String", "tagValue: String", "eventTypes: [String!]", "user: String", "timeFrom: String", "timeTo: String"]->Array.forEach(
87
- f => expect(sdl->String.includes(f))->toBe(true),
88
- )
91
+ [
92
+ "entityId: ID",
93
+ "tagKey: String",
94
+ "tagValue: String",
95
+ "eventTypes: [String!]",
96
+ "user: String",
97
+ "timeFrom: String",
98
+ "timeTo: String",
99
+ ]->Array.forEach(f => expect(sdl->String.includes(f))->toBe(true))
89
100
  })
90
101
 
91
102
  testSync("a plugin with no event logs emits nothing at all", () => {
@@ -1,6 +1,5 @@
1
1
  open JestGlobals
2
2
 
3
-
4
3
  @schema
5
4
  type simpleRecord = {
6
5
  label: string,
@@ -21,28 +20,41 @@ describe("SchemaWalker:", () => {
21
20
  testSync("kind is record", () => expect(result.kind)->toBe("record"))
22
21
  testSync("constructors is absent", () => expect(result.constructors)->toEqual(None))
23
22
 
24
- testSync("fields are sorted alphabetically", () => {
25
- let names = result.fields->Array.map(f => f.name)
26
- expect(names)->toEqual(["count", "label", "rating"])
27
- })
23
+ testSync(
24
+ "fields are sorted alphabetically",
25
+ () => {
26
+ let names = result.fields->Array.map(f => f.name)
27
+ expect(names)->toEqual(["count", "label", "rating"])
28
+ },
29
+ )
28
30
 
29
- testSync("required string field", () => {
30
- let f = result.fields->Array.getUnsafe(1)
31
- expect((f.typeDescription, f.isRequired))->toEqual(("string", true))
32
- })
31
+ testSync(
32
+ "required string field",
33
+ () => {
34
+ let f = result.fields->Array.getUnsafe(1)
35
+ expect((f.typeDescription, f.isRequired))->toEqual(("string", true))
36
+ },
37
+ )
33
38
 
34
- testSync("required int field typeDescription is number", () => {
35
- let f = result.fields->Array.getUnsafe(0)
36
- expect((f.typeDescription, f.isRequired))->toEqual(("number", true))
37
- })
39
+ testSync(
40
+ "required int field typeDescription is number",
41
+ () => {
42
+ let f = result.fields->Array.getUnsafe(0)
43
+ expect((f.typeDescription, f.isRequired))->toEqual(("number", true))
44
+ },
45
+ )
38
46
 
39
- testSync("optional float field", () => {
40
- let f = result.fields->Array.getUnsafe(2)
41
- expect((f.typeDescription, f.isRequired))->toEqual(("option<number>", false))
42
- })
47
+ testSync(
48
+ "optional float field",
49
+ () => {
50
+ let f = result.fields->Array.getUnsafe(2)
51
+ expect((f.typeDescription, f.isRequired))->toEqual(("option<number>", false))
52
+ },
53
+ )
43
54
 
44
- testSync("structuralHash is non-empty", () =>
45
- expect(result.structuralHash->String.length > 0)->toBe(true)
55
+ testSync(
56
+ "structuralHash is non-empty",
57
+ () => expect(result.structuralHash->String.length > 0)->toBe(true),
46
58
  )
47
59
  })
48
60
 
@@ -52,63 +64,97 @@ describe("SchemaWalker:", () => {
52
64
  testSync("kind is variant", () => expect(result.kind)->toBe("variant"))
53
65
  testSync("fields array is empty", () => expect(result.fields)->toEqual([]))
54
66
 
55
- testSync("payload-less constructor Removed is excluded", () => {
56
- let ctors = result.constructors->Option.getOr([])
57
- let names = ctors->Array.map(c => c.name)->Array.toSorted(String.compare)
58
- expect(names)->toEqual(["Added", "Renamed"])
59
- })
60
-
61
- testSync("Added constructor has sorted fields", () => {
62
- let ctors = result.constructors->Option.getOr([])
63
- let added = ctors->Array.find(c => c.name == "Added")->Option.getOr({Plugin_BuiltHook.name: "", fields: []})
64
- expect(added.fields->Array.map(f => f.name))->toEqual(["id", "name"])
65
- })
66
-
67
- testSync("Renamed constructor has sorted fields", () => {
68
- let ctors = result.constructors->Option.getOr([])
69
- let renamed = ctors->Array.find(c => c.name == "Renamed")->Option.getOr({Plugin_BuiltHook.name: "", fields: []})
70
- expect(renamed.fields->Array.map(f => f.name))->toEqual(["id", "newName"])
71
- })
72
-
73
- testSync("structuralHash is non-empty", () =>
74
- expect(result.structuralHash->String.length > 0)->toBe(true)
67
+ testSync(
68
+ "payload-less constructor Removed is excluded",
69
+ () => {
70
+ let ctors = result.constructors->Option.getOr([])
71
+ let names = ctors->Array.map(c => c.name)->Array.toSorted(String.compare)
72
+ expect(names)->toEqual(["Added", "Renamed"])
73
+ },
74
+ )
75
+
76
+ testSync(
77
+ "Added constructor has sorted fields",
78
+ () => {
79
+ let ctors = result.constructors->Option.getOr([])
80
+ let added =
81
+ ctors
82
+ ->Array.find(c => c.name == "Added")
83
+ ->Option.getOr({Plugin_BuiltHook.name: "", fields: []})
84
+ expect(added.fields->Array.map(f => f.name))->toEqual(["id", "name"])
85
+ },
86
+ )
87
+
88
+ testSync(
89
+ "Renamed constructor has sorted fields",
90
+ () => {
91
+ let ctors = result.constructors->Option.getOr([])
92
+ let renamed =
93
+ ctors
94
+ ->Array.find(c => c.name == "Renamed")
95
+ ->Option.getOr({Plugin_BuiltHook.name: "", fields: []})
96
+ expect(renamed.fields->Array.map(f => f.name))->toEqual(["id", "newName"])
97
+ },
98
+ )
99
+
100
+ testSync(
101
+ "structuralHash is non-empty",
102
+ () => expect(result.structuralHash->String.length > 0)->toBe(true),
75
103
  )
76
104
  })
77
105
 
78
106
  describe("hash properties", () => {
79
- testSync("same schema produces identical hash on repeated calls", () => {
80
- let h1 = SchemaWalker.walk("X", simpleRecordSchema).structuralHash
81
- let h2 = SchemaWalker.walk("X", simpleRecordSchema).structuralHash
82
- expect(h1)->toBe(h2)
83
- })
84
-
85
- testSync("typeName does not affect structuralHash", () => {
86
- let h1 = SchemaWalker.walk("TypeA", simpleRecordSchema).structuralHash
87
- let h2 = SchemaWalker.walk("TypeB", simpleRecordSchema).structuralHash
88
- expect(h1)->toBe(h2)
89
- })
90
-
91
- testSync("structurally distinct schemas produce different hashes", () => {
92
- let h1 = SchemaWalker.walk("R", simpleRecordSchema).structuralHash
93
- let h2 = SchemaWalker.walk("V", simpleVariantSchema).structuralHash
94
- expect(h1 == h2)->toBe(false)
95
- })
107
+ testSync(
108
+ "same schema produces identical hash on repeated calls",
109
+ () => {
110
+ let h1 = SchemaWalker.walk("X", simpleRecordSchema).structuralHash
111
+ let h2 = SchemaWalker.walk("X", simpleRecordSchema).structuralHash
112
+ expect(h1)->toBe(h2)
113
+ },
114
+ )
115
+
116
+ testSync(
117
+ "typeName does not affect structuralHash",
118
+ () => {
119
+ let h1 = SchemaWalker.walk("TypeA", simpleRecordSchema).structuralHash
120
+ let h2 = SchemaWalker.walk("TypeB", simpleRecordSchema).structuralHash
121
+ expect(h1)->toBe(h2)
122
+ },
123
+ )
124
+
125
+ testSync(
126
+ "structurally distinct schemas produce different hashes",
127
+ () => {
128
+ let h1 = SchemaWalker.walk("R", simpleRecordSchema).structuralHash
129
+ let h2 = SchemaWalker.walk("V", simpleVariantSchema).structuralHash
130
+ expect(h1 == h2)->toBe(false)
131
+ },
132
+ )
96
133
  })
97
134
 
98
135
  describe("scalar / unsupported schema", () => {
99
- testSync("plain string schema → kind unknown", () => {
100
- let result = SchemaWalker.walk("Scalar", S.string)
101
- expect(result.kind)->toBe("unknown")
102
- })
103
-
104
- testSync("plain string schema → empty fields", () => {
105
- let result = SchemaWalker.walk("Scalar", S.string)
106
- expect(result.fields)->toEqual([])
107
- })
108
-
109
- testSync("plain string schema → empty structuralHash", () => {
110
- let result = SchemaWalker.walk("Scalar", S.string)
111
- expect(result.structuralHash)->toBe("")
112
- })
136
+ testSync(
137
+ "plain string schema → kind unknown",
138
+ () => {
139
+ let result = SchemaWalker.walk("Scalar", S.string)
140
+ expect(result.kind)->toBe("unknown")
141
+ },
142
+ )
143
+
144
+ testSync(
145
+ "plain string schema → empty fields",
146
+ () => {
147
+ let result = SchemaWalker.walk("Scalar", S.string)
148
+ expect(result.fields)->toEqual([])
149
+ },
150
+ )
151
+
152
+ testSync(
153
+ "plain string schema → empty structuralHash",
154
+ () => {
155
+ let result = SchemaWalker.walk("Scalar", S.string)
156
+ expect(result.structuralHash)->toBe("")
157
+ },
158
+ )
113
159
  })
114
160
  })
@@ -49,9 +49,9 @@ type error = OutOfStock
49
49
 
50
50
  @schema
51
51
  type event =
52
- StockReserved({
53
- @partitionTag reservationId: string,
54
- @ref("AvailableProducts") productIds: array<string>,
55
- })
52
+ | StockReserved({
53
+ @partitionTag reservationId: string,
54
+ @ref("AvailableProducts") productIds: array<string>,
55
+ })
56
56
 
57
57
  let decide = (_state, _command): result<array<event>, error> => Ok([])
@@ -16,10 +16,10 @@ type consumedEvent =
16
16
  // On the event, not just the state: a renderer composes from an event
17
17
  // payload, so this is the case the marker has to reach.
18
18
  @sensitive contact: string,
19
+ @default(1) quantity: int,
19
20
  })
20
21
 
21
- @live(false)
22
- @schema
22
+ @live(false) @schema
23
23
  type state = {
24
24
  @id itemId: string,
25
25
  @subId version: string,
@@ -27,10 +27,12 @@ type state = {
27
27
  name: string,
28
28
  @semantic("currency") @metric({aggregate: "sum", label: "Revenue"}) total: float,
29
29
  @sensitive contact: string,
30
+ @default(1) quantity: int,
30
31
  }
31
32
 
32
33
  let project = ({event}: Reventless.StateViewSlice.consumed<consumedEvent>) =>
33
34
  switch event {
34
- | ItemRecorded({itemId, ownerId, version, name, total, contact}) =>
35
- [Set(itemId, {itemId, ownerId, version, name, total, contact})]
35
+ | ItemRecorded({itemId, ownerId, version, name, total, contact, quantity}) => [
36
+ Set(itemId, {itemId, ownerId, version, name, total, contact, quantity}),
37
+ ]
36
38
  }
@@ -4,6 +4,7 @@ import * as Sury from "sury";
4
4
  import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/components/DcbTag.res.mjs";
5
5
  import * as ReadModel$Reventless from "@reventlessdev/reventless-spec/src/components/ReadModel.res.mjs";
6
6
  import * as Sensitive$Reventless from "@reventlessdev/reventless-spec/src/components/Sensitive.res.mjs";
7
+ import * as FieldDefault$Reventless from "@reventlessdev/reventless-spec/src/components/FieldDefault.res.mjs";
7
8
  import * as StateAnnotations$Reventless from "@reventlessdev/reventless-spec/src/components/StateAnnotations.res.mjs";
8
9
 
9
10
  let consumedEventSchema = Sury.$schema(s => ({
@@ -13,7 +14,8 @@ let consumedEventSchema = Sury.$schema(s => ({
13
14
  version: s.m(Sury.string),
14
15
  name: s.m(Sury.string),
15
16
  total: s.m(Sury.float),
16
- contact: s.m(Sensitive$Reventless.string)
17
+ contact: s.m(Sensitive$Reventless.string),
18
+ quantity: s.m(FieldDefault$Reventless.int(Sury.int, 1))
17
19
  }));
18
20
 
19
21
  let stateSchema = Sury.$schema(s => ({
@@ -22,7 +24,8 @@ let stateSchema = Sury.$schema(s => ({
22
24
  ownerId: s.m(Sury.string),
23
25
  name: s.m(Sury.string),
24
26
  total: s.m(Sury.float),
25
- contact: s.m(Sensitive$Reventless.string)
27
+ contact: s.m(Sensitive$Reventless.string),
28
+ quantity: s.m(FieldDefault$Reventless.int(Sury.int, 1))
26
29
  }));
27
30
 
28
31
  function project(param) {
@@ -37,7 +40,8 @@ function project(param) {
37
40
  ownerId: event.ownerId,
38
41
  name: event.name,
39
42
  total: event.total,
40
- contact: event.contact
43
+ contact: event.contact,
44
+ quantity: event.quantity
41
45
  }
42
46
  }];
43
47
  }
@@ -5,8 +5,7 @@
5
5
  @@reventless.spec("Customers")
6
6
 
7
7
  @schema
8
- type consumedEvent =
9
- | CustomerAdded({customerId: string, firstName: string, lastName: string})
8
+ type consumedEvent = CustomerAdded({customerId: string, firstName: string, lastName: string})
10
9
 
11
10
  @schema
12
11
  type state = {
@@ -17,6 +16,7 @@ type state = {
17
16
 
18
17
  let project = ({event}: Reventless.StateViewSlice.consumed<consumedEvent>) =>
19
18
  switch event {
20
- | CustomerAdded({customerId, firstName, lastName}) =>
21
- [Set(customerId, {customerId, firstName, lastName})]
19
+ | CustomerAdded({customerId, firstName, lastName}) => [
20
+ Set(customerId, {customerId, firstName, lastName}),
21
+ ]
22
22
  }
@@ -21,6 +21,7 @@ type state = {shipmentId: string, @lifecycle shipmentStatus: shipmentStatus}
21
21
  let project = ({event}: Reventless.StateViewSlice.consumed<consumedEvent>) =>
22
22
  switch event {
23
23
  | ShipmentBooked({shipmentId}) => [Set(shipmentId, {shipmentId, shipmentStatus: Booked})]
24
- | ShipmentDispatched({shipmentId}) =>
25
- [Update(shipmentId, s => {...s, shipmentStatus: Dispatched})]
24
+ | ShipmentDispatched({shipmentId}) => [
25
+ Update(shipmentId, s => {...s, shipmentStatus: Dispatched}),
26
+ ]
26
27
  }
@@ -87,10 +87,7 @@ describe("the lifecycle trail:", () => {
87
87
  // Derived from the log and the envelope's own time, so the same events in the
88
88
  // same order produce the same trail however often they are replayed.
89
89
  testSync("a rebuild from the same events produces an identical trail", () => {
90
- let events = [
91
- (nine, Set("o1", placed)),
92
- (ten, Update("o1", s => {...s, lifecycle: Shipped})),
93
- ]
90
+ let events = [(nine, Set("o1", placed)), (ten, Update("o1", s => {...s, lifecycle: Shipped}))]
94
91
  expect(run(events)->statesOf)->toEqual(run(events)->statesOf)
95
92
  })
96
93
 
@@ -14,25 +14,29 @@ describe("Projection.optimizeActions", () => {
14
14
  let optimized = Projection.optimizeActions([Delete("a"), Update("a", x => x + 1), Set("a", 99)])
15
15
  // Was 3 (with a duplicated Update("a")) before the fix.
16
16
  expect(optimized->Array.length)->toBe(2)
17
- let sets =
18
- optimized->Array.filterMap(a =>
17
+ let sets = optimized->Array.filterMap(
18
+ a =>
19
19
  switch a {
20
20
  | Set(id, s) => Some((id, s))
21
21
  | _ => None
22
- }
23
- )
22
+ },
23
+ )
24
24
  expect(sets)->toEqual([("a", 99)])
25
25
  })
26
26
 
27
27
  testSync("merges a same-id Create+Update chain into a single Create", () => {
28
- let optimized = Projection.optimizeActions([Create("a", 0), Update("a", x => x + 1), Update("a", x => x + 10)])
29
- let creates =
30
- optimized->Array.filterMap(a =>
28
+ let optimized = Projection.optimizeActions([
29
+ Create("a", 0),
30
+ Update("a", x => x + 1),
31
+ Update("a", x => x + 10),
32
+ ])
33
+ let creates = optimized->Array.filterMap(
34
+ a =>
31
35
  switch a {
32
36
  | Create(id, s) => Some((id, s))
33
37
  | _ => None
34
- }
35
- )
38
+ },
39
+ )
36
40
  expect(creates)->toEqual([("a", 11)])
37
41
  })
38
42
  })
@@ -1,4 +1,3 @@
1
-
2
1
  // ─────────────────────────────────────────────────────────────
3
2
  // Test state spec
4
3
  // ─────────────────────────────────────────────────────────────
@@ -12,83 +12,118 @@ let _ = beforeEach(() => reset())
12
12
 
13
13
  describe("QueryDb_Operations:", () => {
14
14
  describe("loadStream", () => {
15
- testPromise("returns empty stream for unknown id", async () => {
16
- let result = await Ops.loadStream("unknown-id")->Stream.runCollect->Effect.runPromise
17
- expect(result)->toEqual([])
18
- })
15
+ testPromise(
16
+ "returns empty stream for unknown id",
17
+ async () => {
18
+ let result = await Ops.loadStream("unknown-id")->Stream.runCollect->Effect.runPromise
19
+ expect(result)->toEqual([])
20
+ },
21
+ )
19
22
 
20
- testPromise("decodes saved state back to typed state", async () => {
21
- let state: ItemQueryDbSpec.state = {name: "Widget", count: 5}
22
- let _ = await Ops.save("item-1", state, Init, None)
23
- let result = await Ops.loadStream("item-1")->Stream.runCollect->Effect.runPromise
24
- switch result {
25
- | [s] => expect((s.name, s.count))->toEqual(("Widget", 5))
26
- | _ => expect("unexpected result")->toEqual("Ok([state])")
27
- }
28
- })
23
+ testPromise(
24
+ "decodes saved state back to typed state",
25
+ async () => {
26
+ let state: ItemQueryDbSpec.state = {name: "Widget", count: 5}
27
+ let _ = await Ops.save("item-1", state, Init, None)
28
+ let result = await Ops.loadStream("item-1")->Stream.runCollect->Effect.runPromise
29
+ switch result {
30
+ | [s] => expect((s.name, s.count))->toEqual(("Widget", 5))
31
+ | _ => expect("unexpected result")->toEqual("Ok([state])")
32
+ }
33
+ },
34
+ )
29
35
 
30
- testPromise("returns updated state after overwrite", async () => {
31
- let _ = await Ops.save("item-1", {name: "Widget", count: 1}, Init, None)
32
- let _ = await Ops.save("item-1", {name: "Widget Updated", count: 2}, Overwrite, None)
33
- let result = await Ops.loadStream("item-1")->Stream.runCollect->Effect.runPromise
34
- switch result {
35
- | [s] => expect(s.name)->toBe("Widget Updated")
36
- | _ => expect("unexpected result")->toEqual("Ok([state])")
37
- }
38
- })
36
+ testPromise(
37
+ "returns updated state after overwrite",
38
+ async () => {
39
+ let _ = await Ops.save("item-1", {name: "Widget", count: 1}, Init, None)
40
+ let _ = await Ops.save("item-1", {name: "Widget Updated", count: 2}, Overwrite, None)
41
+ let result = await Ops.loadStream("item-1")->Stream.runCollect->Effect.runPromise
42
+ switch result {
43
+ | [s] => expect(s.name)->toBe("Widget Updated")
44
+ | _ => expect("unexpected result")->toEqual("Ok([state])")
45
+ }
46
+ },
47
+ )
39
48
  })
40
49
 
41
50
  describe("save", () => {
42
- testPromise("returns Ok on success", async () => {
43
- let result = await Ops.save("item-1", {name: "Widget", count: 1}, Init, None)
44
- expect(Result.isOk(result))->toBe(true)
45
- })
51
+ testPromise(
52
+ "returns Ok on success",
53
+ async () => {
54
+ let result = await Ops.save("item-1", {name: "Widget", count: 1}, Init, None)
55
+ expect(Result.isOk(result))->toBe(true)
56
+ },
57
+ )
46
58
 
47
- testPromise("stores id field in JSON", async () => {
48
- let _ = await Ops.save("item-42", {name: "Widget", count: 0}, Init, None)
49
- let stored = store.contents->Dict.get("item-42")->Option.getOr([])
50
- expect(stored->Array.length)->toBe(1)
51
- let json = stored->Array.getUnsafe(0)
52
- let idField =
53
- json
54
- ->JSON.Decode.object
55
- ->Option.flatMap(d => d->Dict.get("id"))
56
- ->Option.flatMap(j => switch j { | JSON.String(s) => Some(s) | _ => None })
57
- expect(idField)->toEqual(Some("item-42"))
58
- })
59
+ testPromise(
60
+ "stores id field in JSON",
61
+ async () => {
62
+ let _ = await Ops.save("item-42", {name: "Widget", count: 0}, Init, None)
63
+ let stored = store.contents->Dict.get("item-42")->Option.getOr([])
64
+ expect(stored->Array.length)->toBe(1)
65
+ let json = stored->Array.getUnsafe(0)
66
+ let idField =
67
+ json
68
+ ->JSON.Decode.object
69
+ ->Option.flatMap(d => d->Dict.get("id"))
70
+ ->Option.flatMap(
71
+ j =>
72
+ switch j {
73
+ | JSON.String(s) => Some(s)
74
+ | _ => None
75
+ },
76
+ )
77
+ expect(idField)->toEqual(Some("item-42"))
78
+ },
79
+ )
59
80
 
60
- testPromise("returns Error when storage fails", async () => {
61
- failNextWrite := true
62
- let result = await Ops.save("item-1", {name: "Widget", count: 1}, Init, None)
63
- expect(Result.isError(result))->toBe(true)
64
- })
81
+ testPromise(
82
+ "returns Error when storage fails",
83
+ async () => {
84
+ failNextWrite := true
85
+ let result = await Ops.save("item-1", {name: "Widget", count: 1}, Init, None)
86
+ expect(Result.isError(result))->toBe(true)
87
+ },
88
+ )
65
89
  })
66
90
 
67
91
  describe("saveBatch", () => {
68
- testPromise("saves multiple states", async () => {
69
- let batch = [
70
- ("item-1", ({name: "A", count: 1}: ItemQueryDbSpec.state), None),
71
- ("item-2", ({name: "B", count: 2}: ItemQueryDbSpec.state), None),
72
- ]
73
- let _ = await Ops.saveBatch(batch)
74
- let r1 = await Ops.loadStream("item-1")->Stream.runCollect->Effect.runPromise
75
- let r2 = await Ops.loadStream("item-2")->Stream.runCollect->Effect.runPromise
76
- expect((r1->Array.length > 0, r2->Array.length > 0))->toEqual((true, true))
77
- })
92
+ testPromise(
93
+ "saves multiple states",
94
+ async () => {
95
+ let batch = [
96
+ ("item-1", ({name: "A", count: 1}: ItemQueryDbSpec.state), None),
97
+ ("item-2", ({name: "B", count: 2}: ItemQueryDbSpec.state), None),
98
+ ]
99
+ let _ = await Ops.saveBatch(batch)
100
+ let r1 = await Ops.loadStream("item-1")->Stream.runCollect->Effect.runPromise
101
+ let r2 = await Ops.loadStream("item-2")->Stream.runCollect->Effect.runPromise
102
+ expect((r1->Array.length > 0, r2->Array.length > 0))->toEqual((true, true))
103
+ },
104
+ )
78
105
 
79
- testPromise("returns Error when storage fails", async () => {
80
- failNextWrite := true
81
- let result = await Ops.saveBatch([("item-1", ({name: "A", count: 1}: ItemQueryDbSpec.state), None)])
82
- expect(Result.isError(result))->toBe(true)
83
- })
106
+ testPromise(
107
+ "returns Error when storage fails",
108
+ async () => {
109
+ failNextWrite := true
110
+ let result = await Ops.saveBatch([
111
+ ("item-1", ({name: "A", count: 1}: ItemQueryDbSpec.state), None),
112
+ ])
113
+ expect(Result.isError(result))->toBe(true)
114
+ },
115
+ )
84
116
  })
85
117
 
86
118
  describe("delete", () => {
87
- testPromise("removes state from storage", async () => {
88
- let _ = await Ops.save("item-1", {name: "Widget", count: 1}, Init, None)
89
- let _ = await Ops.delete("item-1", None)
90
- let result = await Ops.loadStream("item-1")->Stream.runCollect->Effect.runPromise
91
- expect(result)->toEqual([])
92
- })
119
+ testPromise(
120
+ "removes state from storage",
121
+ async () => {
122
+ let _ = await Ops.save("item-1", {name: "Widget", count: 1}, Init, None)
123
+ let _ = await Ops.delete("item-1", None)
124
+ let result = await Ops.loadStream("item-1")->Stream.runCollect->Effect.runPromise
125
+ expect(result)->toEqual([])
126
+ },
127
+ )
93
128
  })
94
129
  })