@reventlessdev/reventless-local 3.0.0-alpha.248 → 3.0.0-alpha.250

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 (170) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/package.json +14 -14
  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 +11 -15
  15. package/src/adapter/Auth/UserStore.res +5 -7
  16. package/src/adapter/Backend.res +1 -2
  17. package/src/adapter/CommandGenerator/CommandGeneratorResolvers_GraphQL.res +42 -26
  18. package/src/adapter/CommandGenerator/InboundTranslationResolvers_GraphQL.res +13 -3
  19. package/src/adapter/CommandTopic/LocalCommandTopicChannel.res +15 -17
  20. package/src/adapter/CommandTopic/LocalCommandTopicRemoteChannel.res +18 -22
  21. package/src/adapter/Counter/LocalCounterHandler.res +1 -1
  22. package/src/adapter/DcbEventLog/DcbEventLogStorage_InMemory.res +33 -22
  23. package/src/adapter/DcbEventLog/DcbEventLogStorage_Sqlite.res +57 -57
  24. package/src/adapter/DomainGraphQL_Server.res +35 -34
  25. package/src/adapter/DomainMCP_Server.res +45 -41
  26. package/src/adapter/EventCollector/LocalEventCollectorChannel.res +47 -36
  27. package/src/adapter/EventHistory/EventHistoryResolvers_GraphQL.res +15 -12
  28. package/src/adapter/EventLog/EventLogStorage_InMemory.res +9 -4
  29. package/src/adapter/EventLog/EventLogStorage_Sqlite.res +36 -24
  30. package/src/adapter/EventTopic/LocalEventTopicPublisher.res +9 -3
  31. package/src/adapter/Heartbeat/LocalHeartbeatRunner.res +9 -14
  32. package/src/adapter/LocalBus.res +12 -12
  33. package/src/adapter/LocalCapabilities.res.mjs +1 -1
  34. package/src/adapter/LocalGeocodeResolvers.res +1 -4
  35. package/src/adapter/LocalSideEffectHandler.res +1 -1
  36. package/src/adapter/LocalStateChangeDescriptor.res +2 -1
  37. package/src/adapter/MCP_ServerInstance.res +51 -39
  38. package/src/adapter/ObjectStore/LocalObjectStore.res +5 -2
  39. package/src/adapter/ObjectStore/ObjectStoreStorage_FileSystem.res +1 -2
  40. package/src/adapter/PlatformGraphQL_Server.res +9 -5
  41. package/src/adapter/ProjectionCheckpoint.res +8 -1
  42. package/src/adapter/ProjectionPending.res +1 -3
  43. package/src/adapter/QueryDb/LocalQueryDbStorage.res +2 -1
  44. package/src/adapter/QueryDb/QueryDbResolvers_GraphQL.res +367 -269
  45. package/src/adapter/QueryDb/QueryDbStorage_InMemory.res +5 -3
  46. package/src/adapter/QueryDb/QueryDbStorage_Sqlite.res +63 -40
  47. package/src/adapter/QueryDb/SortKey_Filter.res +14 -6
  48. package/src/adapter/Runtime/LocalAggregateRuntime_Builder.res +12 -10
  49. package/src/adapter/Runtime/LocalEventCollectorRuntime_Builder.res +8 -2
  50. package/src/adapter/Runtime/LocalPluginRuntime_Builder.res +1 -4
  51. package/src/adapter/Scheduler/LocalScheduledPublisher.res +12 -12
  52. package/src/adapter/SqliteDriver.res +16 -4
  53. package/src/adapter/Task/TaskBucket_Sqlite.res +5 -6
  54. package/src/components/Aggregate_Builder.res +6 -1
  55. package/src/components/Counter_Builder.res +1 -5
  56. package/src/components/DcbEventLog_Builder.res +5 -3
  57. package/src/components/ExtensionPoint_Builder.res +13 -15
  58. package/src/components/InboundTranslationSlice_Builder.res +3 -2
  59. package/src/components/OutboundTranslationSlice_Builder.res +3 -2
  60. package/src/components/ReadModel_Builder.res +18 -20
  61. package/src/components/StateViewSlice_Builder.res +3 -2
  62. package/src/components/Task_Builder.res +12 -13
  63. package/src/reset/LocalSeedReset.res +2 -5
  64. package/src/test/Mocks/MockDcbEventLogStorage.res +5 -1
  65. package/src/test/Mocks/MockEventLogStorage.res +8 -3
  66. package/src/test/Mocks/MockQueryDbStorage.res +3 -2
  67. package/src/test/TestRunner.res +1 -1
  68. package/tests/BakedManifestFilesTest.res +7 -10
  69. package/tests/LocalEventTapTest.res +26 -21
  70. package/tests/LocalPlatformStartTest.res +33 -28
  71. package/tests/PluginEventDecodeTest.res +3 -3
  72. package/tests/ShellConfigTest.res +30 -21
  73. package/tests/SplitApiFixtures.res +34 -10
  74. package/tests/SplitApiTest.res +92 -62
  75. package/tests/adapter/BackendParityTest.res +180 -130
  76. package/tests/adapter/CollectNEventsTest.res +6 -4
  77. package/tests/adapter/CommandAuthorizationTest.res +47 -41
  78. package/tests/adapter/CommandTopicChannelTest.res +163 -139
  79. package/tests/adapter/CounterHandlerTest.res +95 -77
  80. package/tests/adapter/DcbEventLogStorageSqliteTest.res +171 -168
  81. package/tests/adapter/DcbEventLogStorageTest.res +264 -207
  82. package/tests/adapter/DomainGraphQL_ServerTest.res +53 -43
  83. package/tests/adapter/EventCollectorChannelTest.res +153 -124
  84. package/tests/adapter/EventHistoryResolverTest.res +46 -18
  85. package/tests/adapter/EventLogSnapshotParityTest.res +57 -45
  86. package/tests/adapter/EventLogStorageSqliteTest.res +9 -10
  87. package/tests/adapter/EventTapTest.res +37 -20
  88. package/tests/adapter/EventTopicPublisherTest.res +74 -50
  89. package/tests/adapter/GraphQLSubscriptionBridgeTest.res +4 -3
  90. package/tests/adapter/GraphQL_SchemaInspectorTest.res +666 -568
  91. package/tests/adapter/GraphQL_SubscriptionResolversTest.res +105 -96
  92. package/tests/adapter/HeartbeatRunnerTest.res +99 -81
  93. package/tests/adapter/InboundTranslationMutationTest.res +4 -1
  94. package/tests/adapter/LocalAuthHttpTest.res +79 -85
  95. package/tests/adapter/LocalAuthLoginTest.res +42 -50
  96. package/tests/adapter/LocalAuthTest.res +8 -16
  97. package/tests/adapter/LocalAuthUserStoreTest.res +2 -7
  98. package/tests/adapter/LocalBusBoundedTest.res +187 -117
  99. package/tests/adapter/LocalBusPubSubTest.res +118 -76
  100. package/tests/adapter/LocalDispatchContextTest.res +1 -2
  101. package/tests/adapter/LocalEvents_ServerTest.res +242 -187
  102. package/tests/adapter/PgProjectionCatchupTest.res +44 -10
  103. package/tests/adapter/ProjectionCheckpointTest.res +261 -206
  104. package/tests/adapter/QueryDbGsiTtlTest.res +123 -83
  105. package/tests/adapter/QueryDbListPushdownParityTest.res +107 -67
  106. package/tests/adapter/QueryDbListResolverTest.res +225 -208
  107. package/tests/adapter/QueryDbResolvedFieldsTest.res +17 -14
  108. package/tests/adapter/QueryDbStorageSqliteTest.res +33 -49
  109. package/tests/adapter/QueryDbStorageTest.res +163 -117
  110. package/tests/adapter/QueryDbUnionRoundTripTest.res +8 -10
  111. package/tests/adapter/QueryEngineTest.res +179 -126
  112. package/tests/adapter/ScheduledPublisherTest.res +171 -135
  113. package/tests/adapter/ServedBucketHttpTest.res +65 -59
  114. package/tests/adapter/SortKey_FilterTest.res +19 -3
  115. package/tests/adapter/SqliteDriverTest.res +13 -10
  116. package/tests/adapter/TaskBucketTest.res +94 -80
  117. package/tests/components/aggregate/AggregateTest.res +31 -18
  118. package/tests/components/automationslice/AutomationSliceCallbackTest.res +259 -222
  119. package/tests/components/automationslice/AutomationSliceFixtures.res +4 -2
  120. package/tests/components/automationslice/AutomationSliceSelfDeadlockFixtures.res +38 -38
  121. package/tests/components/automationslice/MixedSourceAutomationSliceFixtures.res +12 -14
  122. package/tests/components/automationslice/MixedSourceAutomationSliceTest.res +8 -32
  123. package/tests/components/commandgenerator/CommandGeneratorFixtures.res +7 -5
  124. package/tests/components/commandgenerator/CommandGeneratorTest.res +40 -31
  125. package/tests/components/commandtopic/CommandTopicFixtures.res +3 -4
  126. package/tests/components/commandtopic/CommandTopicStreamFixtures.res +7 -5
  127. package/tests/components/commandtopic/CommandTopicTest.res +24 -15
  128. package/tests/components/counter/CounterFixtures.res +6 -8
  129. package/tests/components/dcb/DcbCrossPartitionFixtures.res +8 -2
  130. package/tests/components/dcb/DcbCrossPartitionReadTest.res +20 -13
  131. package/tests/components/dcb/DcbEventLogAppendStreamTest.res +12 -8
  132. package/tests/components/dcb/DcbEventLogStreamTest.res +88 -61
  133. package/tests/components/dcb/DcbFixtures.res +4 -1
  134. package/tests/components/dcb/DcbTest.res +9 -6
  135. package/tests/components/eventcollector/EventCollectorFixtures.res +0 -1
  136. package/tests/components/eventcollector/EventCollectorTest.res +6 -3
  137. package/tests/components/eventlog/EventLogAppendStreamTest.res +88 -68
  138. package/tests/components/eventlog/EventLogConflictTest.res +8 -4
  139. package/tests/components/eventlog/EventLogFixtures.res +3 -3
  140. package/tests/components/eventlog/EventLogProvisioningSeamTest.res +26 -13
  141. package/tests/components/eventlog/EventLogStreamTest.res +119 -56
  142. package/tests/components/eventlog/EventLogTest.res +20 -4
  143. package/tests/components/eventtopic/EventTopicFixtures.res +3 -3
  144. package/tests/components/eventtopic/EventTopicStreamFixtures.res +1 -2
  145. package/tests/components/eventtopic/EventTopicStreamTest.res +7 -1
  146. package/tests/components/extensionpoint/ExtensionPointFixtures.res +14 -9
  147. package/tests/components/extensionpoint/ExtensionPointTest.res +3 -2
  148. package/tests/components/heartbeat/HeartbeatFixtures.res +5 -6
  149. package/tests/components/heartbeat/HeartbeatTest.res +56 -47
  150. package/tests/components/inboundtranslationslice/InboundTranslationSliceCallbackTest.res +271 -249
  151. package/tests/components/inboundtranslationslice/InboundTranslationSliceFixtures.res +3 -2
  152. package/tests/components/outboundtranslationslice/OutboundTranslationSliceCallbackTest.res +361 -276
  153. package/tests/components/outboundtranslationslice/OutboundTranslationSliceFixtures.res +8 -10
  154. package/tests/components/outboundtranslationslice/OutboundTranslationSlicePlatformFixtures.res +37 -43
  155. package/tests/components/querydb/QueryDbFixtures.res +1 -5
  156. package/tests/components/querydb/QueryDbTest.res +2 -4
  157. package/tests/components/readmodel/DcbReadModelE2EFixtures.res +15 -18
  158. package/tests/components/readmodel/MixedSourceReadModelFixtures.res +5 -7
  159. package/tests/components/readmodel/ReadModelFixtures.res +8 -8
  160. package/tests/components/scheduler/SchedulerTest.res +76 -58
  161. package/tests/components/sideeffecthandler/SideEffectHandlerFixtures.res +4 -4
  162. package/tests/components/stateviewslice/StateViewSliceDisplayNameFixtures.res +5 -7
  163. package/tests/components/stateviewslice/StateViewSliceFixtures.res +8 -6
  164. package/tests/components/stateviewslice/StateViewSliceSubIdFixtures.res +17 -11
  165. package/tests/components/stateviewslice/StateViewSliceSubIdTest.res +12 -2
  166. package/tests/components/task/TaskFixtures.res +5 -5
  167. package/tests/components/task/TaskTest.res +95 -80
  168. package/tests/plugin/PluginsProjection_GWT.res +3 -1
  169. package/tests/plugin/UiFragments_GWT.res +6 -1
  170. package/tests/reset/LocalSeedResetTest.res +1 -0
@@ -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)
@@ -23,4 +23,4 @@ export {
23
23
  registerMessagingSender,
24
24
  capabilities,
25
25
  }
26
- /* Messaging_Log_Backend-ReventlessCore Not a pure module */
26
+ /* 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
 
@@ -26,9 +26,21 @@ type diagnostics = {
26
26
  }
27
27
 
28
28
  type t = {
29
- registerTool: (~name: string, ~definition: ReventlessCore.MCP_SchemaGenerator.mcpToolDefinition, ~handler: toolHandler) => unit,
30
- registerResource: (~name: string, ~definition: ReventlessCore.MCP_SchemaGenerator.mcpResourceDefinition, ~handler: resourceHandler) => unit,
31
- registerResourceTemplate: (~name: string, ~definition: ReventlessCore.MCP_SchemaGenerator.mcpResourceDefinition, ~handler: resourceHandler) => unit,
29
+ registerTool: (
30
+ ~name: string,
31
+ ~definition: ReventlessCore.MCP_SchemaGenerator.mcpToolDefinition,
32
+ ~handler: toolHandler,
33
+ ) => unit,
34
+ registerResource: (
35
+ ~name: string,
36
+ ~definition: ReventlessCore.MCP_SchemaGenerator.mcpResourceDefinition,
37
+ ~handler: resourceHandler,
38
+ ) => unit,
39
+ registerResourceTemplate: (
40
+ ~name: string,
41
+ ~definition: ReventlessCore.MCP_SchemaGenerator.mcpResourceDefinition,
42
+ ~handler: resourceHandler,
43
+ ) => unit,
32
44
  registerToolsFromEntries: (
33
45
  ~pluginName: string,
34
46
  ~mutationEntries: array<ReventlessInfra.Api.mutationSchemaEntry>,
@@ -248,9 +260,7 @@ let make = (~label: string="MCP"): t => {
248
260
  }
249
261
  await handler(uri)
250
262
  | None => {
251
- McpSdk.contents: [
252
- {McpSdk.uri, text: `{"error": "Resource not found: ${uri}"}`},
253
- ],
263
+ McpSdk.contents: [{McpSdk.uri, text: `{"error": "Resource not found: ${uri}"}`}],
254
264
  }
255
265
  }
256
266
  }
@@ -261,43 +271,45 @@ let make = (~label: string="MCP"): t => {
261
271
 
262
272
  let start = (~port: int=3001, ()) => {
263
273
  let httpServer = McpSdk.createHttpServer((req, res) => {
264
- let _ = (async () => {
265
- let reqMethod = req->McpSdk.method
266
- let reqUrl = req->McpSdk.url
274
+ let _ = (
275
+ async () => {
276
+ let reqMethod = req->McpSdk.method
277
+ let reqUrl = req->McpSdk.url
267
278
 
268
- res->McpSdk.setHeader("Access-Control-Allow-Origin", "*")
269
- res->McpSdk.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS")
270
- res->McpSdk.setHeader("Access-Control-Allow-Headers", "Content-Type")
279
+ res->McpSdk.setHeader("Access-Control-Allow-Origin", "*")
280
+ res->McpSdk.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS")
281
+ res->McpSdk.setHeader("Access-Control-Allow-Headers", "Content-Type")
271
282
 
272
- if reqUrl == "/mcp" || reqUrl->String.startsWith("/mcp?") {
273
- switch reqMethod {
274
- | "OPTIONS" =>
275
- res->McpSdk.setStatusCode(204)
276
- res->McpSdk.endResponseNoBody
277
- | "POST" =>
278
- let body = await McpSdk_Helpers.parseJsonBody(req)
279
- let identity = extractIdentity(req)
280
- let server = createServerInstance(identity)
281
- let transport = McpSdk.newStreamableHTTPTransport({
282
- enableJsonResponse: true,
283
- })
284
- let _ = await server->McpSdk.connect(transport)
285
- let _ = await transport->McpSdk.handleRequest(req, res, body)
286
- | "GET" =>
287
- res->McpSdk.setHeader("Content-Type", "text/plain")
288
- res->McpSdk.endResponse(`${label} server running`)
289
- | "DELETE" =>
290
- res->McpSdk.setStatusCode(200)
291
- res->McpSdk.endResponseNoBody
292
- | _ =>
293
- res->McpSdk.setStatusCode(405)
294
- res->McpSdk.endResponse("Method not allowed")
283
+ if reqUrl == "/mcp" || reqUrl->String.startsWith("/mcp?") {
284
+ switch reqMethod {
285
+ | "OPTIONS" =>
286
+ res->McpSdk.setStatusCode(204)
287
+ res->McpSdk.endResponseNoBody
288
+ | "POST" =>
289
+ let body = await McpSdk_Helpers.parseJsonBody(req)
290
+ let identity = extractIdentity(req)
291
+ let server = createServerInstance(identity)
292
+ let transport = McpSdk.newStreamableHTTPTransport({
293
+ enableJsonResponse: true,
294
+ })
295
+ let _ = await server->McpSdk.connect(transport)
296
+ let _ = await transport->McpSdk.handleRequest(req, res, body)
297
+ | "GET" =>
298
+ res->McpSdk.setHeader("Content-Type", "text/plain")
299
+ res->McpSdk.endResponse(`${label} server running`)
300
+ | "DELETE" =>
301
+ res->McpSdk.setStatusCode(200)
302
+ res->McpSdk.endResponseNoBody
303
+ | _ =>
304
+ res->McpSdk.setStatusCode(405)
305
+ res->McpSdk.endResponse("Method not allowed")
306
+ }
307
+ } else {
308
+ res->McpSdk.setStatusCode(404)
309
+ res->McpSdk.endResponse("Not found")
295
310
  }
296
- } else {
297
- res->McpSdk.setStatusCode(404)
298
- res->McpSdk.endResponse("Not found")
299
311
  }
300
- })()
312
+ )()
301
313
  })
302
314
 
303
315
  httpServer->McpSdk.listen(port, () => {
@@ -83,11 +83,14 @@ let declaredStoreList = (): array<(string, string)> => declaredStores->Dict.toAr
83
83
  // key becomes a path, and `uploads/../../etc/passwd` would otherwise escape the
84
84
  // store.
85
85
  let servedKey = (path: string): option<string> => {
86
- let trimmed = path->String.startsWith("/") ? path->String.slice(~start=1, ~end=path->String.length) : path
86
+ let trimmed =
87
+ path->String.startsWith("/") ? path->String.slice(~start=1, ~end=path->String.length) : path
87
88
  let segments = trimmed->String.split("/")
88
89
  let underAPrefix =
89
90
  servedPrefixes.contents->Array.some(p =>
90
- p->String.length > 0 && trimmed->String.startsWith(p ++ "/") && trimmed->String.length > p->String.length + 1
91
+ p->String.length > 0 &&
92
+ trimmed->String.startsWith(p ++ "/") &&
93
+ trimmed->String.length > p->String.length + 1
91
94
  )
92
95
  if underAPrefix && segments->Array.every(seg => seg != "" && seg != "." && seg != "..") {
93
96
  Some(trimmed)
@@ -178,8 +178,7 @@ let topLevelPrefixes = (~root: string): array<string> =>
178
178
  | _ => None
179
179
  } {
180
180
  | None => []
181
- | Some(entries) =>
182
- entries->Array.filter(NodeFs.isDirectory)->Array.map(NodeFs.direntName)
181
+ | Some(entries) => entries->Array.filter(NodeFs.isDirectory)->Array.map(NodeFs.direntName)
183
182
  }
184
183
 
185
184
  /** Deletes every object under `prefix`, returning how many were removed. Goes
@@ -27,16 +27,20 @@
27
27
  // — without the wrapper, anonymous callers could trigger admin mutations
28
28
  // (Plugin_Activate / _Deactivate, etc.) just by hitting the platform port.
29
29
 
30
- let instance: ReventlessGraphqlServer.GraphQL_ServerInstance.t = ReventlessGraphqlServer.GraphQL_ServerInstance.make(~label="GraphQL:Platform")
30
+ let instance: ReventlessGraphqlServer.GraphQL_ServerInstance.t = ReventlessGraphqlServer.GraphQL_ServerInstance.make(
31
+ ~label="GraphQL:Platform",
32
+ )
31
33
 
32
34
  // Wrap a resolver dict so every value enforces the Admin group. Keys are
33
35
  // preserved; resolvers fall through to the underlying instance once the
34
36
  // group check passes.
35
- let wrapAdmin = (
36
- resolvers: dict<ReventlessGraphqlServer.GraphQL_ServerInstance.resolverFn>,
37
- ): dict<ReventlessGraphqlServer.GraphQL_ServerInstance.resolverFn> => {
37
+ let wrapAdmin = (resolvers: dict<ReventlessGraphqlServer.GraphQL_ServerInstance.resolverFn>): dict<
38
+ ReventlessGraphqlServer.GraphQL_ServerInstance.resolverFn,
39
+ > => {
38
40
  let wrapped = Dict.make()
39
- resolvers->Dict.toArray->Array.forEach(((k, v)) =>
41
+ resolvers
42
+ ->Dict.toArray
43
+ ->Array.forEach(((k, v)) =>
40
44
  wrapped->Dict.set(k, Auth_GraphqlContext.requireGroup(~group="Admin", v))
41
45
  )
42
46
  wrapped