@reventlessdev/reventless-local 3.0.0-alpha.249 → 3.0.0-alpha.251

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 (174) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/package.json +13 -13
  3. package/src/BakedManifest.res +5 -6
  4. package/src/EmitCapabilities.res +3 -1
  5. package/src/LocalPlatformRegistry.res +3 -1
  6. package/src/LocalPlatformStart.res +7 -6
  7. package/src/LocalSeedTarget.res +3 -1
  8. package/src/Platform.res +291 -215
  9. package/src/ShellConfig.res +10 -17
  10. package/src/UiHints.res +8 -14
  11. package/src/UiSlots.res +10 -17
  12. package/src/adapter/Api/LocalEvents_Server.res +5 -3
  13. package/src/adapter/Api/LocalGraphQL_Adapter.res +4 -4
  14. package/src/adapter/Auth/LocalAuth.res +12 -16
  15. package/src/adapter/Auth/LocalAuth.res.mjs +2 -1
  16. package/src/adapter/Auth/UserStore.res +13 -40
  17. package/src/adapter/Auth/UserStore.res.mjs +11 -67
  18. package/src/adapter/Backend.res +1 -2
  19. package/src/adapter/CommandGenerator/CommandGeneratorResolvers_GraphQL.res +42 -26
  20. package/src/adapter/CommandGenerator/InboundTranslationResolvers_GraphQL.res +13 -3
  21. package/src/adapter/CommandTopic/LocalCommandTopicChannel.res +15 -17
  22. package/src/adapter/CommandTopic/LocalCommandTopicRemoteChannel.res +18 -22
  23. package/src/adapter/Counter/LocalCounterHandler.res +1 -1
  24. package/src/adapter/DcbEventLog/DcbEventLogStorage_InMemory.res +33 -22
  25. package/src/adapter/DcbEventLog/DcbEventLogStorage_Sqlite.res +57 -57
  26. package/src/adapter/DomainGraphQL_Server.res +35 -34
  27. package/src/adapter/DomainMCP_Server.res +45 -41
  28. package/src/adapter/EventCollector/LocalEventCollectorChannel.res +47 -36
  29. package/src/adapter/EventHistory/EventHistoryResolvers_GraphQL.res +15 -12
  30. package/src/adapter/EventLog/EventLogStorage_InMemory.res +9 -4
  31. package/src/adapter/EventLog/EventLogStorage_Sqlite.res +36 -24
  32. package/src/adapter/EventTopic/LocalEventTopicPublisher.res +9 -3
  33. package/src/adapter/Heartbeat/LocalHeartbeatRunner.res +9 -14
  34. package/src/adapter/LocalBus.res +12 -12
  35. package/src/adapter/LocalCapabilities.res +6 -0
  36. package/src/adapter/LocalCapabilities.res.mjs +5 -2
  37. package/src/adapter/LocalGeocodeResolvers.res +1 -4
  38. package/src/adapter/LocalSideEffectHandler.res +1 -1
  39. package/src/adapter/LocalStateChangeDescriptor.res +2 -1
  40. package/src/adapter/MCP_ServerInstance.res +51 -39
  41. package/src/adapter/ObjectStore/LocalObjectStore.res +5 -2
  42. package/src/adapter/ObjectStore/ObjectStoreStorage_FileSystem.res +1 -2
  43. package/src/adapter/PlatformGraphQL_Server.res +10 -6
  44. package/src/adapter/PlatformGraphQL_Server.res.mjs +2 -1
  45. package/src/adapter/ProjectionCheckpoint.res +8 -1
  46. package/src/adapter/ProjectionPending.res +1 -3
  47. package/src/adapter/QueryDb/LocalQueryDbStorage.res +2 -1
  48. package/src/adapter/QueryDb/QueryDbResolvers_GraphQL.res +367 -269
  49. package/src/adapter/QueryDb/QueryDbStorage_InMemory.res +5 -3
  50. package/src/adapter/QueryDb/QueryDbStorage_Sqlite.res +63 -40
  51. package/src/adapter/QueryDb/SortKey_Filter.res +14 -6
  52. package/src/adapter/Runtime/LocalAggregateRuntime_Builder.res +12 -10
  53. package/src/adapter/Runtime/LocalEventCollectorRuntime_Builder.res +8 -2
  54. package/src/adapter/Runtime/LocalPluginRuntime_Builder.res +1 -4
  55. package/src/adapter/Scheduler/LocalScheduledPublisher.res +12 -12
  56. package/src/adapter/SqliteDriver.res +16 -4
  57. package/src/adapter/Task/TaskBucket_Sqlite.res +5 -6
  58. package/src/components/Aggregate_Builder.res +6 -1
  59. package/src/components/Counter_Builder.res +1 -5
  60. package/src/components/DcbEventLog_Builder.res +5 -3
  61. package/src/components/ExtensionPoint_Builder.res +13 -15
  62. package/src/components/InboundTranslationSlice_Builder.res +3 -2
  63. package/src/components/OutboundTranslationSlice_Builder.res +3 -2
  64. package/src/components/ReadModel_Builder.res +18 -20
  65. package/src/components/StateViewSlice_Builder.res +3 -2
  66. package/src/components/Task_Builder.res +12 -13
  67. package/src/reset/LocalSeedReset.res +2 -5
  68. package/src/test/Mocks/MockDcbEventLogStorage.res +5 -1
  69. package/src/test/Mocks/MockEventLogStorage.res +8 -3
  70. package/src/test/Mocks/MockQueryDbStorage.res +3 -2
  71. package/src/test/TestRunner.res +1 -1
  72. package/tests/BakedManifestFilesTest.res +7 -10
  73. package/tests/LocalEventTapTest.res +26 -21
  74. package/tests/LocalPlatformStartTest.res +33 -28
  75. package/tests/PluginEventDecodeTest.res +3 -3
  76. package/tests/ShellConfigTest.res +30 -21
  77. package/tests/SplitApiFixtures.res +34 -10
  78. package/tests/SplitApiTest.res +92 -62
  79. package/tests/adapter/BackendParityTest.res +180 -130
  80. package/tests/adapter/CollectNEventsTest.res +6 -4
  81. package/tests/adapter/CommandAuthorizationTest.res +47 -41
  82. package/tests/adapter/CommandTopicChannelTest.res +163 -139
  83. package/tests/adapter/CounterHandlerTest.res +95 -77
  84. package/tests/adapter/DcbEventLogStorageSqliteTest.res +171 -168
  85. package/tests/adapter/DcbEventLogStorageTest.res +264 -207
  86. package/tests/adapter/DomainGraphQL_ServerTest.res +53 -43
  87. package/tests/adapter/EventCollectorChannelTest.res +153 -124
  88. package/tests/adapter/EventHistoryResolverTest.res +46 -18
  89. package/tests/adapter/EventLogSnapshotParityTest.res +57 -45
  90. package/tests/adapter/EventLogStorageSqliteTest.res +9 -10
  91. package/tests/adapter/EventTapTest.res +37 -20
  92. package/tests/adapter/EventTopicPublisherTest.res +74 -50
  93. package/tests/adapter/GraphQLSubscriptionBridgeTest.res +4 -3
  94. package/tests/adapter/GraphQL_SchemaInspectorTest.res +666 -568
  95. package/tests/adapter/GraphQL_SubscriptionResolversTest.res +105 -96
  96. package/tests/adapter/HeartbeatRunnerTest.res +99 -81
  97. package/tests/adapter/InboundTranslationMutationTest.res +4 -1
  98. package/tests/adapter/LocalAuthHttpTest.res +79 -85
  99. package/tests/adapter/LocalAuthLoginTest.res +42 -50
  100. package/tests/adapter/LocalAuthTest.res +8 -16
  101. package/tests/adapter/LocalAuthUserStoreTest.res +2 -7
  102. package/tests/adapter/LocalBusBoundedTest.res +187 -117
  103. package/tests/adapter/LocalBusPubSubTest.res +118 -76
  104. package/tests/adapter/LocalDispatchContextTest.res +1 -2
  105. package/tests/adapter/LocalEvents_ServerTest.res +242 -187
  106. package/tests/adapter/PgProjectionCatchupTest.res +44 -10
  107. package/tests/adapter/ProjectionCheckpointTest.res +261 -206
  108. package/tests/adapter/QueryDbGsiTtlTest.res +123 -83
  109. package/tests/adapter/QueryDbListPushdownParityTest.res +107 -67
  110. package/tests/adapter/QueryDbListResolverTest.res +225 -208
  111. package/tests/adapter/QueryDbResolvedFieldsTest.res +17 -14
  112. package/tests/adapter/QueryDbStorageSqliteTest.res +33 -49
  113. package/tests/adapter/QueryDbStorageTest.res +163 -117
  114. package/tests/adapter/QueryDbUnionRoundTripTest.res +8 -10
  115. package/tests/adapter/QueryEngineTest.res +179 -126
  116. package/tests/adapter/ScheduledPublisherTest.res +171 -135
  117. package/tests/adapter/ServedBucketHttpTest.res +65 -59
  118. package/tests/adapter/SortKey_FilterTest.res +19 -3
  119. package/tests/adapter/SqliteDriverTest.res +13 -10
  120. package/tests/adapter/TaskBucketTest.res +94 -80
  121. package/tests/components/aggregate/AggregateTest.res +31 -18
  122. package/tests/components/automationslice/AutomationSliceCallbackTest.res +259 -222
  123. package/tests/components/automationslice/AutomationSliceFixtures.res +4 -2
  124. package/tests/components/automationslice/AutomationSliceSelfDeadlockFixtures.res +38 -38
  125. package/tests/components/automationslice/MixedSourceAutomationSliceFixtures.res +12 -14
  126. package/tests/components/automationslice/MixedSourceAutomationSliceTest.res +8 -32
  127. package/tests/components/commandgenerator/CommandGeneratorFixtures.res +7 -5
  128. package/tests/components/commandgenerator/CommandGeneratorTest.res +40 -31
  129. package/tests/components/commandtopic/CommandTopicFixtures.res +3 -4
  130. package/tests/components/commandtopic/CommandTopicStreamFixtures.res +7 -5
  131. package/tests/components/commandtopic/CommandTopicTest.res +24 -15
  132. package/tests/components/counter/CounterFixtures.res +6 -8
  133. package/tests/components/dcb/DcbCrossPartitionFixtures.res +8 -2
  134. package/tests/components/dcb/DcbCrossPartitionReadTest.res +20 -13
  135. package/tests/components/dcb/DcbEventLogAppendStreamTest.res +12 -8
  136. package/tests/components/dcb/DcbEventLogStreamTest.res +88 -61
  137. package/tests/components/dcb/DcbFixtures.res +4 -1
  138. package/tests/components/dcb/DcbTest.res +9 -6
  139. package/tests/components/eventcollector/EventCollectorFixtures.res +0 -1
  140. package/tests/components/eventcollector/EventCollectorTest.res +6 -3
  141. package/tests/components/eventlog/EventLogAppendStreamTest.res +88 -68
  142. package/tests/components/eventlog/EventLogConflictTest.res +8 -4
  143. package/tests/components/eventlog/EventLogFixtures.res +3 -3
  144. package/tests/components/eventlog/EventLogProvisioningSeamTest.res +26 -13
  145. package/tests/components/eventlog/EventLogStreamTest.res +119 -56
  146. package/tests/components/eventlog/EventLogTest.res +20 -4
  147. package/tests/components/eventtopic/EventTopicFixtures.res +3 -3
  148. package/tests/components/eventtopic/EventTopicStreamFixtures.res +1 -2
  149. package/tests/components/eventtopic/EventTopicStreamTest.res +7 -1
  150. package/tests/components/extensionpoint/ExtensionPointFixtures.res +14 -9
  151. package/tests/components/extensionpoint/ExtensionPointTest.res +3 -2
  152. package/tests/components/heartbeat/HeartbeatFixtures.res +5 -6
  153. package/tests/components/heartbeat/HeartbeatTest.res +56 -47
  154. package/tests/components/inboundtranslationslice/InboundTranslationSliceCallbackTest.res +271 -249
  155. package/tests/components/inboundtranslationslice/InboundTranslationSliceFixtures.res +3 -2
  156. package/tests/components/outboundtranslationslice/OutboundTranslationSliceCallbackTest.res +361 -276
  157. package/tests/components/outboundtranslationslice/OutboundTranslationSliceFixtures.res +8 -10
  158. package/tests/components/outboundtranslationslice/OutboundTranslationSlicePlatformFixtures.res +37 -43
  159. package/tests/components/querydb/QueryDbFixtures.res +1 -5
  160. package/tests/components/querydb/QueryDbTest.res +2 -4
  161. package/tests/components/readmodel/DcbReadModelE2EFixtures.res +15 -18
  162. package/tests/components/readmodel/MixedSourceReadModelFixtures.res +5 -7
  163. package/tests/components/readmodel/ReadModelFixtures.res +8 -8
  164. package/tests/components/scheduler/SchedulerTest.res +76 -58
  165. package/tests/components/sideeffecthandler/SideEffectHandlerFixtures.res +4 -4
  166. package/tests/components/stateviewslice/StateViewSliceDisplayNameFixtures.res +5 -7
  167. package/tests/components/stateviewslice/StateViewSliceFixtures.res +8 -6
  168. package/tests/components/stateviewslice/StateViewSliceSubIdFixtures.res +17 -11
  169. package/tests/components/stateviewslice/StateViewSliceSubIdTest.res +12 -2
  170. package/tests/components/task/TaskFixtures.res +5 -5
  171. package/tests/components/task/TaskTest.res +95 -80
  172. package/tests/plugin/PluginsProjection_GWT.res +3 -1
  173. package/tests/plugin/UiFragments_GWT.res +6 -1
  174. package/tests/reset/LocalSeedResetTest.res +1 -0
@@ -14,8 +14,7 @@ module OrderAggregateSource = {
14
14
  module Id = Id.String
15
15
  let name = "OrderAggregateEventTopic"
16
16
  @schema
17
- type event =
18
- | OrderShipped({orderId: string, productId: string})
17
+ type event = OrderShipped({orderId: string, productId: string})
19
18
  }
20
19
 
21
20
  // ─────────────────────────────────────────────────────────────
@@ -47,10 +46,11 @@ module AutoFulfillSpec = {
47
46
  }
48
47
 
49
48
  @schema
50
- type command = MarkFulfilled({
51
- orderId: @s.matches(DcbTag.string) string,
52
- productId: @s.matches(DcbTag.string) string,
53
- })
49
+ type command =
50
+ | MarkFulfilled({
51
+ orderId: @s.matches(DcbTag.string) string,
52
+ productId: @s.matches(DcbTag.string) string,
53
+ })
54
54
 
55
55
  let maxRetries = 3
56
56
  let heartbeatInterval = 60
@@ -122,10 +122,11 @@ module FromInventoryDcb = AutomationSlice.Mapping.Make(
122
122
  // Mappings collection
123
123
  // ─────────────────────────────────────────────────────────────
124
124
 
125
- module AutoFulfillAutomation: AutomationSlice.Automation
126
- with module Spec := AutoFulfillSpec = {
127
- let process = (id, item: AutoFulfillSpec.todoItem) =>
128
- Some((id, AutoFulfillSpec.MarkFulfilled({orderId: item.orderId, productId: item.productId})))
125
+ module AutoFulfillAutomation: AutomationSlice.Automation with module Spec := AutoFulfillSpec = {
126
+ let process = (id, item: AutoFulfillSpec.todoItem) => Some((
127
+ id,
128
+ AutoFulfillSpec.MarkFulfilled({orderId: item.orderId, productId: item.productId}),
129
+ ))
129
130
  let onExhausted = (_id, _item: AutoFulfillSpec.todoItem) => None
130
131
  let moduleUrl: string = %raw(`import.meta.url`)
131
132
  module M = AutomationSlice.Mappings.Make(AutoFulfillSpec)
@@ -168,10 +169,7 @@ let _ = TestRunner.setup()
168
169
  // ─────────────────────────────────────────────────────────────
169
170
 
170
171
  module AutomationSliceMaker = AutomationSlice_Builder.Make(Bus)
171
- module AutoFulfill = AutomationSliceMaker.Make(
172
- AutoFulfillSpec,
173
- AutoFulfillAutomation,
174
- )
172
+ module AutoFulfill = AutomationSliceMaker.Make(AutoFulfillSpec, AutoFulfillAutomation)
175
173
 
176
174
  // Mirrors the context that `Plugin_Builder` constructs for local deployments
177
175
  // (environment from `LocalPluginSpec.environment`, platformName "local").
@@ -25,10 +25,7 @@ describe("MixedSource AutomationSlice", () => {
25
25
 
26
26
  testPromise("Aggregate-source event creates a TODO and produces a command", async () => {
27
27
  publishedCommands := []
28
- let _ = await publishAggregateEvent(
29
- "env-a1",
30
- OrderShipped({orderId: "o1", productId: "p1"}),
31
- )
28
+ let _ = await publishAggregateEvent("env-a1", OrderShipped({orderId: "o1", productId: "p1"}))
32
29
 
33
30
  // Trigger phase2 manually since we don't have a heartbeat in this test.
34
31
  let ops = await slice->ReventlessCore.Component.operations->TestRunner.resolve
@@ -52,10 +49,7 @@ describe("MixedSource AutomationSlice", () => {
52
49
 
53
50
  testPromise("DCB-source StockReserved creates a TODO and produces a command", async () => {
54
51
  publishedCommands := []
55
- let _ = await publishDcbEvent(
56
- "env-d1",
57
- StockReserved({orderId: "o2", productId: "p2"}),
58
- )
52
+ let _ = await publishDcbEvent("env-d1", StockReserved({orderId: "o2", productId: "p2"}))
59
53
 
60
54
  let ops = await slice->ReventlessCore.Component.operations->TestRunner.resolve
61
55
  await ops.processPending()
@@ -70,14 +64,8 @@ describe("MixedSource AutomationSlice", () => {
70
64
  // Reserve fires phase1+phase2 (inline) — produces 1 command, item becomes
71
65
  // Processing. StockReleased then resolves the item to Completed —
72
66
  // subsequent processPending calls produce no further commands for it.
73
- let _ = await publishDcbEvent(
74
- "env-d2",
75
- StockReserved({orderId: "o3", productId: "p3"}),
76
- )
77
- let _ = await publishDcbEvent(
78
- "env-d3",
79
- StockReleased({orderId: "o3", productId: "p3"}),
80
- )
67
+ let _ = await publishDcbEvent("env-d2", StockReserved({orderId: "o3", productId: "p3"}))
68
+ let _ = await publishDcbEvent("env-d3", StockReleased({orderId: "o3", productId: "p3"}))
81
69
 
82
70
  expect(publishedCommands.contents->Array.length)->toBe(1)
83
71
 
@@ -91,14 +79,8 @@ describe("MixedSource AutomationSlice", () => {
91
79
 
92
80
  testPromise("both sources can drive items into the same TODO list independently", async () => {
93
81
  publishedCommands := []
94
- let _ = await publishAggregateEvent(
95
- "env-a2",
96
- OrderShipped({orderId: "o4", productId: "p4"}),
97
- )
98
- let _ = await publishDcbEvent(
99
- "env-d4",
100
- StockReserved({orderId: "o5", productId: "p5"}),
101
- )
82
+ let _ = await publishAggregateEvent("env-a2", OrderShipped({orderId: "o4", productId: "p4"}))
83
+ let _ = await publishDcbEvent("env-d4", StockReserved({orderId: "o5", productId: "p5"}))
102
84
 
103
85
  let ops = await slice->ReventlessCore.Component.operations->TestRunner.resolve
104
86
  await ops.processPending()
@@ -112,14 +94,8 @@ describe("MixedSource AutomationSlice", () => {
112
94
  testPromise("first-writer-wins idempotency across mappings sharing an ID", async () => {
113
95
  publishedCommands := []
114
96
  // Both sources produce the same composite ID — second arrival is ignored.
115
- let _ = await publishAggregateEvent(
116
- "env-a3",
117
- OrderShipped({orderId: "o6", productId: "p6"}),
118
- )
119
- let _ = await publishDcbEvent(
120
- "env-d5",
121
- StockReserved({orderId: "o6", productId: "p6"}),
122
- )
97
+ let _ = await publishAggregateEvent("env-a3", OrderShipped({orderId: "o6", productId: "p6"}))
98
+ let _ = await publishDcbEvent("env-d5", StockReserved({orderId: "o6", productId: "p6"}))
123
99
 
124
100
  let ops = await slice->ReventlessCore.Component.operations->TestRunner.resolve
125
101
  await ops.processPending()
@@ -1,7 +1,6 @@
1
1
  // Fixtures for CommandGenerator integration tests.
2
2
  // Verifies that makeHandler builds a resolver that publishes the correct commandJson.
3
3
 
4
-
5
4
  // Activate Pulumi mock mode (must be called before any Component.make)
6
5
  let _ = TestRunner.setup()
7
6
 
@@ -14,13 +13,13 @@ module CGSpec = {
14
13
  let name = "TestCGAggregate"
15
14
 
16
15
  @schema
17
- type command = | CreateCGItem({name: string})
16
+ type command = CreateCGItem({name: string})
18
17
 
19
18
  @schema
20
- type event = | CGItemCreated({name: string})
19
+ type event = CGItemCreated({name: string})
21
20
 
22
21
  @schema
23
- type error = | CGAlreadyExists
22
+ type error = CGAlreadyExists
24
23
 
25
24
  let moduleUrl: string = %raw(`import.meta.url`)
26
25
  }
@@ -42,7 +41,10 @@ module CGBehavior = {
42
41
  | CGItemCreated({name}) => {name: name}
43
42
  }
44
43
 
45
- let decide = (_state: state, command: CGSpec.command): result<array<CGSpec.event>, CGSpec.error> =>
44
+ let decide = (_state: state, command: CGSpec.command): result<
45
+ array<CGSpec.event>,
46
+ CGSpec.error,
47
+ > =>
46
48
  switch command {
47
49
  | CreateCGItem({name}) => Ok([CGSpec.CGItemCreated({name: name})])
48
50
  }
@@ -8,36 +8,45 @@ describe("CommandGenerator_Builder.Make:", () => {
8
8
  let _ = beforeEach(() => resetMocks())
9
9
 
10
10
  describe("makeHandler:", () => {
11
- testPromise("CreateCGItem payload publishes correct commandJson", async () => {
12
- // Resolve the handler from the Output wrapper
13
- let handler = await CGMaker.makeHandler(~publishJsons=mockPublish, ~publishJsonsAndWait=None)->TestRunner.resolve
14
-
15
- // Build payload: arguments must include id AND the command fields (name).
16
- // generateCommand stringifies arguments, slices off id (index 0), uses the rest as params.
17
- let payload: ReventlessCore.CommandGenerator.payload = {
18
- command: "CreateCGItem",
19
- arguments: {"id": "item-1", "name": "widget"}->Obj.magic,
20
- meta: {ip: ["127.0.0.1"], user: "testuser", info: ""},
21
- identity: Reventless.Identity.anonymous,
22
- }
23
-
24
- // Call handler generateCommand is async (awaits publishJsons internally)
25
- let _ = await handler(payload, ())->Effect.runPromise
26
-
27
- expect(capturedCmds.contents->Array.length)->toBe(1)
28
-
29
- let cmd = capturedCmds.contents->Array.getUnsafe(0)
30
- expect(cmd.id)->toBe("item-1")
31
-
32
- // commandJson should decode to CreateCGItem({name: "widget"})
33
- let decoded = cmd.commandJson->Reventless.Util_Sury.fromJson(CGSpec.commandSchema)
34
- expect(decoded)->toEqual(CGSpec.CreateCGItem({name: "widget"}))
35
- })
36
-
37
- testPromise("make creates a CommandGenerator component without throwing", async () => {
38
- let cg = CGMaker.make(~name="test-cg")
39
- let _ = cg
40
- expect(true)->toBe(true)
41
- })
11
+ testPromise(
12
+ "CreateCGItem payload publishes correct commandJson",
13
+ async () => {
14
+ // Resolve the handler from the Output wrapper
15
+ let handler = await CGMaker.makeHandler(
16
+ ~publishJsons=mockPublish,
17
+ ~publishJsonsAndWait=None,
18
+ )->TestRunner.resolve
19
+
20
+ // Build payload: arguments must include id AND the command fields (name).
21
+ // generateCommand stringifies arguments, slices off id (index 0), uses the rest as params.
22
+ let payload: ReventlessCore.CommandGenerator.payload = {
23
+ command: "CreateCGItem",
24
+ arguments: {"id": "item-1", "name": "widget"}->Obj.magic,
25
+ meta: {ip: ["127.0.0.1"], user: "testuser", info: ""},
26
+ identity: Reventless.Identity.anonymous,
27
+ }
28
+
29
+ // Call handler — generateCommand is async (awaits publishJsons internally)
30
+ let _ = await handler(payload, ())->Effect.runPromise
31
+
32
+ expect(capturedCmds.contents->Array.length)->toBe(1)
33
+
34
+ let cmd = capturedCmds.contents->Array.getUnsafe(0)
35
+ expect(cmd.id)->toBe("item-1")
36
+
37
+ // commandJson should decode to CreateCGItem({name: "widget"})
38
+ let decoded = cmd.commandJson->Reventless.Util_Sury.fromJson(CGSpec.commandSchema)
39
+ expect(decoded)->toEqual(CGSpec.CreateCGItem({name: "widget"}))
40
+ },
41
+ )
42
+
43
+ testPromise(
44
+ "make creates a CommandGenerator component without throwing",
45
+ async () => {
46
+ let cg = CGMaker.make(~name="test-cg")
47
+ let _ = cg
48
+ expect(true)->toBe(true)
49
+ },
50
+ )
42
51
  })
43
52
  })
@@ -5,13 +5,13 @@ module ItemSpec = {
5
5
  let name = "TestCommandTopicItem"
6
6
 
7
7
  @schema
8
- type command = | CreateItem({name: string}) | DeleteItem({id: string})
8
+ type command = CreateItem({name: string}) | DeleteItem({id: string})
9
9
 
10
10
  @schema
11
- type event = | ItemCreated({name: string})
11
+ type event = ItemCreated({name: string})
12
12
 
13
13
  @schema
14
- type error = | AlreadyExists
14
+ type error = AlreadyExists
15
15
 
16
16
  let moduleUrl: string = %raw(`import.meta.url`)
17
17
  }
@@ -34,4 +34,3 @@ module CommandTopicMaker = ReventlessCore.CommandTopic_Builder.Make(
34
34
  )
35
35
 
36
36
  let cmdTopic = CommandTopicMaker.make(~name="TestCommandTopic")
37
-
@@ -7,13 +7,13 @@ module ItemSpec = {
7
7
  let name = "StreamCmdItem"
8
8
 
9
9
  @schema
10
- type command = | CreateItem({name: string}) | DeleteItem({id: string})
10
+ type command = CreateItem({name: string}) | DeleteItem({id: string})
11
11
 
12
12
  @schema
13
- type event = | ItemCreated({name: string})
13
+ type event = ItemCreated({name: string})
14
14
 
15
15
  @schema
16
- type error = | AlreadyExists
16
+ type error = AlreadyExists
17
17
 
18
18
  let moduleUrl: string = %raw(`import.meta.url`)
19
19
  }
@@ -37,7 +37,9 @@ let _ = StreamBus.registerCommandHandler("StreamCmdTopicCmdTopic", async (body,
37
37
  })
38
38
 
39
39
  let makeJson = (id): Reventless.Message.commandJson => {
40
- Reventless.Message.id: id,
40
+ Reventless.Message.id,
41
41
  meta: testMeta,
42
- commandJson: ItemSpec.CreateItem({name: id})->ReventlessCore.Message.encode(ItemSpec.commandSchema),
42
+ commandJson: ItemSpec.CreateItem({name: id})->ReventlessCore.Message.encode(
43
+ ItemSpec.commandSchema,
44
+ ),
43
45
  }
@@ -11,14 +11,17 @@ describe("CommandTopic (in-memory)", () => {
11
11
 
12
12
  testPromise("publishJsons succeeds without throwing", async () => {
13
13
  let ops = await cmdTopic->ReventlessCore.Component.operations->TestRunner.resolve
14
- let commandJson = ItemSpec.CreateItem({name: "Widget"})->ReventlessCore.Message.encode(ItemSpec.commandSchema)
14
+ let commandJson =
15
+ ItemSpec.CreateItem({name: "Widget"})->ReventlessCore.Message.encode(ItemSpec.commandSchema)
15
16
  let didThrow = ref(false)
16
17
  try {
17
- await ops.publishJsons([{
18
- Reventless.Message.id: "item-1",
19
- meta: testMeta,
20
- commandJson,
21
- }])
18
+ await ops.publishJsons([
19
+ {
20
+ Reventless.Message.id: "item-1",
21
+ meta: testMeta,
22
+ commandJson,
23
+ },
24
+ ])
22
25
  } catch {
23
26
  | _ => didThrow := true
24
27
  }
@@ -41,15 +44,21 @@ describe("CommandTopic (in-memory)", () => {
41
44
  // Verify the bus receives the command on the expected channel.
42
45
  // Channel name = make name ++ ComponentType.toName(CommandTopic) = "TestCommandTopic" ++ "CmdTopic"
43
46
  let received: ref<bool> = ref(false)
44
- Bus.registerCommandHandler("TestCommandTopicCmdTopic", async (_json, _ctx) => {
45
- received := true
46
- })
47
- let commandJson = ItemSpec.DeleteItem({id: "item-99"})->ReventlessCore.Message.encode(ItemSpec.commandSchema)
48
- await ops.publishJsons([{
49
- Reventless.Message.id: "item-99",
50
- meta: testMeta,
51
- commandJson,
52
- }])
47
+ Bus.registerCommandHandler(
48
+ "TestCommandTopicCmdTopic",
49
+ async (_json, _ctx) => {
50
+ received := true
51
+ },
52
+ )
53
+ let commandJson =
54
+ ItemSpec.DeleteItem({id: "item-99"})->ReventlessCore.Message.encode(ItemSpec.commandSchema)
55
+ await ops.publishJsons([
56
+ {
57
+ Reventless.Message.id: "item-99",
58
+ meta: testMeta,
59
+ commandJson,
60
+ },
61
+ ])
53
62
  expect(received.contents)->toBe(true)
54
63
  })
55
64
  })
@@ -23,14 +23,12 @@ module CounterMaker = Counter_Builder.Make(Bus)
23
23
 
24
24
  let counterEvents: ref<array<JSON.t>> = ref([])
25
25
 
26
- let counter = CounterMaker.make(
27
- ~name="TestCounter",
28
- ~jsonEventsHandler=stream =>
29
- stream
30
- ->Stream.runCollect
31
- ->Effect.map(chunk => {
32
- counterEvents := counterEvents.contents->Array.concat(chunk)
33
- }),
26
+ let counter = CounterMaker.make(~name="TestCounter", ~jsonEventsHandler=stream =>
27
+ stream
28
+ ->Stream.runCollect
29
+ ->Effect.map(chunk => {
30
+ counterEvents := counterEvents.contents->Array.concat(chunk)
31
+ })
34
32
  )
35
33
 
36
34
  // ─────────────────────────────────────────────────────────────
@@ -69,7 +69,10 @@ module AddProductBehavior = {
69
69
  let evolve = (state: state, event: Spec.consumedEvent): state =>
70
70
  switch event {
71
71
  | ProductAdded(_) => {...state, exists: true}
72
- | CategoryAdded({categoryId}) => {...state, liveCats: Array.concat(state.liveCats, [categoryId])}
72
+ | CategoryAdded({categoryId}) => {
73
+ ...state,
74
+ liveCats: Array.concat(state.liveCats, [categoryId]),
75
+ }
73
76
  }
74
77
 
75
78
  let decide = (state: state, command: Spec.command): result<array<Spec.event>, Spec.error> =>
@@ -148,7 +151,10 @@ let publishJsonsOutput = publishJsons->Pulumi.Output.make
148
151
  let _addProductSlice = AddProductMaker.make(
149
152
  ~dcbEventLog=eventLog,
150
153
  ~publishJsons=publishJsonsOutput,
151
- ~tagKeysByEventType=Dict.fromArray([("ProductAdded", ["productId"]), ("CategoryAdded", ["categoryId"])]),
154
+ ~tagKeysByEventType=Dict.fromArray([
155
+ ("ProductAdded", ["productId"]),
156
+ ("CategoryAdded", ["categoryId"]),
157
+ ]),
152
158
  ~crossPartitionTagKeys=["categoryId"],
153
159
  )
154
160
 
@@ -44,25 +44,32 @@ describe("DCB inferred cross-partition read (sibling exclusion)", () => {
44
44
  await dispatch(addProductJson("p3", "cat3", "Blocks"), "p3")
45
45
 
46
46
  // Queryable by its own partition key…
47
- let byProduct = await ops.read(~query=[{tags: [{Reventless.DcbTag.key: "productId", value: "p3"}]}])
47
+ let byProduct = await ops.read(
48
+ ~query=[{tags: [{Reventless.DcbTag.key: "productId", value: "p3"}]}],
49
+ )
48
50
  expect(byProduct.events->Array.some(e => e.eventType == "ProductAdded"))->toBe(true)
49
51
 
50
52
  // …but the categoryId index returns only the category's own event, never p3.
51
- let byCategory = await ops.read(~query=[{tags: [{Reventless.DcbTag.key: "categoryId", value: "cat3"}]}])
53
+ let byCategory = await ops.read(
54
+ ~query=[{tags: [{Reventless.DcbTag.key: "categoryId", value: "cat3"}]}],
55
+ )
52
56
  expect(byCategory.events->Array.some(e => e.eventType == "ProductAdded"))->toBe(false)
53
57
  expect(byCategory.events->Array.some(e => e.eventType == "CategoryAdded"))->toBe(true)
54
58
  },
55
59
  )
56
60
 
57
- testPromise("re-adding the same product is still rejected (its own read is partition-scoped)", async () => {
58
- let ops = await eventLog->ReventlessCore.Component.operations->TestRunner.resolve
59
- let _ = await ops.append([
60
- encodeEvent(CategoryAdded({categoryId: "cat2", name: "Books"})),
61
- encodeEvent(ProductAdded({productId: "p9", categoryId: "cat2", name: "Novel"})),
62
- ])
63
- capturedEventCount := 0 // count only events produced by the command below
64
- // p9 already exists — its own productId clause returns its ProductAdded.
65
- await dispatch(addProductJson("p9", "cat2", "Novel"), "p9")
66
- expect(capturedEventCount.contents)->toBe(0)
67
- })
61
+ testPromise(
62
+ "re-adding the same product is still rejected (its own read is partition-scoped)",
63
+ async () => {
64
+ let ops = await eventLog->ReventlessCore.Component.operations->TestRunner.resolve
65
+ let _ = await ops.append([
66
+ encodeEvent(CategoryAdded({categoryId: "cat2", name: "Books"})),
67
+ encodeEvent(ProductAdded({productId: "p9", categoryId: "cat2", name: "Novel"})),
68
+ ])
69
+ capturedEventCount := 0 // count only events produced by the command below
70
+ // p9 already exists — its own productId clause returns its ProductAdded.
71
+ await dispatch(addProductJson("p9", "cat2", "Novel"), "p9")
72
+ expect(capturedEventCount.contents)->toBe(0)
73
+ },
74
+ )
68
75
  })
@@ -25,10 +25,11 @@ describe("DcbEventLog.appendStream (in-memory adapter)", () => {
25
25
 
26
26
  testPromise("appendStream writes all events from stream", async () => {
27
27
  let ops = await eventLog->ReventlessCore.Component.operations->TestRunner.resolve
28
- let stream = [
29
- encodeEvent(DcbFixtures.ItemEventLog.ItemAdded({id: "as-w1", name: "Widget1"})),
30
- encodeEvent(DcbFixtures.ItemEventLog.ItemAdded({id: "as-w1", name: "Widget2"})),
31
- ]->Stream.fromIterable
28
+ let stream =
29
+ [
30
+ encodeEvent(DcbFixtures.ItemEventLog.ItemAdded({id: "as-w1", name: "Widget1"})),
31
+ encodeEvent(DcbFixtures.ItemEventLog.ItemAdded({id: "as-w1", name: "Widget2"})),
32
+ ]->Stream.fromIterable
32
33
  let result = await ops.appendStream(stream)->Effect.runPromise
33
34
  let isOk = switch result {
34
35
  | Ok(_) => true
@@ -52,10 +53,12 @@ describe("DcbEventLog.appendStream (in-memory adapter)", () => {
52
53
 
53
54
  testPromise("readStream → appendStream pipeline copies events", async () => {
54
55
  let ops = await eventLog->ReventlessCore.Component.operations->TestRunner.resolve
55
- let _ = await ops.append([encodeEvent(DcbFixtures.ItemEventLog.ItemAdded({id: "as-src", name: "Source"}))])
56
+ let _ = await ops.append([
57
+ encodeEvent(DcbFixtures.ItemEventLog.ItemAdded({id: "as-src", name: "Source"})),
58
+ ])
56
59
  // readStream returns rawSequencedEvent; extract bare rawEvent for appendStream
57
- let srcStream =
58
- ops.readStream(~query=tagQuery("as-src"))->Stream.map(se => {
60
+ let srcStream = ops.readStream(~query=tagQuery("as-src"))->Stream.map(
61
+ se => {
59
62
  let rawEvent: ReventlessInfra.DcbEventLog.rawEvent = {
60
63
  eventType: se.eventType,
61
64
  data: se.data,
@@ -63,7 +66,8 @@ describe("DcbEventLog.appendStream (in-memory adapter)", () => {
63
66
  meta: se.meta,
64
67
  }
65
68
  rawEvent
66
- })
69
+ },
70
+ )
67
71
  let _ = await ops.appendStream(srcStream)->Effect.runPromise
68
72
  let dst = await ops.read(~query=tagQuery("as-src"))
69
73
  // 1 original + 1 copy = 2 events for the same tag