@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
@@ -30,31 +30,30 @@ type rowState = {
30
30
  geolocation?: geolocation,
31
31
  }
32
32
 
33
- let rowStateSchemaWithAnnotations =
34
- rowStateSchema->S.Metadata.set(
35
- ~id=Reventless.StateAnnotations.stateAnnotationsId,
36
- {
37
- ids: ["productId"],
38
- compositeIds: [],
39
- subIds: [],
40
- compositeSubIds: [],
41
- indexes: [],
42
- hidden: [],
43
- summary: [],
44
- drillTargets: [],
45
- drillTargetKeys: [],
46
- collapsed: [],
47
- scan: ["status"],
48
- scanSort: ["name"],
49
- semantic: [],
50
- metric: [],
51
- lifecycle: None,
52
- groupBy: None,
53
- visibility: None,
54
- live: None,
55
- retired: None,
56
- },
57
- )
33
+ let rowStateSchemaWithAnnotations = rowStateSchema->S.Metadata.set(
34
+ ~id=Reventless.StateAnnotations.stateAnnotationsId,
35
+ {
36
+ ids: ["productId"],
37
+ compositeIds: [],
38
+ subIds: [],
39
+ compositeSubIds: [],
40
+ indexes: [],
41
+ hidden: [],
42
+ summary: [],
43
+ drillTargets: [],
44
+ drillTargetKeys: [],
45
+ collapsed: [],
46
+ scan: ["status"],
47
+ scanSort: ["name"],
48
+ semantic: [],
49
+ metric: [],
50
+ lifecycle: None,
51
+ groupBy: None,
52
+ visibility: None,
53
+ live: None,
54
+ retired: None,
55
+ },
56
+ )
58
57
 
59
58
  // ─────────────────────────────────────────────────────────────
60
59
  // Helpers — invoke a registered resolver with synthetic args/ctx
@@ -63,12 +62,22 @@ let rowStateSchemaWithAnnotations =
63
62
  // Inject a synthetic authenticated identity so the resolver's spec-level
64
63
  // authorization check (AllowAuthenticated, see Resolvers.make below) lets
65
64
  // these list-resolver mechanics tests through.
66
- let emptyCtx: JSON.t = JSON.Encode.object(Dict.fromArray([
67
- ("request", JSON.Encode.object(Dict.fromArray([
68
- ("headers", JSON.Encode.object(Dict.make())),
69
- ]))),
70
- ("identity", ({...Reventless.Identity.anonymous, userId: "test-user", username: "test-user", groups: ["User"]}: Reventless.Identity.t)->Obj.magic),
71
- ]))
65
+ let emptyCtx: JSON.t = JSON.Encode.object(
66
+ Dict.fromArray([
67
+ ("request", JSON.Encode.object(Dict.fromArray([("headers", JSON.Encode.object(Dict.make()))]))),
68
+ (
69
+ "identity",
70
+ (
71
+ {
72
+ ...Reventless.Identity.anonymous,
73
+ userId: "test-user",
74
+ username: "test-user",
75
+ groups: ["User"],
76
+ }: Reventless.Identity.t
77
+ )->Obj.magic,
78
+ ),
79
+ ]),
80
+ )
72
81
 
73
82
  let argsOf = (entries: array<(string, JSON.t)>): JSON.t =>
74
83
  JSON.Encode.object(Dict.fromArray(entries))
@@ -97,7 +106,10 @@ let edgeNodeField = (edge: JSON.t, field: string): string =>
97
106
  ->Option.getOr("")
98
107
 
99
108
  let edges2Find = (edges: array<JSON.t>, pid: string): JSON.t =>
100
- edges->Array.find(e => e->getField("node")->Option.flatMap(n => n->getString("productId")) === Some(pid))
109
+ edges
110
+ ->Array.find(e =>
111
+ e->getField("node")->Option.flatMap(n => n->getString("productId")) === Some(pid)
112
+ )
101
113
  ->Option.getOr(JSON.Encode.null)
102
114
 
103
115
  let edgeNodeJson = (edge: JSON.t, field: string): option<JSON.t> =>
@@ -216,8 +228,7 @@ describe("QueryDb list resolver — keyset pagination", () => {
216
228
 
217
229
  testPromise("first: 2 over a 5-row fixture returns 2 edges and hasNextPage = true", async () => {
218
230
  let (resolver, _) = await buildFixture(~name="PageA")
219
- let response =
220
- await resolver(JSON.Encode.null, argsOf([("first", numJson(2))]), emptyCtx)
231
+ let response = await resolver(JSON.Encode.null, argsOf([("first", numJson(2))]), emptyCtx)
221
232
  let edges = getEdges(response)
222
233
  expect(edges->Array.length)->toBe(2)
223
234
  expect(pageInfoBool(response, "hasNextPage"))->toBe(true)
@@ -229,16 +240,14 @@ describe("QueryDb list resolver — keyset pagination", () => {
229
240
 
230
241
  testPromise("after: <endCursor> walks forward without overlap", async () => {
231
242
  let (resolver, _) = await buildFixture(~name="PageB")
232
- let page1 =
233
- await resolver(JSON.Encode.null, argsOf([("first", numJson(2))]), emptyCtx)
243
+ let page1 = await resolver(JSON.Encode.null, argsOf([("first", numJson(2))]), emptyCtx)
234
244
  let endCursor1 = pageInfoString(page1, "endCursor")->Option.getOr("")
235
245
 
236
- let page2 =
237
- await resolver(
238
- JSON.Encode.null,
239
- argsOf([("first", numJson(2)), ("after", strJson(endCursor1))]),
240
- emptyCtx,
241
- )
246
+ let page2 = await resolver(
247
+ JSON.Encode.null,
248
+ argsOf([("first", numJson(2)), ("after", strJson(endCursor1))]),
249
+ emptyCtx,
250
+ )
242
251
  let edges2 = getEdges(page2)
243
252
  expect(edges2->Array.length)->toBe(2)
244
253
  expect(edgeNodeField(edges2->Array.getUnsafe(0), "productId"))->toBe("p-3")
@@ -246,12 +255,11 @@ describe("QueryDb list resolver — keyset pagination", () => {
246
255
  expect(pageInfoBool(page2, "hasNextPage"))->toBe(true)
247
256
 
248
257
  let endCursor2 = pageInfoString(page2, "endCursor")->Option.getOr("")
249
- let page3 =
250
- await resolver(
251
- JSON.Encode.null,
252
- argsOf([("first", numJson(2)), ("after", strJson(endCursor2))]),
253
- emptyCtx,
254
- )
258
+ let page3 = await resolver(
259
+ JSON.Encode.null,
260
+ argsOf([("first", numJson(2)), ("after", strJson(endCursor2))]),
261
+ emptyCtx,
262
+ )
255
263
  let edges3 = getEdges(page3)
256
264
  expect(edges3->Array.length)->toBe(1)
257
265
  expect(edgeNodeField(edges3->Array.getUnsafe(0), "productId"))->toBe("p-5")
@@ -261,31 +269,27 @@ describe("QueryDb list resolver — keyset pagination", () => {
261
269
  testPromise("last + before walks backward and flips hasPreviousPage", async () => {
262
270
  let (resolver, _) = await buildFixture(~name="PageC")
263
271
  // Walk to page 3 first to grab a startCursor we can walk back from.
264
- let page1 =
265
- await resolver(JSON.Encode.null, argsOf([("first", numJson(2))]), emptyCtx)
272
+ let page1 = await resolver(JSON.Encode.null, argsOf([("first", numJson(2))]), emptyCtx)
266
273
  let endCursor1 = pageInfoString(page1, "endCursor")->Option.getOr("")
267
- let page2 =
268
- await resolver(
269
- JSON.Encode.null,
270
- argsOf([("first", numJson(2)), ("after", strJson(endCursor1))]),
271
- emptyCtx,
272
- )
274
+ let page2 = await resolver(
275
+ JSON.Encode.null,
276
+ argsOf([("first", numJson(2)), ("after", strJson(endCursor1))]),
277
+ emptyCtx,
278
+ )
273
279
  let endCursor2 = pageInfoString(page2, "endCursor")->Option.getOr("")
274
- let page3 =
275
- await resolver(
276
- JSON.Encode.null,
277
- argsOf([("first", numJson(2)), ("after", strJson(endCursor2))]),
278
- emptyCtx,
279
- )
280
+ let page3 = await resolver(
281
+ JSON.Encode.null,
282
+ argsOf([("first", numJson(2)), ("after", strJson(endCursor2))]),
283
+ emptyCtx,
284
+ )
280
285
  let startCursor3 = pageInfoString(page3, "startCursor")->Option.getOr("")
281
286
 
282
287
  // last: 2 + before: <startCursor3> should return p-3, p-4 in forward order.
283
- let backPage =
284
- await resolver(
285
- JSON.Encode.null,
286
- argsOf([("last", numJson(2)), ("before", strJson(startCursor3))]),
287
- emptyCtx,
288
- )
288
+ let backPage = await resolver(
289
+ JSON.Encode.null,
290
+ argsOf([("last", numJson(2)), ("before", strJson(startCursor3))]),
291
+ emptyCtx,
292
+ )
289
293
  let backEdges = getEdges(backPage)
290
294
  expect(backEdges->Array.length)->toBe(2)
291
295
  expect(edgeNodeField(backEdges->Array.getUnsafe(0), "productId"))->toBe("p-3")
@@ -298,12 +302,11 @@ describe("QueryDb list resolver — keyset pagination", () => {
298
302
 
299
303
  // Walk one more step back — should land on p-1, p-2 with hasPreviousPage = false.
300
304
  let backStart = pageInfoString(backPage, "startCursor")->Option.getOr("")
301
- let firstPage =
302
- await resolver(
303
- JSON.Encode.null,
304
- argsOf([("last", numJson(2)), ("before", strJson(backStart))]),
305
- emptyCtx,
306
- )
305
+ let firstPage = await resolver(
306
+ JSON.Encode.null,
307
+ argsOf([("last", numJson(2)), ("before", strJson(backStart))]),
308
+ emptyCtx,
309
+ )
307
310
  let firstEdges = getEdges(firstPage)
308
311
  expect(firstEdges->Array.length)->toBe(2)
309
312
  expect(edgeNodeField(firstEdges->Array.getUnsafe(0), "productId"))->toBe("p-1")
@@ -314,12 +317,11 @@ describe("QueryDb list resolver — keyset pagination", () => {
314
317
  testPromise("filter.<field>Eq + first / after paginates only the narrowed subset", async () => {
315
318
  let (resolver, _) = await buildFixture(~name="PageD")
316
319
  let activeFilter = JSON.Encode.object(Dict.fromArray([("statusEq", strJson("active"))]))
317
- let page1 =
318
- await resolver(
319
- JSON.Encode.null,
320
- argsOf([("filter", activeFilter), ("first", numJson(2))]),
321
- emptyCtx,
322
- )
320
+ let page1 = await resolver(
321
+ JSON.Encode.null,
322
+ argsOf([("filter", activeFilter), ("first", numJson(2))]),
323
+ emptyCtx,
324
+ )
323
325
  let edges1 = getEdges(page1)
324
326
  expect(edges1->Array.length)->toBe(2)
325
327
  // active rows: p-1, p-2, p-4 (id-ascending). First page should be p-1, p-2.
@@ -328,16 +330,11 @@ describe("QueryDb list resolver — keyset pagination", () => {
328
330
  expect(pageInfoBool(page1, "hasNextPage"))->toBe(true)
329
331
 
330
332
  let endCursor1 = pageInfoString(page1, "endCursor")->Option.getOr("")
331
- let page2 =
332
- await resolver(
333
- JSON.Encode.null,
334
- argsOf([
335
- ("filter", activeFilter),
336
- ("first", numJson(2)),
337
- ("after", strJson(endCursor1)),
338
- ]),
339
- emptyCtx,
340
- )
333
+ let page2 = await resolver(
334
+ JSON.Encode.null,
335
+ argsOf([("filter", activeFilter), ("first", numJson(2)), ("after", strJson(endCursor1))]),
336
+ emptyCtx,
337
+ )
341
338
  let edges2 = getEdges(page2)
342
339
  expect(edges2->Array.length)->toBe(1)
343
340
  expect(edgeNodeField(edges2->Array.getUnsafe(0), "productId"))->toBe("p-4")
@@ -348,16 +345,14 @@ describe("QueryDb list resolver — keyset pagination", () => {
348
345
  let (resolver, _) = await buildFixture(~name="PageE")
349
346
  // orderBy field: name, direction: DESC. Names sorted DESC:
350
347
  // Echo (p-3), Delta (p-5), Charlie (p-1), Bravo (p-4), Alpha (p-2).
351
- let orderBy = JSON.Encode.object(Dict.fromArray([
352
- ("field", strJson("name")),
353
- ("direction", strJson("DESC")),
354
- ]))
355
- let page1 =
356
- await resolver(
357
- JSON.Encode.null,
358
- argsOf([("orderBy", orderBy), ("first", numJson(2))]),
359
- emptyCtx,
360
- )
348
+ let orderBy = JSON.Encode.object(
349
+ Dict.fromArray([("field", strJson("name")), ("direction", strJson("DESC"))]),
350
+ )
351
+ let page1 = await resolver(
352
+ JSON.Encode.null,
353
+ argsOf([("orderBy", orderBy), ("first", numJson(2))]),
354
+ emptyCtx,
355
+ )
361
356
  let edges1 = getEdges(page1)
362
357
  expect(edges1->Array.length)->toBe(2)
363
358
  expect(edgeNodeField(edges1->Array.getUnsafe(0), "name"))->toBe("Echo")
@@ -365,16 +360,11 @@ describe("QueryDb list resolver — keyset pagination", () => {
365
360
  expect(pageInfoBool(page1, "hasNextPage"))->toBe(true)
366
361
 
367
362
  let endCursor1 = pageInfoString(page1, "endCursor")->Option.getOr("")
368
- let page2 =
369
- await resolver(
370
- JSON.Encode.null,
371
- argsOf([
372
- ("orderBy", orderBy),
373
- ("first", numJson(2)),
374
- ("after", strJson(endCursor1)),
375
- ]),
376
- emptyCtx,
377
- )
363
+ let page2 = await resolver(
364
+ JSON.Encode.null,
365
+ argsOf([("orderBy", orderBy), ("first", numJson(2)), ("after", strJson(endCursor1))]),
366
+ emptyCtx,
367
+ )
378
368
  let edges2 = getEdges(page2)
379
369
  expect(edges2->Array.length)->toBe(2)
380
370
  expect(edgeNodeField(edges2->Array.getUnsafe(0), "name"))->toBe("Charlie")
@@ -412,59 +402,66 @@ describe("QueryDb list resolver — SQLite push-down path", () => {
412
402
  }
413
403
 
414
404
  testPromise("first:2 then after walks forward without overlap", () =>
415
- withSqlite(async () => {
416
- let (resolver, _) = await buildFixture(~name="SqlPageA")
417
- let page1 = await resolver(JSON.Encode.null, argsOf([("first", numJson(2))]), emptyCtx)
418
- let edges1 = getEdges(page1)
419
- expect(edges1->Array.length)->toBe(2)
420
- expect(edgeNodeField(edges1->Array.getUnsafe(0), "productId"))->toBe("p-1")
421
- expect(edgeNodeField(edges1->Array.getUnsafe(1), "productId"))->toBe("p-2")
422
- expect(pageInfoBool(page1, "hasNextPage"))->toBe(true)
423
-
424
- let endCursor1 = pageInfoString(page1, "endCursor")->Option.getOr("")
425
- let page2 =
426
- await resolver(
405
+ withSqlite(
406
+ async () => {
407
+ let (resolver, _) = await buildFixture(~name="SqlPageA")
408
+ let page1 = await resolver(JSON.Encode.null, argsOf([("first", numJson(2))]), emptyCtx)
409
+ let edges1 = getEdges(page1)
410
+ expect(edges1->Array.length)->toBe(2)
411
+ expect(edgeNodeField(edges1->Array.getUnsafe(0), "productId"))->toBe("p-1")
412
+ expect(edgeNodeField(edges1->Array.getUnsafe(1), "productId"))->toBe("p-2")
413
+ expect(pageInfoBool(page1, "hasNextPage"))->toBe(true)
414
+
415
+ let endCursor1 = pageInfoString(page1, "endCursor")->Option.getOr("")
416
+ let page2 = await resolver(
427
417
  JSON.Encode.null,
428
418
  argsOf([("first", numJson(2)), ("after", strJson(endCursor1))]),
429
419
  emptyCtx,
430
420
  )
431
- let edges2 = getEdges(page2)
432
- expect(edges2->Array.length)->toBe(2)
433
- expect(edgeNodeField(edges2->Array.getUnsafe(0), "productId"))->toBe("p-3")
434
- expect(edgeNodeField(edges2->Array.getUnsafe(1), "productId"))->toBe("p-4")
435
- })
421
+ let edges2 = getEdges(page2)
422
+ expect(edges2->Array.length)->toBe(2)
423
+ expect(edgeNodeField(edges2->Array.getUnsafe(0), "productId"))->toBe("p-3")
424
+ expect(edgeNodeField(edges2->Array.getUnsafe(1), "productId"))->toBe("p-4")
425
+ },
426
+ )
436
427
  )
437
428
 
438
429
  testPromise("orderBy name DESC + first:2 returns the reverse-sorted head", () =>
439
- withSqlite(async () => {
440
- let (resolver, _) = await buildFixture(~name="SqlPageB")
441
- let orderBy = JSON.Encode.object(
442
- Dict.fromArray([("field", strJson("name")), ("direction", strJson("DESC"))]),
443
- )
444
- let page1 =
445
- await resolver(
430
+ withSqlite(
431
+ async () => {
432
+ let (resolver, _) = await buildFixture(~name="SqlPageB")
433
+ let orderBy = JSON.Encode.object(
434
+ Dict.fromArray([("field", strJson("name")), ("direction", strJson("DESC"))]),
435
+ )
436
+ let page1 = await resolver(
446
437
  JSON.Encode.null,
447
438
  argsOf([("orderBy", orderBy), ("first", numJson(2))]),
448
439
  emptyCtx,
449
440
  )
450
- let edges1 = getEdges(page1)
451
- expect(edges1->Array.length)->toBe(2)
452
- expect(edgeNodeField(edges1->Array.getUnsafe(0), "name"))->toBe("Echo")
453
- expect(edgeNodeField(edges1->Array.getUnsafe(1), "name"))->toBe("Delta")
454
- })
441
+ let edges1 = getEdges(page1)
442
+ expect(edges1->Array.length)->toBe(2)
443
+ expect(edgeNodeField(edges1->Array.getUnsafe(0), "name"))->toBe("Echo")
444
+ expect(edgeNodeField(edges1->Array.getUnsafe(1), "name"))->toBe("Delta")
445
+ },
446
+ )
455
447
  )
456
448
 
457
449
  testPromise("statusEq active narrows the pushed page", () =>
458
- withSqlite(async () => {
459
- let (resolver, _) = await buildFixture(~name="SqlPageC")
460
- let activeFilter = JSON.Encode.object(Dict.fromArray([("statusEq", strJson("active"))]))
461
- let response =
462
- await resolver(JSON.Encode.null, argsOf([("filter", activeFilter)]), emptyCtx)
463
- let edges = getEdges(response)
464
- expect(edges->Array.length)->toBe(3)
465
- expect(edgeNodeField(edges->Array.getUnsafe(0), "productId"))->toBe("p-1")
466
- expect(edgeNodeField(edges->Array.getUnsafe(2), "productId"))->toBe("p-4")
467
- })
450
+ withSqlite(
451
+ async () => {
452
+ let (resolver, _) = await buildFixture(~name="SqlPageC")
453
+ let activeFilter = JSON.Encode.object(Dict.fromArray([("statusEq", strJson("active"))]))
454
+ let response = await resolver(
455
+ JSON.Encode.null,
456
+ argsOf([("filter", activeFilter)]),
457
+ emptyCtx,
458
+ )
459
+ let edges = getEdges(response)
460
+ expect(edges->Array.length)->toBe(3)
461
+ expect(edgeNodeField(edges->Array.getUnsafe(0), "productId"))->toBe("p-1")
462
+ expect(edgeNodeField(edges->Array.getUnsafe(2), "productId"))->toBe("p-4")
463
+ },
464
+ )
468
465
  )
469
466
  })
470
467
 
@@ -491,7 +488,11 @@ describe("QueryDb by-index resolver", () => {
491
488
 
492
489
  testPromise("answers the rows carrying the index value, as edges", async () => {
493
490
  let resolver = await indexResolver(~name="IdxA")
494
- let response = await resolver(JSON.Encode.null, argsOf([("status", strJson("active"))]), emptyCtx)
491
+ let response = await resolver(
492
+ JSON.Encode.null,
493
+ argsOf([("status", strJson("active"))]),
494
+ emptyCtx,
495
+ )
495
496
  let edges = getEdges(response)
496
497
  expect(edges->Array.length)->toBe(3)
497
498
  expect(edgeNodeField(edges->Array.getUnsafe(0), "productId"))->toBe("p-1")
@@ -499,25 +500,23 @@ describe("QueryDb by-index resolver", () => {
499
500
 
500
501
  testPromise("pages forward on first/after like every other connection", async () => {
501
502
  let resolver = await indexResolver(~name="IdxB")
502
- let page1 =
503
- await resolver(
504
- JSON.Encode.null,
505
- argsOf([("status", strJson("active")), ("first", numJson(2))]),
506
- emptyCtx,
507
- )
503
+ let page1 = await resolver(
504
+ JSON.Encode.null,
505
+ argsOf([("status", strJson("active")), ("first", numJson(2))]),
506
+ emptyCtx,
507
+ )
508
508
  expect(getEdges(page1)->Array.length)->toBe(2)
509
509
  expect(pageInfoBool(page1, "hasNextPage"))->toBe(true)
510
510
 
511
- let page2 =
512
- await resolver(
513
- JSON.Encode.null,
514
- argsOf([
515
- ("status", strJson("active")),
516
- ("first", numJson(2)),
517
- ("after", strJson(pageInfoString(page1, "endCursor")->Option.getOr(""))),
518
- ]),
519
- emptyCtx,
520
- )
511
+ let page2 = await resolver(
512
+ JSON.Encode.null,
513
+ argsOf([
514
+ ("status", strJson("active")),
515
+ ("first", numJson(2)),
516
+ ("after", strJson(pageInfoString(page1, "endCursor")->Option.getOr(""))),
517
+ ]),
518
+ emptyCtx,
519
+ )
521
520
  let edges2 = getEdges(page2)
522
521
  expect(edges2->Array.length)->toBe(1)
523
522
  expect(edgeNodeField(edges2->Array.getUnsafe(0), "productId"))->toBe("p-4")
@@ -530,13 +529,20 @@ describe("QueryDb by-index resolver", () => {
530
529
  // it, so the whole row would vanish rather than error.
531
530
  testPromise("carries a union field's member type through", async () => {
532
531
  let resolver = await indexResolver(~name="IdxU")
533
- let response = await resolver(JSON.Encode.null, argsOf([("status", strJson("active"))]), emptyCtx)
532
+ let response = await resolver(
533
+ JSON.Encode.null,
534
+ argsOf([("status", strJson("active"))]),
535
+ emptyCtx,
536
+ )
534
537
  let edges = getEdges(response)
535
538
  let seeded =
536
- edges->Array.find(e => edgeNodeField(e, "productId") === "p-1")->Option.getOr(JSON.Encode.null)
539
+ edges
540
+ ->Array.find(e => edgeNodeField(e, "productId") === "p-1")
541
+ ->Option.getOr(JSON.Encode.null)
537
542
  let geo = edgeNodeJson(seeded, "geolocation")->Option.flatMap(JSON.Decode.object)
538
- expect(geo->Option.flatMap(o => o->Dict.get("__typename"))->Option.flatMap(JSON.Decode.string))
539
- ->toEqual(Some("GeolocationLocated"))
543
+ expect(
544
+ geo->Option.flatMap(o => o->Dict.get("__typename"))->Option.flatMap(JSON.Decode.string),
545
+ )->toEqual(Some("GeolocationLocated"))
540
546
  expect(
541
547
  geo
542
548
  ->Option.flatMap(o => o->Dict.get("lat"))
@@ -547,7 +553,11 @@ describe("QueryDb by-index resolver", () => {
547
553
  // An absent optional union is left absent rather than stamped into an arm.
548
554
  testPromise("leaves an absent union absent", async () => {
549
555
  let resolver = await indexResolver(~name="IdxV")
550
- let response = await resolver(JSON.Encode.null, argsOf([("status", strJson("active"))]), emptyCtx)
556
+ let response = await resolver(
557
+ JSON.Encode.null,
558
+ argsOf([("status", strJson("active"))]),
559
+ emptyCtx,
560
+ )
551
561
  let other = edges2Find(getEdges(response), "p-2")
552
562
  // Assert the row is real first, so an absent field cannot pass as an absent row.
553
563
  expect(edgeNodeField(other, "name"))->toBe("Alpha")
@@ -560,12 +570,11 @@ describe("QueryDb by-index resolver", () => {
560
570
  testPromise("refuses backward paging instead of answering forward", async () => {
561
571
  let resolver = await indexResolver(~name="IdxC")
562
572
  let outcome = try {
563
- let _ =
564
- await resolver(
565
- JSON.Encode.null,
566
- argsOf([("status", strJson("active")), ("last", numJson(2))]),
567
- emptyCtx,
568
- )
573
+ let _ = await resolver(
574
+ JSON.Encode.null,
575
+ argsOf([("status", strJson("active")), ("last", numJson(2))]),
576
+ emptyCtx,
577
+ )
569
578
  Ok()
570
579
  } catch {
571
580
  | e => Error(e->JsExn.fromException->Option.flatMap(JsExn.message)->Option.getOr(""))
@@ -573,7 +582,9 @@ describe("QueryDb by-index resolver", () => {
573
582
  switch outcome {
574
583
  | Ok() => expect("no error")->toBe("a BAD_USER_INPUT refusal")
575
584
  | Error(message) =>
576
- expect(message->String.includes("Backward pagination (last/before) is not supported"))->toBe(true)
585
+ expect(message->String.includes("Backward pagination (last/before) is not supported"))->toBe(
586
+ true,
587
+ )
577
588
  }
578
589
  })
579
590
  })
@@ -594,31 +605,30 @@ type lineState = {
594
605
  geolocation?: geolocation,
595
606
  }
596
607
 
597
- let lineStateSchemaWithAnnotations =
598
- lineStateSchema->S.Metadata.set(
599
- ~id=Reventless.StateAnnotations.stateAnnotationsId,
600
- {
601
- ids: ["orderId"],
602
- compositeIds: [],
603
- subIds: ["lineNo"],
604
- compositeSubIds: [],
605
- indexes: [],
606
- hidden: [],
607
- summary: [],
608
- drillTargets: [],
609
- drillTargetKeys: [],
610
- collapsed: [],
611
- scan: [],
612
- scanSort: [],
613
- semantic: [],
614
- metric: [],
615
- lifecycle: None,
616
- groupBy: None,
617
- visibility: None,
618
- live: None,
619
- retired: None,
620
- },
621
- )
608
+ let lineStateSchemaWithAnnotations = lineStateSchema->S.Metadata.set(
609
+ ~id=Reventless.StateAnnotations.stateAnnotationsId,
610
+ {
611
+ ids: ["orderId"],
612
+ compositeIds: [],
613
+ subIds: ["lineNo"],
614
+ compositeSubIds: [],
615
+ indexes: [],
616
+ hidden: [],
617
+ summary: [],
618
+ drillTargets: [],
619
+ drillTargetKeys: [],
620
+ collapsed: [],
621
+ scan: [],
622
+ scanSort: [],
623
+ semantic: [],
624
+ metric: [],
625
+ lifecycle: None,
626
+ groupBy: None,
627
+ visibility: None,
628
+ live: None,
629
+ retired: None,
630
+ },
631
+ )
622
632
 
623
633
  let buildSubIdFixture = async (~name: string) => {
624
634
  module Bus = LocalBus.Make()
@@ -679,7 +689,12 @@ let buildSubIdFixture = async (~name: string) => {
679
689
  // optional field absent.
680
690
  let _ = await ops.save(
681
691
  "o-1",
682
- {orderId: "o-1", lineNo: "l-1", label: "First", geolocation: Located({lat: 48.2082, lng: 16.3738})},
692
+ {
693
+ orderId: "o-1",
694
+ lineNo: "l-1",
695
+ label: "First",
696
+ geolocation: Located({lat: 48.2082, lng: 16.3738}),
697
+ },
683
698
  Init,
684
699
  None,
685
700
  )
@@ -713,10 +728,12 @@ describe("QueryDb sub-id resolver — the Items door", () => {
713
728
  let response = await resolver(JSON.Encode.null, argsOf([("id", strJson("o-1"))]), emptyCtx)
714
729
  let first = getEdges(response)->Array.getUnsafe(0)
715
730
  let geo = edgeNodeJson(first, "geolocation")->Option.flatMap(JSON.Decode.object)
716
- expect(geo->Option.flatMap(o => o->Dict.get("__typename"))->Option.flatMap(JSON.Decode.string))
717
- ->toEqual(Some("GeolocationLocated"))
718
- expect(geo->Option.flatMap(o => o->Dict.get("lat"))->Option.flatMap(JSON.Decode.float))
719
- ->toEqual(Some(48.2082))
731
+ expect(
732
+ geo->Option.flatMap(o => o->Dict.get("__typename"))->Option.flatMap(JSON.Decode.string),
733
+ )->toEqual(Some("GeolocationLocated"))
734
+ expect(
735
+ geo->Option.flatMap(o => o->Dict.get("lat"))->Option.flatMap(JSON.Decode.float),
736
+ )->toEqual(Some(48.2082))
720
737
  })
721
738
 
722
739
  testPromise("leaves an absent union absent", async () => {