@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
@@ -91,6 +91,7 @@ let registerResourcesFromEntries = (
91
91
  ],
92
92
  }
93
93
  }
94
+
94
95
  // URIs with template parameters ({id}) go into resource templates;
95
96
  // fixed URIs (list endpoints) go into regular resources.
96
97
  if def.uriTemplate->String.includes("{") {
@@ -237,9 +238,7 @@ let createServerInstance = (identity: Reventless.Identity.t) => {
237
238
  }
238
239
  await handler(uri)
239
240
  | None => {
240
- McpSdk.contents: [
241
- {McpSdk.uri, text: `{"error": "Resource not found: ${uri}"}`},
242
- ],
241
+ McpSdk.contents: [{McpSdk.uri, text: `{"error": "Resource not found: ${uri}"}`}],
243
242
  }
244
243
  }
245
244
  }
@@ -250,45 +249,47 @@ let createServerInstance = (identity: Reventless.Identity.t) => {
250
249
 
251
250
  let start = (~port: int=3001, ()) => {
252
251
  let httpServer = McpSdk.createHttpServer((req, res) => {
253
- let _ = (async () => {
254
- let reqMethod = req->McpSdk.method
255
- let reqUrl = req->McpSdk.url
256
-
257
- // CORS headers for browser-based clients (e.g. MCP Inspector)
258
- res->McpSdk.setHeader("Access-Control-Allow-Origin", "*")
259
- res->McpSdk.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS")
260
- res->McpSdk.setHeader("Access-Control-Allow-Headers", "Content-Type")
261
-
262
- if reqUrl == "/mcp" || reqUrl->String.startsWith("/mcp?") {
263
- switch reqMethod {
264
- | "OPTIONS" =>
265
- res->McpSdk.setStatusCode(204)
266
- res->McpSdk.endResponseNoBody
267
- | "POST" =>
268
- let body = await McpSdk_Helpers.parseJsonBody(req)
269
- let identity = extractIdentity(req)
270
- // Stateless mode: fresh server + transport per request
271
- let server = createServerInstance(identity)
272
- let transport = McpSdk.newStreamableHTTPTransport({
273
- enableJsonResponse: true,
274
- })
275
- let _ = await server->McpSdk.connect(transport)
276
- let _ = await transport->McpSdk.handleRequest(req, res, body)
277
- | "GET" =>
278
- res->McpSdk.setHeader("Content-Type", "text/plain")
279
- res->McpSdk.endResponse("MCP server running")
280
- | "DELETE" =>
281
- res->McpSdk.setStatusCode(200)
282
- res->McpSdk.endResponseNoBody
283
- | _ =>
284
- res->McpSdk.setStatusCode(405)
285
- res->McpSdk.endResponse("Method not allowed")
252
+ let _ = (
253
+ async () => {
254
+ let reqMethod = req->McpSdk.method
255
+ let reqUrl = req->McpSdk.url
256
+
257
+ // CORS headers for browser-based clients (e.g. MCP Inspector)
258
+ res->McpSdk.setHeader("Access-Control-Allow-Origin", "*")
259
+ res->McpSdk.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS")
260
+ res->McpSdk.setHeader("Access-Control-Allow-Headers", "Content-Type")
261
+
262
+ if reqUrl == "/mcp" || reqUrl->String.startsWith("/mcp?") {
263
+ switch reqMethod {
264
+ | "OPTIONS" =>
265
+ res->McpSdk.setStatusCode(204)
266
+ res->McpSdk.endResponseNoBody
267
+ | "POST" =>
268
+ let body = await McpSdk_Helpers.parseJsonBody(req)
269
+ let identity = extractIdentity(req)
270
+ // Stateless mode: fresh server + transport per request
271
+ let server = createServerInstance(identity)
272
+ let transport = McpSdk.newStreamableHTTPTransport({
273
+ enableJsonResponse: true,
274
+ })
275
+ let _ = await server->McpSdk.connect(transport)
276
+ let _ = await transport->McpSdk.handleRequest(req, res, body)
277
+ | "GET" =>
278
+ res->McpSdk.setHeader("Content-Type", "text/plain")
279
+ res->McpSdk.endResponse("MCP server running")
280
+ | "DELETE" =>
281
+ res->McpSdk.setStatusCode(200)
282
+ res->McpSdk.endResponseNoBody
283
+ | _ =>
284
+ res->McpSdk.setStatusCode(405)
285
+ res->McpSdk.endResponse("Method not allowed")
286
+ }
287
+ } else {
288
+ res->McpSdk.setStatusCode(404)
289
+ res->McpSdk.endResponse("Not found")
286
290
  }
287
- } else {
288
- res->McpSdk.setStatusCode(404)
289
- res->McpSdk.endResponse("Not found")
290
291
  }
291
- })()
292
+ )()
292
293
  })
293
294
 
294
295
  httpServer->McpSdk.listen(port, () => {
@@ -301,7 +302,10 @@ let start = (~port: int=3001, ()) => {
301
302
  toolNames->Array.forEach(t => log.info(~comp="MCP:Domain", ` - ${t}`))
302
303
  log.info(~comp="MCP:Domain", `resources (${resourceNames->Array.length->Int.toString}):`)
303
304
  resourceNames->Array.forEach(r => log.info(~comp="MCP:Domain", ` - ${r}`))
304
- log.info(~comp="MCP:Domain", `resource templates (${templateNames->Array.length->Int.toString}):`)
305
+ log.info(
306
+ ~comp="MCP:Domain",
307
+ `resource templates (${templateNames->Array.length->Int.toString}):`,
308
+ )
305
309
  templateNames->Array.forEach(r => log.info(~comp="MCP:Domain", ` - ${r}`))
306
310
  }
307
311
  })
@@ -10,11 +10,11 @@ module Make = (Bus: LocalBus.T) => {
10
10
  type channelParts = unit
11
11
  type runtimeParts = LocalRuntimeEnvironment.parts
12
12
 
13
- let make: ReventlessCore.EventCollector_Adapter.channelMaker<callbackEvent, 'context, channelParts> = (
14
- ~name as _,
15
- ~eventTopics,
16
- ~owner as _, ~opts as _,
17
- ) => {
13
+ let make: ReventlessCore.EventCollector_Adapter.channelMaker<
14
+ callbackEvent,
15
+ 'context,
16
+ channelParts,
17
+ > = (~name as _, ~eventTopics, ~owner as _, ~opts as _) => {
18
18
  // Collect all event topic resources as our channel resources
19
19
  let eventTopicResources =
20
20
  eventTopics
@@ -26,8 +26,8 @@ module Make = (Bus: LocalBus.T) => {
26
26
  resources: eventTopicResources,
27
27
  enqueueEvent: ((_, _, _) => Promise.resolve())->Pulumi.Output.make,
28
28
  handleChannelEvent: (handleEvents: ReventlessCore.EventCollector.jsonEventsHandler) =>
29
- ((json: JSON.t, _ctx) =>
30
- handleEvents(Stream.fromIterable([json]))->Effect.map(_ => ())
29
+ (
30
+ (json: JSON.t, _ctx) => handleEvents(Stream.fromIterable([json]))->Effect.map(_ => ())
31
31
  )->Pulumi.Output.make,
32
32
  }
33
33
  }
@@ -43,43 +43,54 @@ module Make = (Bus: LocalBus.T) => {
43
43
  let _reg =
44
44
  runtime.parts.handlerDeferred
45
45
  ->Deferred.await_
46
- ->Effect.flatMap(handler => Effect.sync(() => Bus.registerEventCollectorHandler(name, handler)))
46
+ ->Effect.flatMap(handler =>
47
+ Effect.sync(() => Bus.registerEventCollectorHandler(name, handler))
48
+ )
47
49
  ->Effect.runPromise
48
50
  ->ignore
49
51
 
50
- channelSpecs->Array.forEach(({eventTopics}: ReventlessCore.EventCollector_Adapter.channelSpec<
51
- callbackEvent,
52
- 'context,
53
- channelParts,
54
- >) => {
52
+ channelSpecs->Array.forEach((
53
+ {eventTopics}: ReventlessCore.EventCollector_Adapter.channelSpec<
54
+ callbackEvent,
55
+ 'context,
56
+ channelParts,
57
+ >,
58
+ ) => {
55
59
  eventTopics
56
60
  ->Dict.valuesToArray
57
61
  ->Array.forEach((topicOutputs: ReventlessCore.EventTopic.outputs) => {
58
- topicOutputs.resources->Array.forEach(resource => {
59
- // resource.name is the bus topic key set by LocalEventTopicPublisher
60
- let _ = resource.name->Pulumi.Output.apply(topicName => {
61
- // Stream-based drain: subscribeToEventStream returns a scoped Effect that
62
- // yields Stream<queuedEvent>. done_ is run explicitly after each handler call
63
- // to unblock publishEvent.
64
- let drainEffect = Effect.scoped(
65
- Bus.subscribeToEventStream(topicName)
66
- ->Effect.flatMap(stream =>
67
- stream->Stream.runForEach(msg =>
68
- Effect.promise(async () => {
69
- let handler =
70
- await runtime.parts.handlerDeferred->Deferred.await_->Effect.runPromise
71
- await handler(msg.json, ())
72
- })
73
- ->Effect.zipRight(msg.done_)
62
+ topicOutputs.resources->Array.forEach(
63
+ resource => {
64
+ // resource.name is the bus topic key set by LocalEventTopicPublisher
65
+ let _ = resource.name->Pulumi.Output.apply(
66
+ topicName => {
67
+ // Stream-based drain: subscribeToEventStream returns a scoped Effect that
68
+ // yields Stream<queuedEvent>. done_ is run explicitly after each handler call
69
+ // to unblock publishEvent.
70
+ let drainEffect = Effect.scoped(
71
+ Bus.subscribeToEventStream(topicName)->Effect.flatMap(
72
+ stream =>
73
+ stream->Stream.runForEach(
74
+ msg =>
75
+ Effect.promise(
76
+ async () => {
77
+ let handler = await runtime.parts.handlerDeferred
78
+ ->Deferred.await_
79
+ ->Effect.runPromise
80
+ await handler(msg.json, ())
81
+ },
82
+ )->Effect.zipRight(msg.done_),
83
+ ),
84
+ ),
74
85
  )
75
- ),
86
+ let _ = Effect.runFork(drainEffect)
87
+ // Signal that this topic's subscription is registered.
88
+ // Latch.open_ is idempotent — calling it for multiple topics is safe.
89
+ runtime.parts.subscriptionLatch->Latch.open_->Effect.runPromise->ignore
90
+ },
76
91
  )
77
- let _ = Effect.runFork(drainEffect)
78
- // Signal that this topic's subscription is registered.
79
- // Latch.open_ is idempotent — calling it for multiple topics is safe.
80
- runtime.parts.subscriptionLatch->Latch.open_->Effect.runPromise->ignore
81
- })
82
- })
92
+ },
93
+ )
83
94
  })
84
95
  })
85
96
  []
@@ -123,10 +123,12 @@ let readFilter = (args: JSON.t): filter => {
123
123
  entityId: ?s("entityId"),
124
124
  tagKey: ?s("tagKey"),
125
125
  tagValue: ?s("tagValue"),
126
- eventTypes: ?f
127
- ->Dict.get("eventTypes")
128
- ->Option.flatMap(JSON.Decode.array)
129
- ->Option.map(a => a->Array.filterMap(JSON.Decode.string)),
126
+ eventTypes: ?(
127
+ f
128
+ ->Dict.get("eventTypes")
129
+ ->Option.flatMap(JSON.Decode.array)
130
+ ->Option.map(a => a->Array.filterMap(JSON.Decode.string))
131
+ ),
130
132
  user: ?s("user"),
131
133
  timeFrom: ?s("timeFrom"),
132
134
  timeTo: ?s("timeTo"),
@@ -193,7 +195,10 @@ let matchesTag = (r: record, f: filter): bool =>
193
195
 
194
196
  let matchesFilter = (r: record, f: filter): bool => {
195
197
  let userOf = () =>
196
- r.meta->JSON.Decode.object->Option.flatMap(d => d->Dict.get("user"))->Option.flatMap(JSON.Decode.string)
198
+ r.meta
199
+ ->JSON.Decode.object
200
+ ->Option.flatMap(d => d->Dict.get("user"))
201
+ ->Option.flatMap(JSON.Decode.string)
197
202
  let timeOf = () =>
198
203
  r.meta
199
204
  ->JSON.Decode.object
@@ -309,8 +314,7 @@ module Make = (Bus: LocalBus.T) => {
309
314
  envelopes->Array.mapWithIndex((json, i) => {
310
315
  let obj = json->JSON.Decode.object->Option.getOr(Dict.make())
311
316
  let event = obj->Dict.get("event")->Option.getOr(JSON.Encode.null)
312
- let id =
313
- obj->Dict.get("id")->Option.flatMap(JSON.Decode.string)->Option.getOr(entityId)
317
+ let id = obj->Dict.get("id")->Option.flatMap(JSON.Decode.string)->Option.getOr(entityId)
314
318
  let meta = metaJsonFromEnvelope(obj)
315
319
  let time =
316
320
  meta
@@ -386,11 +390,10 @@ module Make = (Bus: LocalBus.T) => {
386
390
  // not disagree about arguments or return type. (The supporting types
387
391
  // reach the server separately, via `schemaTypeRegistrationHook` over
388
392
  // the fragment's `types`.)
389
- let sdlFields =
390
- Plugin_EventQuerySchema.generate(
391
- ~plugin=params.pluginName,
392
- ~eventLogEntries=[entry],
393
- ).queryFields
393
+ let sdlFields = Plugin_EventQuerySchema.generate(
394
+ ~plugin=params.pluginName,
395
+ ~eventLogEntries=[entry],
396
+ ).queryFields
394
397
 
395
398
  server.registerQueries(~sdlFields, ~resolvers=Dict.fromArray([(fieldName, resolver)]))
396
399
  }
@@ -50,8 +50,9 @@ let makeMemoryStorage = (~name as _name, ~opts as _) => {
50
50
  let replay: ReventlessCore.EventLog.replay<string, JSON.t> = id =>
51
51
  replayStream(id)->Stream.runCollect->Effect.runPromise
52
52
 
53
- let latestSnapshot: ReventlessCore.EventLog.latestSnapshot<string> = async id =>
54
- Ok(snapshots->Dict.get(id))
53
+ let latestSnapshot: ReventlessCore.EventLog.latestSnapshot<string> = async id => Ok(
54
+ snapshots->Dict.get(id),
55
+ )
55
56
 
56
57
  let writeSnapshot: ReventlessCore.EventLog.writeSnapshot<string> = async (id, snap) => {
57
58
  snapshots->Dict.set(id, snap)
@@ -60,7 +61,11 @@ let makeMemoryStorage = (~name as _name, ~opts as _) => {
60
61
 
61
62
  // Appends each stream item sequentially to storage.
62
63
  // Node.js is single-threaded so a plain ref is safe for the seqNr counter.
63
- let appendStream: ReventlessCore.EventLog.appendStream<string, JSON.t> = (startingSeqNr, id, stream) => {
64
+ let appendStream: ReventlessCore.EventLog.appendStream<string, JSON.t> = (
65
+ startingSeqNr,
66
+ id,
67
+ stream,
68
+ ) => {
64
69
  let seqNrRef = ref(startingSeqNr)
65
70
  stream->Stream.runForEach(json =>
66
71
  Stm.TRef.modify(eventsRef, events => {
@@ -78,7 +83,7 @@ let makeMemoryStorage = (~name as _name, ~opts as _) => {
78
83
  switch result {
79
84
  | Ok() =>
80
85
  seqNrRef := seqNrRef.contents + 1
81
- Effect.succeed(())
86
+ Effect.succeed()
82
87
  | Error(msg) => Effect.fail(msg)
83
88
  }
84
89
  )
@@ -58,27 +58,32 @@ let countAll = (db: SqliteDriver.t): int => {
58
58
  let makeStorage = (~db: SqliteDriver.t, ~name: string, ~opts as _) => {
59
59
  ensureSchema(db)
60
60
 
61
- let insertStmt = db->SqliteDriver.prepare(
62
- "INSERT INTO event_log(log_name, aggregate_id, seq_nr, payload) VALUES(?,?,?,?)",
63
- )
61
+ let insertStmt =
62
+ db->SqliteDriver.prepare(
63
+ "INSERT INTO event_log(log_name, aggregate_id, seq_nr, payload) VALUES(?,?,?,?)",
64
+ )
64
65
  // Expected next seq_nr for an aggregate. Events are always appended
65
66
  // contiguously from seq 0 (the OCC check below forbids gaps), so
66
67
  // `MAX(seq_nr)+1` equals the row count — but it reads the rightmost leaf of the
67
68
  // (log_name, aggregate_id, seq_nr) PK index in O(log n) instead of `COUNT(*)`
68
69
  // scanning every row for the aggregate. `COALESCE(…, -1)+1` yields 0 when empty.
69
- let nextSeqStmt = db->SqliteDriver.prepare(
70
- "SELECT COALESCE(MAX(seq_nr), -1) + 1 AS c FROM event_log WHERE log_name=? AND aggregate_id=?",
71
- )
72
- let selectByIdStmt = db->SqliteDriver.prepare(
73
- "SELECT payload FROM event_log WHERE log_name=? AND aggregate_id=? AND seq_nr >= ? ORDER BY seq_nr ASC",
74
- )
70
+ let nextSeqStmt =
71
+ db->SqliteDriver.prepare(
72
+ "SELECT COALESCE(MAX(seq_nr), -1) + 1 AS c FROM event_log WHERE log_name=? AND aggregate_id=?",
73
+ )
74
+ let selectByIdStmt =
75
+ db->SqliteDriver.prepare(
76
+ "SELECT payload FROM event_log WHERE log_name=? AND aggregate_id=? AND seq_nr >= ? ORDER BY seq_nr ASC",
77
+ )
75
78
  let lastRowidStmt = db->SqliteDriver.prepare("SELECT last_insert_rowid() AS r")
76
- let upsertSnapshotStmt = db->SqliteDriver.prepare(
77
- "INSERT OR REPLACE INTO snapshot(log_name, aggregate_id, seq_nr, state, schema_hash) VALUES(?,?,?,?,?)",
78
- )
79
- let selectSnapshotStmt = db->SqliteDriver.prepare(
80
- "SELECT seq_nr, state, schema_hash FROM snapshot WHERE log_name=? AND aggregate_id=?",
81
- )
79
+ let upsertSnapshotStmt =
80
+ db->SqliteDriver.prepare(
81
+ "INSERT OR REPLACE INTO snapshot(log_name, aggregate_id, seq_nr, state, schema_hash) VALUES(?,?,?,?,?)",
82
+ )
83
+ let selectSnapshotStmt =
84
+ db->SqliteDriver.prepare(
85
+ "SELECT seq_nr, state, schema_hash FROM snapshot WHERE log_name=? AND aggregate_id=?",
86
+ )
82
87
 
83
88
  let expectedNextSeq = (id: string): int =>
84
89
  switch nextSeqStmt->SqliteDriver.get([JSON.Encode.string(name), JSON.Encode.string(id)]) {
@@ -118,6 +123,7 @@ let makeStorage = (~db: SqliteDriver.t, ~name: string, ~opts as _) => {
118
123
  | None => 0
119
124
  }
120
125
  })
126
+
121
127
  // Register the committed batch as appended-but-not-yet-published for the
122
128
  // projection checkpoint low-watermark; Platform's afterPublish hook
123
129
  // resolves the msgIds once the publish cycle completes. A transaction's
@@ -160,11 +166,7 @@ let makeStorage = (~db: SqliteDriver.t, ~name: string, ~opts as _) => {
160
166
 
161
167
  let replayArray = (id: string, ~fromSeq=0): array<JSON.t> =>
162
168
  selectByIdStmt
163
- ->SqliteDriver.all([
164
- JSON.Encode.string(name),
165
- JSON.Encode.string(id),
166
- JSON.Encode.int(fromSeq),
167
- ])
169
+ ->SqliteDriver.all([JSON.Encode.string(name), JSON.Encode.string(id), JSON.Encode.int(fromSeq)])
168
170
  ->Array.map(decodePayload)
169
171
 
170
172
  let replay: EventLog.replay<string, JSON.t> = async id => replayArray(id)
@@ -172,7 +174,7 @@ let makeStorage = (~db: SqliteDriver.t, ~name: string, ~opts as _) => {
172
174
  // node:sqlite's iterate() could back a lazy stream, but the current Stream
173
175
  // API only has array-based fromIterable. Small events, dev-only backend →
174
176
  // materialise via all() and wrap. Swap to a lazy adapter if it matters.
175
- let replayStream = (id, ~fromSeq=?) => replayArray(id, ~fromSeq=?fromSeq)->Stream.fromIterable
177
+ let replayStream = (id, ~fromSeq=?) => replayArray(id, ~fromSeq?)->Stream.fromIterable
176
178
 
177
179
  let latestSnapshot: EventLog.latestSnapshot<string> = async id =>
178
180
  try {
@@ -197,7 +199,10 @@ let makeStorage = (~db: SqliteDriver.t, ~name: string, ~opts as _) => {
197
199
  } catch {
198
200
  | exn =>
199
201
  Error(
200
- exn->JsExn.fromException->Option.flatMap(JsExn.message)->Option.getOr("snapshot read error"),
202
+ exn
203
+ ->JsExn.fromException
204
+ ->Option.flatMap(JsExn.message)
205
+ ->Option.getOr("snapshot read error"),
201
206
  )
202
207
  }
203
208
 
@@ -230,7 +235,9 @@ let makeStorage = (~db: SqliteDriver.t, ~name: string, ~opts as _) => {
230
235
  stream
231
236
  ->Stream.runCollect
232
237
  ->Effect.flatMap(jsons =>
233
- Effect.promise(() => appendTracked(~track=false, startingSeqNr, id, jsons))->Effect.flatMap(result =>
238
+ Effect.promise(() =>
239
+ appendTracked(~track=false, startingSeqNr, id, jsons)
240
+ )->Effect.flatMap(result =>
234
241
  switch result {
235
242
  | Ok() => Effect.succeed()
236
243
  // appendStream's error channel is a string; map the typed append error.
@@ -257,7 +264,12 @@ let makeStorage = (~db: SqliteDriver.t, ~name: string, ~opts as _) => {
257
264
  )
258
265
  }
259
266
 
260
- module Make = (Bus: LocalBus.T, DbProvider: {let db: SqliteDriver.t}) => {
267
+ module Make = (
268
+ Bus: LocalBus.T,
269
+ DbProvider: {
270
+ let db: SqliteDriver.t
271
+ },
272
+ ) => {
261
273
  let make: EventLog_Adapter.storageMaker = (~name, ~owner as _, ~opts) => {
262
274
  let (storageName, replay, storage) = makeStorage(~db=DbProvider.db, ~name, ~opts)
263
275
  Bus.registerEventLogReplay(storageName, replay)
@@ -2,7 +2,12 @@
2
2
  // The resource name doubles as the bus topic key that EventCollectorChannel subscribes to.
3
3
 
4
4
  module Make = (Bus: LocalBus.T) => {
5
- let make: ReventlessCore.EventTopic_Adapter.publisherMaker = (~name, ~storageResources as _, ~owner as _, ~opts as _) => {
5
+ let make: ReventlessCore.EventTopic_Adapter.publisherMaker = (
6
+ ~name,
7
+ ~storageResources as _,
8
+ ~owner as _,
9
+ ~opts as _,
10
+ ) => {
6
11
  let publishJson = (service, meta, json) => Bus.publishEvent(name, service, meta, json)
7
12
  let publishJsonStream: ReventlessInfra.EventTopic.publishJsonStream = stream =>
8
13
  stream
@@ -10,8 +15,9 @@ module Make = (Bus: LocalBus.T) => {
10
15
  ->Stream.runForEach(items =>
11
16
  Effect.promise(() =>
12
17
  items
13
- ->Array.map(({ReventlessInfra.EventTopic.service, meta, json}) =>
14
- publishJson(service, meta, json)
18
+ ->Array.map(
19
+ ({ReventlessInfra.EventTopic.service: service, meta, json}) =>
20
+ publishJson(service, meta, json),
15
21
  )
16
22
  ->Promise.all
17
23
  ->Promise.thenResolve(_ => ())
@@ -32,20 +32,15 @@ let make: ReventlessCore.Heartbeat_Adapter.runnerMaker<runtimeParts> = (
32
32
  ) => {
33
33
  let handlerDeferred = runtime.parts.handlerDeferred
34
34
  let intervalMs = timeout * 60 * 1000
35
- let handle = setIntervalJs(
36
- () => {
37
- // Await the handler Deferred on each tick. Since the Deferred is completed
38
- // during make() setup, this resolves immediately after the first tick.
39
- handlerDeferred
40
- ->Deferred.await_
41
- ->Effect.flatMap(handler =>
42
- Effect.promise(() => handler(Obj.magic(()), ()))
43
- )
44
- ->Effect.runPromise
45
- ->ignore
46
- },
47
- intervalMs,
48
- )
35
+ let handle = setIntervalJs(() => {
36
+ // Await the handler Deferred on each tick. Since the Deferred is completed
37
+ // during make() setup, this resolves immediately after the first tick.
38
+ handlerDeferred
39
+ ->Deferred.await_
40
+ ->Effect.flatMap(handler => Effect.promise(() => handler(Obj.magic(), ())))
41
+ ->Effect.runPromise
42
+ ->ignore
43
+ }, intervalMs)
49
44
  activeTimers.contents->Dict.set(name, handle)
50
45
  {resources: []}
51
46
  }
@@ -66,6 +66,7 @@ let emitEventTap = (~topic: string, ~service: string, ~payload: JSON.t) => {
66
66
  // later sees is the event's ordinal rather than the ordinal of its own arrival.
67
67
  eventTapSeq := eventTapSeq.contents + 1
68
68
  let toStdout = LocalEventTap.stdoutEnabled()
69
+
69
70
  // The line is built only for somebody: serialising every event for no reader is
70
71
  // pure cost on the publish path.
71
72
  if toStdout || LocalEventTap.hasReaders() {
@@ -296,8 +297,8 @@ module Impl = (C: BusConfig): T => {
296
297
  // draining, and run `done_` via `ensuring` so the countdown always
297
298
  // advances regardless of outcome.
298
299
  Effect.tryPromise(~catch=e => e, () => handler(msg.service, msg.meta, msg.json))
299
- ->Effect.catchAll(err =>
300
- Effect.sync(() => Console.error2("[LocalBus] subscriber handler failed:", err))
300
+ ->Effect.catchAll(
301
+ err => Effect.sync(() => Console.error2("[LocalBus] subscriber handler failed:", err)),
301
302
  )
302
303
  ->Effect.ensuring(msg.done_)
303
304
  )
@@ -404,11 +405,10 @@ module Impl = (C: BusConfig): T => {
404
405
  let pending = queue.contents
405
406
  queue.contents = []
406
407
  pending->Array.forEach(json => {
407
- let _ =
408
- handler(json, ())->Promise.catch(e => {
409
- Console.error2("[LocalBus] parked command handler failed:", e)
410
- Promise.resolve()
411
- })
408
+ let _ = handler(json, ())->Promise.catch(e => {
409
+ Console.error2("[LocalBus] parked command handler failed:", e)
410
+ Promise.resolve()
411
+ })
412
412
  })
413
413
  | None => ()
414
414
  }
@@ -461,14 +461,14 @@ module Impl = (C: BusConfig): T => {
461
461
  // Cross-plugin subscriptions are fire-and-forget: the handler is started asynchronously
462
462
  // and done_ is called immediately. This prevents a deadlock where publishEvent on the
463
463
  // EP topic would block until the downstream aggregate command chain completes.
464
- let makeFireAndForgetHandler = handler => (_, _, json) => {
465
- let _ =
466
- handler(json, ())->Promise.catch(e => {
464
+ let makeFireAndForgetHandler = handler =>
465
+ (_, _, json) => {
466
+ let _ = handler(json, ())->Promise.catch(e => {
467
467
  Console.error2("[LocalBus] fire-and-forget handler failed:", e)
468
468
  Promise.resolve()
469
469
  })
470
- Promise.resolve()
471
- }
470
+ Promise.resolve()
471
+ }
472
472
 
473
473
  let registerEventCollectorHandler = (ecName, handler) => {
474
474
  eventCollectorHandlers.contents->Dict.set(ecName, handler)
@@ -27,4 +27,10 @@ let registerMessagingSender = (sender: string) => messagingSenderRef := sender
27
27
  let capabilities = (): Reventless.Capabilities.t => {
28
28
  geocode: Reventless.Capabilities.none.geocode,
29
29
  messaging: ReventlessCore.Messaging_Log_Backend.provider(~sender=messagingSenderRef.contents),
30
+ // Refusing rather than absent: the seam exists so a deployment can be asked
31
+ // what it provisions. Nothing writes back to the local user store yet, and
32
+ // answering `Unavailable` leaves the work queued and visible instead of
33
+ // recording a verdict against a person who has done nothing wrong.
34
+ identityProvider: Reventless.Capabilities.none.identityProvider,
35
+ secrets: ReventlessCore.Secrets_Node_Backend.provider,
30
36
  }
@@ -1,6 +1,7 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
3
  import * as Capabilities$Reventless from "@reventlessdev/reventless-spec/src/semantic/Capabilities.res.mjs";
4
+ import * as Secrets_Node_Backend$ReventlessCore from "@reventlessdev/reventless-core/src/adapter/Secrets/Secrets_Node_Backend.res.mjs";
4
5
  import * as Messaging_Log_Backend$ReventlessCore from "@reventlessdev/reventless-core/src/adapter/Messaging/Messaging_Log_Backend.res.mjs";
5
6
 
6
7
  let messagingSenderRef = {
@@ -14,7 +15,9 @@ function registerMessagingSender(sender) {
14
15
  function capabilities() {
15
16
  return {
16
17
  geocode: Capabilities$Reventless.none.geocode,
17
- messaging: Messaging_Log_Backend$ReventlessCore.provider(messagingSenderRef.contents)
18
+ messaging: Messaging_Log_Backend$ReventlessCore.provider(messagingSenderRef.contents),
19
+ identityProvider: Capabilities$Reventless.none.identityProvider,
20
+ secrets: Secrets_Node_Backend$ReventlessCore.provider
18
21
  };
19
22
  }
20
23
 
@@ -23,4 +26,4 @@ export {
23
26
  registerMessagingSender,
24
27
  capabilities,
25
28
  }
26
- /* Messaging_Log_Backend-ReventlessCore Not a pure module */
29
+ /* Capabilities-Reventless Not a pure module */
@@ -56,8 +56,5 @@ let register = (server: ReventlessGraphqlServer.GraphQL_ServerInstance.t): unit
56
56
  }
57
57
  })
58
58
  server.registerTypes(~sdlTypes=ReventlessCore.Platform_AdminApi.geocodeTypes)
59
- server.registerQueries(
60
- ~sdlFields=ReventlessCore.Platform_AdminApi.geocodeQueryFields,
61
- ~resolvers,
62
- )
59
+ server.registerQueries(~sdlFields=ReventlessCore.Platform_AdminApi.geocodeQueryFields, ~resolvers)
63
60
  }
@@ -40,7 +40,7 @@ let make = (
40
40
  eventCollector: eventCollectorOutputs,
41
41
  })
42
42
  },
43
- ~opts=opts,
43
+ ~opts,
44
44
  )
45
45
  }
46
46
 
@@ -25,7 +25,8 @@
25
25
  Over the cap the state is dropped and the downgrade logged — a metadata-only
26
26
  descriptor still tells the client to refetch, where a failed publish would
27
27
  tell it nothing. */
28
- let maxStateChars = 60 * 1024
28
+ let maxStateChars =
29
+ 60 * 1024
29
30
 
30
31
  /** The `@retired` field of a registered read model, or `None`.
31
32