@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
@@ -32,17 +32,14 @@ module Make = (Bus: LocalBus.T) => {
32
32
  > = (~name, ~owner as _, ~opts as _=?) => {
33
33
  // Captured when handleChannelEvent is called; used by publishJsonsAndWait to
34
34
  // run the handler inline and collect typed outcomes without going through the bus.
35
- let handleCmdsRef: ref<
36
- option<ReventlessCore.CommandTopic.jsonCommandsHandler>,
37
- > = ref(None)
35
+ let handleCmdsRef: ref<option<ReventlessCore.CommandTopic.jsonCommandsHandler>> = ref(None)
38
36
 
39
37
  let publishJsons: ReventlessInfra.CommandTopic.publishJsons = async jsons => {
40
- let _ =
41
- await jsons
42
- ->Array.map(async (cmdJson: Reventless.Message.commandJson) => {
43
- await Bus.dispatchCommand(name, ReventlessCore.CommandTopic.encodeCommandJson(cmdJson))
44
- })
45
- ->Promise.all
38
+ let _ = await jsons
39
+ ->Array.map(async (cmdJson: Reventless.Message.commandJson) => {
40
+ await Bus.dispatchCommand(name, ReventlessCore.CommandTopic.encodeCommandJson(cmdJson))
41
+ })
42
+ ->Promise.all
46
43
  }
47
44
 
48
45
  let publishJsonsStream: ReventlessInfra.CommandTopic.publishJsonsStream = stream =>
@@ -55,18 +52,20 @@ module Make = (Bus: LocalBus.T) => {
55
52
  | None =>
56
53
  // Handler not yet registered — fall back to fire-and-forget, return Pending
57
54
  let _ = await publishJsons(jsons)
58
- jsons->Array.map(cmdJson =>
59
- ReventlessCore.CommandTopic.Pending({msgId: cmdJson.meta.msgId})
60
- )
61
- | Some(handleCmds) =>
62
- await ReventlessCore.CommandTopic.runInlineAndCollect(jsons, handleCmds)
55
+ jsons->Array.map(cmdJson => ReventlessCore.CommandTopic.Pending({
56
+ msgId: cmdJson.meta.msgId,
57
+ }))
58
+ | Some(handleCmds) => await ReventlessCore.CommandTopic.runInlineAndCollect(jsons, handleCmds)
63
59
  }
64
60
  }
65
61
 
66
62
  let handleChannelEvent = (
67
63
  handleCmds: ReventlessCore.CommandTopic.jsonCommandsHandler,
68
- ): Pulumi.Output.t<ReventlessCore.Runtime.effectHandler<callbackEvent, 'context, unit, string>> => {
64
+ ): Pulumi.Output.t<
65
+ ReventlessCore.Runtime.effectHandler<callbackEvent, 'context, unit, string>,
66
+ > => {
69
67
  handleCmdsRef.contents = Some(handleCmds)
68
+
70
69
  (
71
70
  (fullBody: JSON.t, _ctx) => {
72
71
  // Pass the full body as `command` — that's what handleJsonCommands decodes
@@ -84,8 +83,7 @@ module Make = (Bus: LocalBus.T) => {
84
83
  runtimeParts,
85
84
  > = (~name as _, ~channel, ~runtime, ~resources as _, ~opts as _) => {
86
85
  Bus.registerCommandHandler(channel.parts.name, async (json, ctx) => {
87
- let handler =
88
- await runtime.parts.handlerDeferred->Deferred.await_->Effect.runPromise
86
+ let handler = await runtime.parts.handlerDeferred->Deferred.await_->Effect.runPromise
89
87
  await handler(json, ctx)
90
88
  })
91
89
  []
@@ -5,28 +5,24 @@ module Make = (Bus: LocalBus.T) => {
5
5
  let make: ReventlessCore.CommandTopic_Adapter.remoteChannelMaker = resources => {
6
6
  resources,
7
7
  remotePublish: async jsons => {
8
- let _ =
9
- await jsons
10
- ->Array.map(async (cmdJson: Reventless.Message.commandJson) => {
11
- // Encode the same way LocalCommandTopicChannel does
12
- let body = JSON.Encode.object(
13
- Dict.fromArray([
14
- ("id", JSON.Encode.string(cmdJson.id)),
15
- (
16
- "meta",
17
- cmdJson.meta->Reventless.Util_Sury.toJson(Reventless.Message.metaSchema),
18
- ),
19
- ("command", cmdJson.commandJson),
20
- ]),
21
- )
22
- // Dispatch to the first resource's name (the channel name)
23
- let channelName = switch resources {
24
- | [] => ""
25
- | resources => (resources->Array.getUnsafe(0)).name
26
- }
27
- await Bus.dispatchCommand(channelName, body)
28
- })
29
- ->Promise.all
8
+ let _ = await jsons
9
+ ->Array.map(async (cmdJson: Reventless.Message.commandJson) => {
10
+ // Encode the same way LocalCommandTopicChannel does
11
+ let body = JSON.Encode.object(
12
+ Dict.fromArray([
13
+ ("id", JSON.Encode.string(cmdJson.id)),
14
+ ("meta", cmdJson.meta->Reventless.Util_Sury.toJson(Reventless.Message.metaSchema)),
15
+ ("command", cmdJson.commandJson),
16
+ ]),
17
+ )
18
+ // Dispatch to the first resource's name (the channel name)
19
+ let channelName = switch resources {
20
+ | [] => ""
21
+ | resources => (resources->Array.getUnsafe(0)).name
22
+ }
23
+ await Bus.dispatchCommand(channelName, body)
24
+ })
25
+ ->Promise.all
30
26
  },
31
27
  }
32
28
  }
@@ -20,7 +20,7 @@ let make: Counter_Adapter.handlerMaker = (
20
20
  ~publishChannelId as _,
21
21
  ~opts as _,
22
22
  ) => {
23
- addToCounterTarget: async ({ReventlessInfra.Counter.counterId, target, targetRef}) => {
23
+ addToCounterTarget: async ({ReventlessInfra.Counter.counterId: counterId, target, targetRef}) => {
24
24
  let refKey = counterId ++ ":" ++ targetRef
25
25
  switch targetRefStore.contents->Dict.get(refKey) {
26
26
  | Some(_) => () // Already counted this (counterId, targetRef) pair — skip
@@ -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.