@reventlessdev/reventless-local 3.0.0-alpha.249 → 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 +8 -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 +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
@@ -82,16 +82,18 @@ let makeStorage = (~name as _name, ~indexes as _, ~partitionTag as _, ~opts as _
82
82
  | Some(tags) if tags->Array.length > 0 =>
83
83
  // Events carrying ALL of the clause's tags = intersection of the tags'
84
84
  // posting lists; a missing tag ⇒ empty ⇒ this clause adds nothing.
85
- let lists = tags->Array.map(t =>
86
- byTag->Dict.get(tagPostingKey(t.key, t.value))->Option.getOr([])
87
- )
85
+ let lists =
86
+ tags->Array.map(t => byTag->Dict.get(tagPostingKey(t.key, t.value))->Option.getOr([]))
88
87
  if lists->Array.some(l => l->Array.length == 0) {
89
88
  ()
90
89
  } else {
91
- let sorted = lists->Array.toSorted((a, b) => Int.toFloat(a->Array.length - b->Array.length))
90
+ let sorted =
91
+ lists->Array.toSorted((a, b) => Int.toFloat(a->Array.length - b->Array.length))
92
92
  let acc0 = sorted->Array.getUnsafe(0)
93
93
  let folded =
94
- sorted->Array.slice(~start=1, ~end=sorted->Array.length)->Array.reduce(acc0, intersectSorted)
94
+ sorted
95
+ ->Array.slice(~start=1, ~end=sorted->Array.length)
96
+ ->Array.reduce(acc0, intersectSorted)
95
97
  addAll(folded)
96
98
  }
97
99
  | _ =>
@@ -100,7 +102,8 @@ let makeStorage = (~name as _name, ~indexes as _, ~partitionTag as _, ~opts as _
100
102
  // clause is unconstrained and matches everything.
101
103
  switch clause.eventTypes {
102
104
  | Some(types) => types->Array.forEach(t => addAll(byType->Dict.get(t)->Option.getOr([])))
103
- | None => events.contents->Array.forEachWithIndex((_, i) => acc->Dict.set(Int.toString(i), i))
105
+ | None =>
106
+ events.contents->Array.forEachWithIndex((_, i) => acc->Dict.set(Int.toString(i), i))
104
107
  }
105
108
  }
106
109
  })
@@ -147,21 +150,23 @@ let makeStorage = (~name as _name, ~indexes as _, ~partitionTag as _, ~opts as _
147
150
  Error(ReventlessInfra.DcbEventLog.Conflict)
148
151
  } else {
149
152
  let base = events.contents->Array.length
150
- let storedEvents =
151
- newEvents->Array.mapWithIndex((event: DcbEventLog_Adapter.rawStoredEvent, j) => {
152
- position := position.contents + 1
153
- let pos = position.contents->Int.toString
154
- let stored: DcbEventLog_Adapter.rawSequencedEvent = {
155
- position: pos,
156
- eventType: event.eventType,
157
- data: event.data,
158
- tags: event.tags,
159
- meta: event.meta,
160
- recordedAt: Message.nowAsISOString(),
161
- }
162
- indexEvent(base + j, stored)
163
- stored
164
- })
153
+ let storedEvents = newEvents->Array.mapWithIndex((
154
+ event: DcbEventLog_Adapter.rawStoredEvent,
155
+ j,
156
+ ) => {
157
+ position := position.contents + 1
158
+ let pos = position.contents->Int.toString
159
+ let stored: DcbEventLog_Adapter.rawSequencedEvent = {
160
+ position: pos,
161
+ eventType: event.eventType,
162
+ data: event.data,
163
+ tags: event.tags,
164
+ meta: event.meta,
165
+ recordedAt: Message.nowAsISOString(),
166
+ }
167
+ indexEvent(base + j, stored)
168
+ stored
169
+ })
165
170
  events := events.contents->Array.concat(storedEvents)
166
171
  Ok(position.contents->Int.toString)
167
172
  }
@@ -191,7 +196,13 @@ let makeStorage = (~name as _name, ~indexes as _, ~partitionTag as _, ~opts as _
191
196
  // routing. The flag is accepted for interface parity and ignored.
192
197
  // Pure in-memory storageMaker (no BackendState dispatch, no Bus). Backend
193
198
  // selection lives in LocalDcbEventLogStorage.Make.
194
- let make: DcbEventLog_Adapter.storageMaker = (~name, ~indexes, ~partitionTag, ~crossPartitionTagKeys as _=?, ~opts) => {
199
+ let make: DcbEventLog_Adapter.storageMaker = (
200
+ ~name,
201
+ ~indexes,
202
+ ~partitionTag,
203
+ ~crossPartitionTagKeys as _=?,
204
+ ~opts,
205
+ ) => {
195
206
  let (_, _, storage) = makeStorage(~name, ~indexes, ~partitionTag, ~opts)
196
207
  storage
197
208
  }
@@ -37,9 +37,7 @@ let ensureSchema = (db: SqliteDriver.t) => {
37
37
  // Serves the per-position tag hydration (the `WHERE log_name = ? AND position
38
38
  // IN (...)` batch). The `_by_kv` index leads with tag_key/tag_value, so it
39
39
  // cannot answer a position lookup — without this the batch scanned dcb_tag.
40
- db->SqliteDriver.exec(
41
- "CREATE INDEX IF NOT EXISTS dcb_tag_by_pos ON dcb_tag(log_name, position)",
42
- )
40
+ db->SqliteDriver.exec("CREATE INDEX IF NOT EXISTS dcb_tag_by_pos ON dcb_tag(log_name, position)")
43
41
  }
44
42
 
45
43
  let posToInt = (pos: string) => pos->Int.fromString->Option.getOr(0)
@@ -81,11 +79,10 @@ let sqlEscape = (s: string) => s->String.replaceAll("'", "''")
81
79
  // Build the WHERE clause and bound params for one query clause.
82
80
  // Returns (whereSql, params) where whereSql includes a leading `(`/trailing `)`
83
81
  // and uses `dcb_event` table aliases throughout.
84
- let buildClauseSql = (
85
- ~logName: string,
86
- ~clause: DcbTag.queryItem,
87
- ~after: option<int>,
88
- ): (string, array<JSON.t>) => {
82
+ let buildClauseSql = (~logName: string, ~clause: DcbTag.queryItem, ~after: option<int>): (
83
+ string,
84
+ array<JSON.t>,
85
+ ) => {
89
86
  let parts = ref(["log_name = ?"])
90
87
  let params = ref([JSON.Encode.string(logName)])
91
88
 
@@ -126,11 +123,10 @@ let buildClauseSql = (
126
123
  // The WHERE expression (+ bound params) for a query — shared by the row-fetching
127
124
  // read path and the LIMIT-1 existence check the append-condition uses, so both
128
125
  // match the exact same rows.
129
- let buildQueryWhere = (
130
- ~logName: string,
131
- ~query: DcbTag.query,
132
- ~after: option<int>,
133
- ): (string, array<JSON.t>) => {
126
+ let buildQueryWhere = (~logName: string, ~query: DcbTag.query, ~after: option<int>): (
127
+ string,
128
+ array<JSON.t>,
129
+ ) => {
134
130
  if query->Array.length == 0 {
135
131
  let baseWhere = switch after {
136
132
  | Some(_) => "log_name = ? AND position > ?"
@@ -150,11 +146,10 @@ let buildQueryWhere = (
150
146
  }
151
147
  }
152
148
 
153
- let buildQuerySql = (
154
- ~logName: string,
155
- ~query: DcbTag.query,
156
- ~after: option<int>,
157
- ): (string, array<JSON.t>) => {
149
+ let buildQuerySql = (~logName: string, ~query: DcbTag.query, ~after: option<int>): (
150
+ string,
151
+ array<JSON.t>,
152
+ ) => {
158
153
  let (where, params) = buildQueryWhere(~logName, ~query, ~after)
159
154
  (
160
155
  `SELECT position, event_type, data, meta, recorded_at FROM dcb_event WHERE ${where} ORDER BY position ASC`,
@@ -171,19 +166,21 @@ let makeStorage = (
171
166
  ) => {
172
167
  ensureSchema(db)
173
168
 
174
- let insertEventStmt = db->SqliteDriver.prepare(
175
- "INSERT INTO dcb_event(log_name, position, event_type, data, meta, recorded_at) VALUES(?,?,?,?,?,?)",
176
- )
169
+ let insertEventStmt =
170
+ db->SqliteDriver.prepare(
171
+ "INSERT INTO dcb_event(log_name, position, event_type, data, meta, recorded_at) VALUES(?,?,?,?,?,?)",
172
+ )
177
173
  let lastRowidStmt = db->SqliteDriver.prepare("SELECT last_insert_rowid() AS r")
178
- let insertTagStmt = db->SqliteDriver.prepare(
179
- "INSERT INTO dcb_tag(log_name, position, tag_key, tag_value) VALUES(?,?,?,?)",
180
- )
181
- let maxPositionStmt = db->SqliteDriver.prepare(
182
- "SELECT COALESCE(MAX(position), 0) AS m FROM dcb_event WHERE log_name = ?",
183
- )
184
- let headPositionStmt = db->SqliteDriver.prepare(
185
- "SELECT MAX(position) AS m FROM dcb_event WHERE log_name = ?",
186
- )
174
+ let insertTagStmt =
175
+ db->SqliteDriver.prepare(
176
+ "INSERT INTO dcb_tag(log_name, position, tag_key, tag_value) VALUES(?,?,?,?)",
177
+ )
178
+ let maxPositionStmt =
179
+ db->SqliteDriver.prepare(
180
+ "SELECT COALESCE(MAX(position), 0) AS m FROM dcb_event WHERE log_name = ?",
181
+ )
182
+ let headPositionStmt =
183
+ db->SqliteDriver.prepare("SELECT MAX(position) AS m FROM dcb_event WHERE log_name = ?")
187
184
 
188
185
  // Prepared-statement cache keyed on SQL text. `runQuery`, the batched tag
189
186
  // hydration, and the conflict check all build SQL whose shape varies with the
@@ -218,8 +215,7 @@ let makeStorage = (
218
215
  if positions->Array.length > 0 {
219
216
  let placeholders = positions->Array.map(_ => "?")->Array.join(",")
220
217
  let sql = `SELECT position, tag_key, tag_value FROM dcb_tag WHERE log_name = ? AND position IN (${placeholders}) ORDER BY position ASC, rowid ASC`
221
- let params =
222
- [JSON.Encode.string(name)]->Array.concat(positions->Array.map(JSON.Encode.int))
218
+ let params = [JSON.Encode.string(name)]->Array.concat(positions->Array.map(JSON.Encode.int))
223
219
  preparedFor(sql)
224
220
  ->SqliteDriver.all(params)
225
221
  ->Array.forEach(row => {
@@ -249,7 +245,9 @@ let makeStorage = (
249
245
  byPos
250
246
  }
251
247
 
252
- let runQuery = (~query: DcbTag.query, ~after: option<int>): array<DcbEventLog_Adapter.rawSequencedEvent> => {
248
+ let runQuery = (~query: DcbTag.query, ~after: option<int>): array<
249
+ DcbEventLog_Adapter.rawSequencedEvent,
250
+ > => {
253
251
  let (sql, params) = buildQuerySql(~logName=name, ~query, ~after)
254
252
  let rows = preparedFor(sql)->SqliteDriver.all(params)
255
253
  let positions = rows->Array.map(row =>
@@ -284,14 +282,17 @@ let makeStorage = (
284
282
  | Some(JSON.String(s)) => s
285
283
  | _ => ""
286
284
  }
287
- ({
288
- DcbEventLog_Adapter.position: Int.toString(position),
289
- eventType,
290
- data,
291
- tags: tagsByPos->Dict.get(Int.toString(position))->Option.getOr([]),
292
- meta,
293
- recordedAt,
294
- }: DcbEventLog_Adapter.rawSequencedEvent)
285
+
286
+ (
287
+ {
288
+ DcbEventLog_Adapter.position: Int.toString(position),
289
+ eventType,
290
+ data,
291
+ tags: tagsByPos->Dict.get(Int.toString(position))->Option.getOr([]),
292
+ meta,
293
+ recordedAt,
294
+ }: DcbEventLog_Adapter.rawSequencedEvent
295
+ )
295
296
  })
296
297
  }
297
298
 
@@ -305,10 +306,7 @@ let makeStorage = (
305
306
  ->Option.isSome
306
307
  }
307
308
 
308
- let read = async (
309
- ~query: DcbTag.query,
310
- ~after=?,
311
- ): DcbEventLog_Adapter.rawReadResult => {
309
+ let read = async (~query: DcbTag.query, ~after=?): DcbEventLog_Adapter.rawReadResult => {
312
310
  let afterInt = after->Option.map(posToInt)
313
311
  let events = runQuery(~query, ~after=afterInt)
314
312
  let head = currentMaxPosition()
@@ -319,10 +317,10 @@ let makeStorage = (
319
317
  }
320
318
  }
321
319
 
322
- let append = async (
323
- newEvents: array<DcbEventLog_Adapter.rawStoredEvent>,
324
- ~condition=?,
325
- ): result<DcbTag.sequencePosition, ReventlessInfra.DcbEventLog.appendError> => {
320
+ let append = async (newEvents: array<DcbEventLog_Adapter.rawStoredEvent>, ~condition=?): result<
321
+ DcbTag.sequencePosition,
322
+ ReventlessInfra.DcbEventLog.appendError,
323
+ > => {
326
324
  let result = ref(Ok(""))
327
325
  // (msgId, dcb_event rowid) per inserted event, for the projection
328
326
  // checkpoint's DCB-axis pending set. The rowid is read immediately after
@@ -363,14 +361,16 @@ let makeStorage = (
363
361
  }
364
362
  | None => ()
365
363
  }
366
- event.tags->Array.forEach(tag => {
367
- insertTagStmt->SqliteDriver.run([
368
- JSON.Encode.string(name),
369
- JSON.Encode.int(pos),
370
- JSON.Encode.string(tag.key),
371
- JSON.Encode.string(tag.value),
372
- ])
373
- })
364
+ event.tags->Array.forEach(
365
+ tag => {
366
+ insertTagStmt->SqliteDriver.run([
367
+ JSON.Encode.string(name),
368
+ JSON.Encode.int(pos),
369
+ JSON.Encode.string(tag.key),
370
+ JSON.Encode.string(tag.value),
371
+ ])
372
+ },
373
+ )
374
374
  lastPos := pos
375
375
  })
376
376
 
@@ -37,7 +37,6 @@ type nodeResponse
37
37
  // mangled by utf8 decoding (setEncoding is deliberately NOT called).
38
38
  @send external _onDataBuf: (nodeRequest, @as("data") _, NodeBuffer.t => unit) => unit = "on"
39
39
 
40
-
41
40
  let readBody = (req: nodeRequest, onBody: string => unit): unit => {
42
41
  let buf = ref("")
43
42
  req->_setEncoding("utf8")
@@ -65,10 +64,7 @@ type requestHandler = (nodeRequest, nodeResponse) => unit
65
64
  // -- /__inmemory/login + /logout handlers -----------------------------------
66
65
 
67
66
  let _writeJson = (res: nodeResponse, ~status: int, body: JSON.t): unit => {
68
- res->writeHead(
69
- status,
70
- {"Content-Type": "application/json", "Access-Control-Allow-Origin": "*"},
71
- )
67
+ res->writeHead(status, {"Content-Type": "application/json", "Access-Control-Allow-Origin": "*"})
72
68
  res->end_(body->JSON.stringify)
73
69
  }
74
70
 
@@ -109,16 +105,12 @@ let handleLogin = (req: nodeRequest, res: nodeResponse): unit =>
109
105
  | Some(i) => i
110
106
  | None => Reventless.Identity.anonymous
111
107
  }
112
- let identityJson =
113
- identity->Reventless.Util_Sury.toJson(Reventless.Identity.schema)
108
+ let identityJson = identity->Reventless.Util_Sury.toJson(Reventless.Identity.schema)
114
109
  _writeJson(
115
110
  res,
116
111
  ~status=200,
117
112
  JSON.Encode.object(
118
- Dict.fromArray([
119
- ("token", JSON.Encode.string(token)),
120
- ("identity", identityJson),
121
- ]),
113
+ Dict.fromArray([("token", JSON.Encode.string(token)), ("identity", identityJson)]),
122
114
  ),
123
115
  )
124
116
  }
@@ -169,10 +161,7 @@ let handleSwitchRole = (req: nodeRequest, res: nodeResponse): unit =>
169
161
  JSON.Encode.object(
170
162
  Dict.fromArray([
171
163
  ("token", JSON.Encode.string(newToken)),
172
- (
173
- "identity",
174
- identity->Reventless.Util_Sury.toJson(Reventless.Identity.schema),
175
- ),
164
+ ("identity", identity->Reventless.Util_Sury.toJson(Reventless.Identity.schema)),
176
165
  ]),
177
166
  ),
178
167
  )
@@ -205,8 +194,7 @@ let _corsWriteHeaders = {
205
194
  }
206
195
 
207
196
  let handleObjectPut = (req: nodeRequest, res: nodeResponse, ~key: string): unit => {
208
- let contentType =
209
- req.headers->Dict.get("content-type")->Option.getOr("application/octet-stream")
197
+ let contentType = req.headers->Dict.get("content-type")->Option.getOr("application/octet-stream")
210
198
  readBodyBuf(req, bytes => {
211
199
  LocalObjectStore.put(~key, ~bytes, ~contentType)
212
200
  res->writeHead(200, _corsWriteHeaders)
@@ -239,17 +227,22 @@ let handleObjectGet = (res: nodeResponse, ~key: string): unit =>
239
227
  let _isInvalidBearer = (req: nodeRequest): bool =>
240
228
  switch req.headers->Dict.get("authorization") {
241
229
  | Some(h) if String.startsWith(h, "Bearer ") =>
242
- let token =
243
- String.slice(h, ~start=7, ~end=String.length(h))->String.trim
230
+ let token = String.slice(h, ~start=7, ~end=String.length(h))->String.trim
244
231
  LocalAuth.Login.verifyAndDecode(token)->Option.isNone
245
232
  | _ => false
246
233
  }
247
234
 
248
235
  // Shared dispatch for start(). Order matters: built-in
249
236
  // endpoints win over the yoga catch-all so they aren't shadowed by graphql.
250
- let _dispatch = (req: nodeRequest, res: nodeResponse, yoga: YG.yoga, getSdl: unit => string): unit => {
237
+ let _dispatch = (
238
+ req: nodeRequest,
239
+ res: nodeResponse,
240
+ yoga: YG.yoga,
241
+ getSdl: unit => string,
242
+ ): unit => {
251
243
  // Strip query string before path matching.
252
244
  let path = req.url->String.split("?")->Array.get(0)->Option.getOr(req.url)
245
+
253
246
  // /__inmemory/login is the path that *issues* tokens — it never carries
254
247
  // one. Every other path must reject an unverifiable Bearer with 401 so
255
248
  // the host-shell's `on401` logout path can fire on stale tokens.
@@ -257,9 +250,7 @@ let _dispatch = (req: nodeRequest, res: nodeResponse, yoga: YG.yoga, getSdl: uni
257
250
  _writeJson(
258
251
  res,
259
252
  ~status=401,
260
- JSON.Encode.object(
261
- Dict.fromArray([("error", JSON.Encode.string("Invalid bearer token"))]),
262
- ),
253
+ JSON.Encode.object(Dict.fromArray([("error", JSON.Encode.string("Invalid bearer token"))])),
263
254
  )
264
255
  } else if path == "/sdl" {
265
256
  res->writeHead(200, {"Content-Type": "text/plain", "Access-Control-Allow-Origin": "*"})
@@ -429,8 +420,7 @@ let relabelScope = (~from: string, ~to_: string) =>
429
420
  ->Dict.toArray
430
421
  ->Array.forEach(((typeName, byField)) =>
431
422
  switch existing.fieldResolvers->Dict.get(typeName) {
432
- | Some(target) =>
433
- byField->Dict.toArray->Array.forEach(((k, v)) => target->Dict.set(k, v))
423
+ | Some(target) => byField->Dict.toArray->Array.forEach(((k, v)) => target->Dict.set(k, v))
434
424
  | None => existing.fieldResolvers->Dict.set(typeName, byField)
435
425
  }
436
426
  )
@@ -483,7 +473,9 @@ let registerFieldResolvers = (~typeName: string, ~resolvers: dict<resolverFn>) =
483
473
  let getFieldResolver = (~typeName: string, fieldName: string): option<resolverFn> =>
484
474
  buckets.contents
485
475
  ->Dict.valuesToArray
486
- ->Array.findMap(b => b.fieldResolvers->Dict.get(typeName)->Option.flatMap(d => d->Dict.get(fieldName)))
476
+ ->Array.findMap(b =>
477
+ b.fieldResolvers->Dict.get(typeName)->Option.flatMap(d => d->Dict.get(fieldName))
478
+ )
487
479
 
488
480
  /** Look up a registered mutation resolver by field name (used by MCP_Server).
489
481
  Searches all scope buckets. */
@@ -575,8 +567,7 @@ let assembleBucketSdl = (
575
567
  sections->Array.push(typeDefinitions->Array.join("\n\n"))
576
568
  }
577
569
  if withRootDefaults || queryFields->Array.length > 0 {
578
- let queries =
579
- queryFields->Array.length > 0 ? queryFields->Array.join("\n") : " _noop: String"
570
+ let queries = queryFields->Array.length > 0 ? queryFields->Array.join("\n") : " _noop: String"
580
571
  sections->Array.push(`type Query {\n${queries}\n}`)
581
572
  }
582
573
  if withRootDefaults || mutationFields->Array.length > 0 {
@@ -737,8 +728,14 @@ let start = (~port: int=4000, ~contextFactory as _: option<YG.contextFactory>=?,
737
728
  // original Error even when the client response is masked — is never swallowed
738
729
  // again), while verbose debug/info stay gated on GRAPHQL_DEBUG.
739
730
  let yogaLogging: YG.yogaLogger = {
740
- debug: a => if debug { log.debug(~comp="GraphQL:Domain", YG.logArgToString(a)) },
741
- info: a => if debug { log.info(~comp="GraphQL:Domain", YG.logArgToString(a)) },
731
+ debug: a =>
732
+ if debug {
733
+ log.debug(~comp="GraphQL:Domain", YG.logArgToString(a))
734
+ },
735
+ info: a =>
736
+ if debug {
737
+ log.info(~comp="GraphQL:Domain", YG.logArgToString(a))
738
+ },
742
739
  warn: a => log.warn(~comp="GraphQL:Domain", YG.logArgToString(a)),
743
740
  error: a => log.error(~comp="GraphQL:Domain", YG.logArgToString(a)),
744
741
  }
@@ -760,15 +757,19 @@ let start = (~port: int=4000, ~contextFactory as _: option<YG.contextFactory>=?,
760
757
  let detail = err->JsExn.message->Option.getOr("unknown error")
761
758
  log.error(
762
759
  ~comp="GraphQL:Domain",
763
- `failed to bind port ${port->Int.toString}: ${detail} — ` ++
764
- `is another server (dev server, prior test) already listening there?`,
760
+ `failed to bind port ${port->Int.toString}: ${detail} — ` ++ `is another server (dev server, prior test) already listening there?`,
765
761
  )
766
762
  // Re-raise so the failure is loud instead of a silently-unbound server whose
767
763
  // requests fall through to whatever else holds the port.
768
- JsError.throwWithMessage(`DomainGraphQL_Server could not bind port ${port->Int.toString}: ${detail}`)
764
+ JsError.throwWithMessage(
765
+ `DomainGraphQL_Server could not bind port ${port->Int.toString}: ${detail}`,
766
+ )
769
767
  })
770
768
  server->YG.listen(port, () =>
771
- log.info(~comp="GraphQL:Domain", `listening on http://localhost:${port->Int.toString}/graphql (SDL: /sdl)`)
769
+ log.info(
770
+ ~comp="GraphQL:Domain",
771
+ `listening on http://localhost:${port->Int.toString}/graphql (SDL: /sdl)`,
772
+ )
772
773
  )
773
774
  // Local AppSync Events transport (subscribe WS). Attached here so every
774
775
  // start mode (split, unified, replay) carries it.
@@ -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
  })