@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.
- package/CHANGELOG.md +30 -0
- package/package.json +13 -13
- package/src/BakedManifest.res +5 -6
- package/src/EmitCapabilities.res +3 -1
- package/src/LocalPlatformRegistry.res +3 -1
- package/src/LocalPlatformStart.res +7 -6
- package/src/LocalSeedTarget.res +3 -1
- package/src/Platform.res +291 -215
- package/src/ShellConfig.res +10 -17
- package/src/UiHints.res +8 -14
- package/src/UiSlots.res +10 -17
- package/src/adapter/Api/LocalEvents_Server.res +5 -3
- package/src/adapter/Api/LocalGraphQL_Adapter.res +4 -4
- package/src/adapter/Auth/LocalAuth.res +12 -16
- package/src/adapter/Auth/LocalAuth.res.mjs +2 -1
- package/src/adapter/Auth/UserStore.res +13 -40
- package/src/adapter/Auth/UserStore.res.mjs +11 -67
- package/src/adapter/Backend.res +1 -2
- package/src/adapter/CommandGenerator/CommandGeneratorResolvers_GraphQL.res +42 -26
- package/src/adapter/CommandGenerator/InboundTranslationResolvers_GraphQL.res +13 -3
- package/src/adapter/CommandTopic/LocalCommandTopicChannel.res +15 -17
- package/src/adapter/CommandTopic/LocalCommandTopicRemoteChannel.res +18 -22
- package/src/adapter/Counter/LocalCounterHandler.res +1 -1
- package/src/adapter/DcbEventLog/DcbEventLogStorage_InMemory.res +33 -22
- package/src/adapter/DcbEventLog/DcbEventLogStorage_Sqlite.res +57 -57
- package/src/adapter/DomainGraphQL_Server.res +35 -34
- package/src/adapter/DomainMCP_Server.res +45 -41
- package/src/adapter/EventCollector/LocalEventCollectorChannel.res +47 -36
- package/src/adapter/EventHistory/EventHistoryResolvers_GraphQL.res +15 -12
- package/src/adapter/EventLog/EventLogStorage_InMemory.res +9 -4
- package/src/adapter/EventLog/EventLogStorage_Sqlite.res +36 -24
- package/src/adapter/EventTopic/LocalEventTopicPublisher.res +9 -3
- package/src/adapter/Heartbeat/LocalHeartbeatRunner.res +9 -14
- package/src/adapter/LocalBus.res +12 -12
- package/src/adapter/LocalCapabilities.res +6 -0
- package/src/adapter/LocalCapabilities.res.mjs +5 -2
- package/src/adapter/LocalGeocodeResolvers.res +1 -4
- package/src/adapter/LocalSideEffectHandler.res +1 -1
- package/src/adapter/LocalStateChangeDescriptor.res +2 -1
- package/src/adapter/MCP_ServerInstance.res +51 -39
- package/src/adapter/ObjectStore/LocalObjectStore.res +5 -2
- package/src/adapter/ObjectStore/ObjectStoreStorage_FileSystem.res +1 -2
- package/src/adapter/PlatformGraphQL_Server.res +10 -6
- package/src/adapter/PlatformGraphQL_Server.res.mjs +2 -1
- package/src/adapter/ProjectionCheckpoint.res +8 -1
- package/src/adapter/ProjectionPending.res +1 -3
- package/src/adapter/QueryDb/LocalQueryDbStorage.res +2 -1
- package/src/adapter/QueryDb/QueryDbResolvers_GraphQL.res +367 -269
- package/src/adapter/QueryDb/QueryDbStorage_InMemory.res +5 -3
- package/src/adapter/QueryDb/QueryDbStorage_Sqlite.res +63 -40
- package/src/adapter/QueryDb/SortKey_Filter.res +14 -6
- package/src/adapter/Runtime/LocalAggregateRuntime_Builder.res +12 -10
- package/src/adapter/Runtime/LocalEventCollectorRuntime_Builder.res +8 -2
- package/src/adapter/Runtime/LocalPluginRuntime_Builder.res +1 -4
- package/src/adapter/Scheduler/LocalScheduledPublisher.res +12 -12
- package/src/adapter/SqliteDriver.res +16 -4
- package/src/adapter/Task/TaskBucket_Sqlite.res +5 -6
- package/src/components/Aggregate_Builder.res +6 -1
- package/src/components/Counter_Builder.res +1 -5
- package/src/components/DcbEventLog_Builder.res +5 -3
- package/src/components/ExtensionPoint_Builder.res +13 -15
- package/src/components/InboundTranslationSlice_Builder.res +3 -2
- package/src/components/OutboundTranslationSlice_Builder.res +3 -2
- package/src/components/ReadModel_Builder.res +18 -20
- package/src/components/StateViewSlice_Builder.res +3 -2
- package/src/components/Task_Builder.res +12 -13
- package/src/reset/LocalSeedReset.res +2 -5
- package/src/test/Mocks/MockDcbEventLogStorage.res +5 -1
- package/src/test/Mocks/MockEventLogStorage.res +8 -3
- package/src/test/Mocks/MockQueryDbStorage.res +3 -2
- package/src/test/TestRunner.res +1 -1
- package/tests/BakedManifestFilesTest.res +7 -10
- package/tests/LocalEventTapTest.res +26 -21
- package/tests/LocalPlatformStartTest.res +33 -28
- package/tests/PluginEventDecodeTest.res +3 -3
- package/tests/ShellConfigTest.res +30 -21
- package/tests/SplitApiFixtures.res +34 -10
- package/tests/SplitApiTest.res +92 -62
- package/tests/adapter/BackendParityTest.res +180 -130
- package/tests/adapter/CollectNEventsTest.res +6 -4
- package/tests/adapter/CommandAuthorizationTest.res +47 -41
- package/tests/adapter/CommandTopicChannelTest.res +163 -139
- package/tests/adapter/CounterHandlerTest.res +95 -77
- package/tests/adapter/DcbEventLogStorageSqliteTest.res +171 -168
- package/tests/adapter/DcbEventLogStorageTest.res +264 -207
- package/tests/adapter/DomainGraphQL_ServerTest.res +53 -43
- package/tests/adapter/EventCollectorChannelTest.res +153 -124
- package/tests/adapter/EventHistoryResolverTest.res +46 -18
- package/tests/adapter/EventLogSnapshotParityTest.res +57 -45
- package/tests/adapter/EventLogStorageSqliteTest.res +9 -10
- package/tests/adapter/EventTapTest.res +37 -20
- package/tests/adapter/EventTopicPublisherTest.res +74 -50
- package/tests/adapter/GraphQLSubscriptionBridgeTest.res +4 -3
- package/tests/adapter/GraphQL_SchemaInspectorTest.res +666 -568
- package/tests/adapter/GraphQL_SubscriptionResolversTest.res +105 -96
- package/tests/adapter/HeartbeatRunnerTest.res +99 -81
- package/tests/adapter/InboundTranslationMutationTest.res +4 -1
- package/tests/adapter/LocalAuthHttpTest.res +79 -85
- package/tests/adapter/LocalAuthLoginTest.res +42 -50
- package/tests/adapter/LocalAuthTest.res +8 -16
- package/tests/adapter/LocalAuthUserStoreTest.res +2 -7
- package/tests/adapter/LocalBusBoundedTest.res +187 -117
- package/tests/adapter/LocalBusPubSubTest.res +118 -76
- package/tests/adapter/LocalDispatchContextTest.res +1 -2
- package/tests/adapter/LocalEvents_ServerTest.res +242 -187
- package/tests/adapter/PgProjectionCatchupTest.res +44 -10
- package/tests/adapter/ProjectionCheckpointTest.res +261 -206
- package/tests/adapter/QueryDbGsiTtlTest.res +123 -83
- package/tests/adapter/QueryDbListPushdownParityTest.res +107 -67
- package/tests/adapter/QueryDbListResolverTest.res +225 -208
- package/tests/adapter/QueryDbResolvedFieldsTest.res +17 -14
- package/tests/adapter/QueryDbStorageSqliteTest.res +33 -49
- package/tests/adapter/QueryDbStorageTest.res +163 -117
- package/tests/adapter/QueryDbUnionRoundTripTest.res +8 -10
- package/tests/adapter/QueryEngineTest.res +179 -126
- package/tests/adapter/ScheduledPublisherTest.res +171 -135
- package/tests/adapter/ServedBucketHttpTest.res +65 -59
- package/tests/adapter/SortKey_FilterTest.res +19 -3
- package/tests/adapter/SqliteDriverTest.res +13 -10
- package/tests/adapter/TaskBucketTest.res +94 -80
- package/tests/components/aggregate/AggregateTest.res +31 -18
- package/tests/components/automationslice/AutomationSliceCallbackTest.res +259 -222
- package/tests/components/automationslice/AutomationSliceFixtures.res +4 -2
- package/tests/components/automationslice/AutomationSliceSelfDeadlockFixtures.res +38 -38
- package/tests/components/automationslice/MixedSourceAutomationSliceFixtures.res +12 -14
- package/tests/components/automationslice/MixedSourceAutomationSliceTest.res +8 -32
- package/tests/components/commandgenerator/CommandGeneratorFixtures.res +7 -5
- package/tests/components/commandgenerator/CommandGeneratorTest.res +40 -31
- package/tests/components/commandtopic/CommandTopicFixtures.res +3 -4
- package/tests/components/commandtopic/CommandTopicStreamFixtures.res +7 -5
- package/tests/components/commandtopic/CommandTopicTest.res +24 -15
- package/tests/components/counter/CounterFixtures.res +6 -8
- package/tests/components/dcb/DcbCrossPartitionFixtures.res +8 -2
- package/tests/components/dcb/DcbCrossPartitionReadTest.res +20 -13
- package/tests/components/dcb/DcbEventLogAppendStreamTest.res +12 -8
- package/tests/components/dcb/DcbEventLogStreamTest.res +88 -61
- package/tests/components/dcb/DcbFixtures.res +4 -1
- package/tests/components/dcb/DcbTest.res +9 -6
- package/tests/components/eventcollector/EventCollectorFixtures.res +0 -1
- package/tests/components/eventcollector/EventCollectorTest.res +6 -3
- package/tests/components/eventlog/EventLogAppendStreamTest.res +88 -68
- package/tests/components/eventlog/EventLogConflictTest.res +8 -4
- package/tests/components/eventlog/EventLogFixtures.res +3 -3
- package/tests/components/eventlog/EventLogProvisioningSeamTest.res +26 -13
- package/tests/components/eventlog/EventLogStreamTest.res +119 -56
- package/tests/components/eventlog/EventLogTest.res +20 -4
- package/tests/components/eventtopic/EventTopicFixtures.res +3 -3
- package/tests/components/eventtopic/EventTopicStreamFixtures.res +1 -2
- package/tests/components/eventtopic/EventTopicStreamTest.res +7 -1
- package/tests/components/extensionpoint/ExtensionPointFixtures.res +14 -9
- package/tests/components/extensionpoint/ExtensionPointTest.res +3 -2
- package/tests/components/heartbeat/HeartbeatFixtures.res +5 -6
- package/tests/components/heartbeat/HeartbeatTest.res +56 -47
- package/tests/components/inboundtranslationslice/InboundTranslationSliceCallbackTest.res +271 -249
- package/tests/components/inboundtranslationslice/InboundTranslationSliceFixtures.res +3 -2
- package/tests/components/outboundtranslationslice/OutboundTranslationSliceCallbackTest.res +361 -276
- package/tests/components/outboundtranslationslice/OutboundTranslationSliceFixtures.res +8 -10
- package/tests/components/outboundtranslationslice/OutboundTranslationSlicePlatformFixtures.res +37 -43
- package/tests/components/querydb/QueryDbFixtures.res +1 -5
- package/tests/components/querydb/QueryDbTest.res +2 -4
- package/tests/components/readmodel/DcbReadModelE2EFixtures.res +15 -18
- package/tests/components/readmodel/MixedSourceReadModelFixtures.res +5 -7
- package/tests/components/readmodel/ReadModelFixtures.res +8 -8
- package/tests/components/scheduler/SchedulerTest.res +76 -58
- package/tests/components/sideeffecthandler/SideEffectHandlerFixtures.res +4 -4
- package/tests/components/stateviewslice/StateViewSliceDisplayNameFixtures.res +5 -7
- package/tests/components/stateviewslice/StateViewSliceFixtures.res +8 -6
- package/tests/components/stateviewslice/StateViewSliceSubIdFixtures.res +17 -11
- package/tests/components/stateviewslice/StateViewSliceSubIdTest.res +12 -2
- package/tests/components/task/TaskFixtures.res +5 -5
- package/tests/components/task/TaskTest.res +95 -80
- package/tests/plugin/PluginsProjection_GWT.res +3 -1
- package/tests/plugin/UiFragments_GWT.res +6 -1
- 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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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(
|
|
67
|
-
|
|
68
|
-
("headers", JSON.Encode.object(Dict.make())),
|
|
69
|
-
|
|
70
|
-
|
|
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
|
|
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
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
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
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
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
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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(
|
|
352
|
-
("field", strJson("name")),
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
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
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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(
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
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
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
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(
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
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(
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
await resolver(
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
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(
|
|
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
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
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
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
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(
|
|
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
|
|
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(
|
|
539
|
-
|
|
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(
|
|
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
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
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(
|
|
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
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
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
|
-
{
|
|
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(
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
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 () => {
|