@reventlessdev/reventless-local 3.0.0-alpha.115 → 3.0.0-alpha.117

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 (59) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/package.json +8 -8
  3. package/src/Platform.res +91 -17
  4. package/src/Platform.res.mjs +89 -30
  5. package/src/adapter/DcbEventLog/DcbEventLogStorage_InMemory.res +102 -17
  6. package/src/adapter/DcbEventLog/DcbEventLogStorage_InMemory.res.mjs +105 -22
  7. package/src/adapter/DcbEventLog/DcbEventLogStorage_Sqlite.res +113 -39
  8. package/src/adapter/DcbEventLog/DcbEventLogStorage_Sqlite.res.mjs +87 -23
  9. package/src/adapter/EventCollector/LocalEventCollectorChannel.res +33 -0
  10. package/src/adapter/EventCollector/LocalEventCollectorChannel.res.mjs +44 -0
  11. package/src/adapter/EventLog/EventLogStorage_InMemory.res +23 -2
  12. package/src/adapter/EventLog/EventLogStorage_InMemory.res.mjs +20 -3
  13. package/src/adapter/EventLog/EventLogStorage_Sqlite.res +132 -24
  14. package/src/adapter/EventLog/EventLogStorage_Sqlite.res.mjs +119 -37
  15. package/src/adapter/LocalBus.res +62 -0
  16. package/src/adapter/LocalBus.res.mjs +120 -0
  17. package/src/adapter/ProjectionCheckpoint.res +350 -0
  18. package/src/adapter/ProjectionCheckpoint.res.mjs +327 -0
  19. package/src/adapter/ProjectionPending.res +60 -0
  20. package/src/adapter/ProjectionPending.res.mjs +64 -0
  21. package/src/adapter/QueryDb/QueryDbListQuery.res +261 -0
  22. package/src/adapter/QueryDb/QueryDbListQuery.res.mjs +212 -0
  23. package/src/adapter/QueryDb/QueryDbResolvers_GraphQL.res +54 -286
  24. package/src/adapter/QueryDb/QueryDbResolvers_GraphQL.res.mjs +28 -190
  25. package/src/adapter/QueryDb/QueryDbStorage_InMemory.res +32 -6
  26. package/src/adapter/QueryDb/QueryDbStorage_InMemory.res.mjs +30 -14
  27. package/src/adapter/QueryDb/QueryDbStorage_Sqlite.res +163 -11
  28. package/src/adapter/QueryDb/QueryDbStorage_Sqlite.res.mjs +101 -3
  29. package/src/adapter/SqliteDriver.res +11 -1
  30. package/src/adapter/SqliteDriver.res.mjs +5 -1
  31. package/src/components/ReadModel_Builder.res +3 -1
  32. package/src/components/ReadModel_Builder.res.mjs +13 -4
  33. package/src/components/StateViewSlice_Builder.res +4 -1
  34. package/src/components/StateViewSlice_Builder.res.mjs +9 -3
  35. package/src/test/Mocks/MockEventLogStorage.res +19 -2
  36. package/src/test/Mocks/MockEventLogStorage.res.mjs +21 -2
  37. package/tests/PluginEventDecodeTest.res +51 -0
  38. package/tests/PluginEventDecodeTest.res.mjs +63 -0
  39. package/tests/adapter/DcbEventLogStorageSqliteTest.res +73 -0
  40. package/tests/adapter/DcbEventLogStorageSqliteTest.res.mjs +86 -0
  41. package/tests/adapter/DcbEventLogStorageTest.res +46 -0
  42. package/tests/adapter/DcbEventLogStorageTest.res.mjs +63 -0
  43. package/tests/adapter/EventLogSnapshotParityTest.res +81 -0
  44. package/tests/adapter/EventLogSnapshotParityTest.res.mjs +141 -0
  45. package/tests/adapter/EventLogStorageSqliteTest.res +128 -0
  46. package/tests/adapter/EventLogStorageSqliteTest.res.mjs +189 -0
  47. package/tests/adapter/ProjectionCheckpointTest.res +422 -0
  48. package/tests/adapter/ProjectionCheckpointTest.res.mjs +401 -0
  49. package/tests/adapter/QueryDbGsiTtlTest.res +78 -0
  50. package/tests/adapter/QueryDbGsiTtlTest.res.mjs +77 -0
  51. package/tests/adapter/QueryDbListPushdownParityTest.res +197 -0
  52. package/tests/adapter/QueryDbListPushdownParityTest.res.mjs +292 -0
  53. package/tests/adapter/QueryDbListResolverTest.res +76 -0
  54. package/tests/adapter/QueryDbListResolverTest.res.mjs +84 -0
  55. package/tests/components/aggregate/AggregateFixtures.res +4 -0
  56. package/tests/components/aggregate/AggregateFixtures.res.mjs +1 -0
  57. package/tests/components/eventlog/EventLogAppendStreamTest.res.mjs +4 -4
  58. package/tests/components/eventlog/EventLogStreamTest.res.mjs +8 -8
  59. package/tests/plugin/PluginBehavior_GWT.res.mjs +1 -0
@@ -25,18 +25,12 @@ type relaySupport = {
25
25
  @val external btoa: string => string = "btoa"
26
26
  @val external atob: string => string = "atob"
27
27
 
28
- // Shared keyset-cursor helpers. The cursor is base64 of the row's value for the
29
- // active sort field (orderBy.field, or "id" when no orderBy is supplied). Used
30
- // by both the connection list resolver and the items resolver so cursor encoding
31
- // stays in lockstep.
28
+ // Keyset-cursor helpers for the `{name}Items` (sub-id) connection base64 of
29
+ // the sub-key value. The main connection list resolver's cursor logic now lives
30
+ // in `QueryDbListQuery`; these remain for the items resolver below.
32
31
  let encodeCursor = (value: string): string => btoa(value)
33
32
  let decodeCursor = (cursor: string): string => atob(cursor)
34
33
 
35
- // Default page size for the connection list resolver when neither `first` nor
36
- // `last` is supplied. Matches the UI's `defaultPageSize` constant; a bound is
37
- // necessary for the keyset model to report `pageInfo` correctly.
38
- let defaultListPageSize = 50
39
-
40
34
  module Make = (Bus: LocalBus.T) => {
41
35
  open ReventlessCore
42
36
 
@@ -288,6 +282,19 @@ module Make = (Bus: LocalBus.T) => {
288
282
  | Some(s) => GraphQL_FragmentGenerator.deriveServerCapability(s)
289
283
  | None => GraphQL_FragmentGenerator.emptyCapability
290
284
  }
285
+
286
+ // Materialise the whole read model (stream preferred, scan fallback). The
287
+ // fallback path for list queries when no backend push-down is available.
288
+ let fetchAllItems = async (): array<JSON.t> =>
289
+ switch Bus.getQueryDbStream(name) {
290
+ | Some(makeStream) => await makeStream()->Stream.runCollect->Effect.runPromise
291
+ | None =>
292
+ switch Bus.getQueryDbScan(name) {
293
+ | Some(scanAll) => scanAll()
294
+ | None => []
295
+ }
296
+ }
297
+
291
298
  let (listSdl, listResolver): (array<string>, GraphQL_ServerInstance.resolverFn) = if connectionSpec {
292
299
  // Relay Connection spec format
293
300
  let filterTypeName = returnTypeName ++ "Filter"
@@ -321,265 +328,27 @@ module Make = (Bus: LocalBus.T) => {
321
328
  },
322
329
  })
323
330
  | Allow =>
324
- let items = switch Bus.getQueryDbStream(name) {
325
- | Some(makeStream) =>
326
- await makeStream()->Stream.runCollect->Effect.runPromise
327
- | None =>
328
- switch Bus.getQueryDbScan(name) {
329
- | Some(scanAll) => scanAll()
330
- | None => []
331
- }
332
- }
333
- // Apply filter (search, searchPrefix, ids) before any pagination.
334
331
  let argsDict = args->JSON.Decode.object->Option.getOr(Dict.make())
335
- let filterDict =
336
- argsDict->Dict.get("filter")->Option.flatMap(JSON.Decode.object)->Option.getOr(Dict.make())
337
- let search = filterDict->Dict.get("search")->Option.flatMap(JSON.Decode.string)
338
- let searchPrefix = filterDict->Dict.get("searchPrefix")->Option.flatMap(JSON.Decode.string)
339
- let ids =
340
- filterDict
341
- ->Dict.get("ids")
342
- ->Option.flatMap(JSON.Decode.array)
343
- ->Option.map(arr => arr->Array.filterMap(JSON.Decode.string))
344
- let getLabel = item =>
345
- item
346
- ->JSON.Decode.object
347
- ->Option.flatMap(d => d->Dict.get(labelField))
348
- ->Option.flatMap(JSON.Decode.string)
349
- ->Option.getOr("")
350
- let getId = item =>
351
- item
352
- ->JSON.Decode.object
353
- ->Option.flatMap(d => d->Dict.get("id"))
354
- ->Option.flatMap(JSON.Decode.string)
355
- ->Option.getOr("")
356
- // Per-field eq / from / to filters derived from capability — applied
357
- // alongside the legacy search/searchPrefix/ids block.
358
- let getFieldString = (item, field) =>
359
- item
360
- ->JSON.Decode.object
361
- ->Option.flatMap(d => d->Dict.get(field))
362
- ->Option.flatMap(v =>
363
- switch v->JSON.Decode.string {
364
- | Some(s) => Some(s)
365
- | None =>
366
- v
367
- ->JSON.Decode.float
368
- ->Option.map(f => Float.toString(f))
369
- }
370
- )
371
- let perFieldChecks: array<JSON.t => bool> = capability.filterFields->Array.flatMap(f => {
372
- let checks: array<JSON.t => bool> = []
373
- switch filterDict->Dict.get(f.name ++ "Eq") {
374
- | Some(v) when v != JSON.Encode.null =>
375
- let expected = switch v->JSON.Decode.string {
376
- | Some(s) => s
377
- | None =>
378
- v->JSON.Decode.float->Option.map(f => Float.toString(f))->Option.getOr("")
379
- }
380
- checks->Array.push(item =>
381
- getFieldString(item, f.name)->Option.mapOr(false, v => v == expected)
382
- )
383
- | _ => ()
332
+ // Prefer a backend list push-down (SQLite builds json_extract predicates
333
+ // + ORDER BY … LIMIT so it never materialises the whole read model). When
334
+ // the backend can't serve this query shape it returns None and we fall
335
+ // back to materialising the full model and running the shared
336
+ // `QueryDbListQuery` spec over it (the same code the in-memory backend and
337
+ // the push-down are tested against).
338
+ let decodeLocalId = id =>
339
+ DomainGraphQL_Server.decodeGlobalId(id)->Option.map(((_, lid)) => lid)
340
+ switch Bus.getQueryDbListPage(name) {
341
+ | Some(listPage) =>
342
+ switch listPage(~argsDict, ~capability, ~labelField) {
343
+ | Some(conn) => conn
344
+ | None =>
345
+ let items = await fetchAllItems()
346
+ QueryDbListQuery.run(~items, ~argsDict, ~capability, ~labelField, ~decodeLocalId)
384
347
  }
385
- if f.range {
386
- switch filterDict->Dict.get(f.name ++ "From") {
387
- | Some(v) when v != JSON.Encode.null =>
388
- let from =
389
- v
390
- ->JSON.Decode.string
391
- ->Option.getOr(
392
- v->JSON.Decode.float->Option.map(f => Float.toString(f))->Option.getOr(""),
393
- )
394
- checks->Array.push(item =>
395
- getFieldString(item, f.name)->Option.mapOr(false, v => v >= from)
396
- )
397
- | _ => ()
398
- }
399
- switch filterDict->Dict.get(f.name ++ "To") {
400
- | Some(v) when v != JSON.Encode.null =>
401
- let to_ =
402
- v
403
- ->JSON.Decode.string
404
- ->Option.getOr(
405
- v->JSON.Decode.float->Option.map(f => Float.toString(f))->Option.getOr(""),
406
- )
407
- checks->Array.push(item =>
408
- getFieldString(item, f.name)->Option.mapOr(false, v => v <= to_)
409
- )
410
- | _ => ()
411
- }
412
- }
413
- checks
414
- })
415
-
416
- let filtered = items->Array.filter(item => {
417
- let passSearch = switch search {
418
- | Some(s) if s->String.length > 0 =>
419
- getLabel(item)->String.toLowerCase->String.includes(s->String.toLowerCase)
420
- | _ => true
421
- }
422
- let passPrefix = switch searchPrefix {
423
- | Some(p) if p->String.length > 0 =>
424
- getLabel(item)->String.toLowerCase->String.startsWith(p->String.toLowerCase)
425
- | _ => true
426
- }
427
- // Filter `ids` accepts either the Relay-encoded global ID (the
428
- // form returned by node.id) or the entity's raw local ID — so
429
- // callers that hold a foreign-key value like `customerId =
430
- // "cust-1"` can hydrate labels without first encoding to
431
- // `Ordering_Customer:cust-1` base64.
432
- let passIds = switch ids {
433
- | Some(idList) if idList->Array.length > 0 =>
434
- let itemId = getId(item)
435
- let itemLocalId =
436
- DomainGraphQL_Server.decodeGlobalId(itemId)->Option.map(((_, lid)) => lid)
437
- idList->Array.some(i => i == itemId || itemLocalId == Some(i))
438
- | _ => true
439
- }
440
- let passPerField = perFieldChecks->Array.every(check => check(item))
441
- passSearch && passPrefix && passIds && passPerField
442
- })
443
-
444
- // Apply orderBy when provided. Sorts on the requested field; ties
445
- // broken by id so keyset-style cursors stay stable across requests
446
- // that share a sort field. When orderBy is omitted, items are sorted
447
- // by id ascending so the natural order is deterministic and pagination
448
- // cursors remain stable.
449
- let orderByDict =
450
- argsDict
451
- ->Dict.get("orderBy")
452
- ->Option.flatMap(JSON.Decode.object)
453
- let orderByField =
454
- orderByDict
455
- ->Option.flatMap(ob => ob->Dict.get("field"))
456
- ->Option.flatMap(JSON.Decode.string)
457
- let direction =
458
- orderByDict
459
- ->Option.flatMap(ob => ob->Dict.get("direction"))
460
- ->Option.flatMap(JSON.Decode.string)
461
- ->Option.getOr("ASC")
462
- let isDesc = direction == "DESC"
463
- let sorted = switch orderByField {
464
- | Some(f) =>
465
- let cmp = (a, b) => {
466
- let av = getFieldString(a, f)->Option.getOr("")
467
- let bv = getFieldString(b, f)->Option.getOr("")
468
- let primary = if av < bv {
469
- -1
470
- } else if av > bv {
471
- 1
472
- } else {
473
- 0
474
- }
475
- let primary = isDesc ? -primary : primary
476
- if primary != 0 {
477
- primary
478
- } else {
479
- let aid = getId(a)
480
- let bid = getId(b)
481
- if aid < bid {
482
- -1
483
- } else if aid > bid {
484
- 1
485
- } else {
486
- 0
487
- }
488
- }
489
- }
490
- filtered->Array.toSorted((a, b) => cmp(a, b)->Int.toFloat)
491
348
  | None =>
492
- // Default to id-ascending so cursor pagination has a stable order.
493
- filtered->Array.toSorted((a, b) => {
494
- let aid = getId(a)
495
- let bid = getId(b)
496
- if aid < bid {
497
- -1.
498
- } else if aid > bid {
499
- 1.
500
- } else {
501
- 0.
502
- }
503
- })
504
- }
505
-
506
- // Keyset pagination. Cursor encodes the row's value for the active
507
- // sort field — orderBy.field when supplied, "id" otherwise. Boundary
508
- // is applied as a value comparison against the sorted array; in DESC
509
- // sort the comparison flips. Note: when the cursor field has duplicate
510
- // values, the boundary excludes all rows with the cursor's value, not
511
- // just the row at the cursor position. Acceptable for in-memory dev;
512
- // in practice common sort fields (id, createdAt) are unique.
513
- let first =
514
- argsDict->Dict.get("first")->Option.flatMap(JSON.Decode.float)->Option.map(Float.toInt)
515
- let after = argsDict->Dict.get("after")->Option.flatMap(JSON.Decode.string)
516
- let last =
517
- argsDict->Dict.get("last")->Option.flatMap(JSON.Decode.float)->Option.map(Float.toInt)
518
- let before = argsDict->Dict.get("before")->Option.flatMap(JSON.Decode.string)
519
- let isBackward = last->Option.isSome
520
- let cursorField = orderByField->Option.getOr("id")
521
- let getCursorValue = item =>
522
- getFieldString(item, cursorField)->Option.getOr(getId(item))
523
-
524
- let cursorBounded = switch (isBackward, after, before) {
525
- | (false, Some(c), _) =>
526
- let cv = decodeCursor(c)
527
- sorted->Array.filter(item => {
528
- let v = getCursorValue(item)
529
- isDesc ? v < cv : v > cv
530
- })
531
- | (true, _, Some(c)) =>
532
- let cv = decodeCursor(c)
533
- sorted->Array.filter(item => {
534
- let v = getCursorValue(item)
535
- isDesc ? v > cv : v < cv
536
- })
537
- | _ => sorted
538
- }
539
-
540
- let pageSize = if isBackward {
541
- last->Option.getOr(defaultListPageSize)
542
- } else {
543
- first->Option.getOr(defaultListPageSize)
544
- }
545
- let take = pageSize + 1
546
- let (pageItems, hasMore) = if isBackward {
547
- // Take the last `take` items from the cursor-bounded slice. If we
548
- // grabbed an extra, drop the leading entry (the boundary marker).
549
- let len = cursorBounded->Array.length
550
- let startIdx = len > take ? len - take : 0
551
- let arr = cursorBounded->Array.slice(~start=startIdx, ~end=len)
552
- let hasMore = arr->Array.length > pageSize
553
- let result = if hasMore {
554
- arr->Array.slice(~start=1, ~end=arr->Array.length)
555
- } else {
556
- arr
557
- }
558
- (result, hasMore)
559
- } else {
560
- let arr = cursorBounded->Array.slice(~start=0, ~end=take)
561
- let hasMore = arr->Array.length > pageSize
562
- (arr->Array.slice(~start=0, ~end=pageSize), hasMore)
349
+ let items = await fetchAllItems()
350
+ QueryDbListQuery.run(~items, ~argsDict, ~capability, ~labelField, ~decodeLocalId)
563
351
  }
564
-
565
- let edges = pageItems->Array.map(item =>
566
- Obj.magic({"node": item, "cursor": encodeCursor(getCursorValue(item))})
567
- )
568
- let startCursor =
569
- pageItems->Array.get(0)->Option.map(item => encodeCursor(getCursorValue(item)))
570
- let endCursor =
571
- pageItems
572
- ->Array.get(pageItems->Array.length - 1)
573
- ->Option.map(item => encodeCursor(getCursorValue(item)))
574
- Obj.magic({
575
- "edges": edges,
576
- "pageInfo": {
577
- "hasNextPage": !isBackward && hasMore,
578
- "hasPreviousPage": isBackward && hasMore,
579
- "startCursor": startCursor->Nullable.fromOption,
580
- "endCursor": endCursor->Nullable.fromOption,
581
- },
582
- })
583
352
  }
584
353
  }
585
354
  (sdl, resolver)
@@ -590,15 +359,7 @@ module Make = (Bus: LocalBus.T) => {
590
359
  switch await runInterceptor(~ctx, ~args) {
591
360
  | Deny(_) => Obj.magic({"nextToken": Nullable.null, "scannedCount": 0, "items": []})
592
361
  | Allow =>
593
- let items = switch Bus.getQueryDbStream(name) {
594
- | Some(makeStream) =>
595
- await makeStream()->Stream.runCollect->Effect.runPromise
596
- | None =>
597
- switch Bus.getQueryDbScan(name) {
598
- | Some(scanAll) => scanAll()
599
- | None => []
600
- }
601
- }
362
+ let items = await fetchAllItems()
602
363
  Obj.magic({"nextToken": Nullable.null, "scannedCount": items->Array.length, "items": items})
603
364
  }
604
365
  }
@@ -748,19 +509,26 @@ module Make = (Bus: LocalBus.T) => {
748
509
  | Allow =>
749
510
  let value =
750
511
  args->JSON.Decode.object->Option.flatMap(d => d->Dict.get(index))->Option.flatMap(JSON.Decode.string)->Option.getOr("")
751
- switch Bus.getQueryDbScan(name) {
752
- | Some(scanAll) =>
753
- scanAll()
754
- ->Array.filter(item =>
755
- item
756
- ->JSON.Decode.object
757
- ->Option.flatMap(d => d->Dict.get(filterField))
758
- ->Option.flatMap(JSON.Decode.string)
759
- ->Option.map(v => v == value)
760
- ->Option.getOr(false)
761
- )
762
- ->JSON.Encode.array
763
- | None => []->JSON.Encode.array
512
+ // Prefer the pushed-down equality lookup (SQLite rides the GSI index;
513
+ // in-memory reuses its lazy snapshot). Fall back to scan+filter only
514
+ // if no lookup is registered for this QueryDb.
515
+ switch Bus.getQueryDbIndexLookup(name) {
516
+ | Some(lookup) => lookup(filterField, value)->JSON.Encode.array
517
+ | None =>
518
+ switch Bus.getQueryDbScan(name) {
519
+ | Some(scanAll) =>
520
+ scanAll()
521
+ ->Array.filter(item =>
522
+ item
523
+ ->JSON.Decode.object
524
+ ->Option.flatMap(d => d->Dict.get(filterField))
525
+ ->Option.flatMap(JSON.Decode.string)
526
+ ->Option.map(v => v == value)
527
+ ->Option.getOr(false)
528
+ )
529
+ ->JSON.Encode.array
530
+ | None => []->JSON.Encode.array
531
+ }
764
532
  }
765
533
  }
766
534
  }
@@ -6,12 +6,12 @@ import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
6
6
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
7
7
  import * as Effect from "effect/Effect";
8
8
  import * as Stdlib_Nullable from "@rescript/runtime/lib/es6/Stdlib_Nullable.js";
9
- import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
10
9
  import * as Identity$Reventless from "@reventlessdev/reventless-spec/src/types/Identity.res.mjs";
11
10
  import * as Authorization$Reventless from "@reventlessdev/reventless-spec/src/types/Authorization.res.mjs";
12
11
  import * as Plugin_Helpers$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/component/Plugin_Helpers.res.mjs";
13
12
  import * as SortKey_Filter$ReventlessLocal from "./SortKey_Filter.res.mjs";
14
13
  import * as QueryDb_Callback$ReventlessCore from "@reventlessdev/reventless-core/src/components/QueryDb/QueryDb_Callback.res.mjs";
14
+ import * as QueryDbListQuery$ReventlessLocal from "./QueryDbListQuery.res.mjs";
15
15
  import * as DomainGraphQL_Server$ReventlessLocal from "../DomainGraphQL_Server.res.mjs";
16
16
  import * as GraphQL_FragmentGenerator$ReventlessCore from "@reventlessdev/reventless-core/src/components/Api/GraphQL_FragmentGenerator.res.mjs";
17
17
 
@@ -157,6 +157,18 @@ function Make(Bus) {
157
157
  let labelField = registryEntry !== undefined ? Stdlib_Option.getOr(registryEntry.labelField, "id") : "id";
158
158
  let stateSchemaOpt = Plugin_Helpers$ReventlessCore.stateSchemaRegistry[name];
159
159
  let capability = stateSchemaOpt !== undefined ? GraphQL_FragmentGenerator$ReventlessCore.deriveServerCapability(stateSchemaOpt) : GraphQL_FragmentGenerator$ReventlessCore.emptyCapability;
160
+ let fetchAllItems = async () => {
161
+ let makeStream = Bus.getQueryDbStream(name);
162
+ if (makeStream !== undefined) {
163
+ return await Effect.runPromise(Stream.runCollect(makeStream()));
164
+ }
165
+ let scanAll = Bus.getQueryDbScan(name);
166
+ if (scanAll !== undefined) {
167
+ return scanAll();
168
+ } else {
169
+ return [];
170
+ }
171
+ };
160
172
  let match;
161
173
  if (connectionSpec) {
162
174
  let filterTypeName = returnTypeName + "Filter";
@@ -178,187 +190,19 @@ function Make(Bus) {
178
190
  }
179
191
  };
180
192
  }
181
- let makeStream = Bus.getQueryDbStream(name);
182
- let items;
183
- if (makeStream !== undefined) {
184
- items = await Effect.runPromise(Stream.runCollect(makeStream()));
185
- } else {
186
- let scanAll = Bus.getQueryDbScan(name);
187
- items = scanAll !== undefined ? scanAll() : [];
188
- }
189
193
  let argsDict = Stdlib_Option.getOr(Stdlib_JSON.Decode.object(args), {});
190
- let filterDict = Stdlib_Option.getOr(Stdlib_Option.flatMap(argsDict["filter"], Stdlib_JSON.Decode.object), {});
191
- let search = Stdlib_Option.flatMap(filterDict["search"], Stdlib_JSON.Decode.string);
192
- let searchPrefix = Stdlib_Option.flatMap(filterDict["searchPrefix"], Stdlib_JSON.Decode.string);
193
- let ids = Stdlib_Option.map(Stdlib_Option.flatMap(filterDict["ids"], Stdlib_JSON.Decode.array), arr => Stdlib_Array.filterMap(arr, Stdlib_JSON.Decode.string));
194
- let getLabel = item => Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(item), d => d[labelField]), Stdlib_JSON.Decode.string), "");
195
- let getId = item => Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(item), d => d["id"]), Stdlib_JSON.Decode.string), "");
196
- let getFieldString = (item, field) => Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(item), d => d[field]), v => {
197
- let s = Stdlib_JSON.Decode.string(v);
198
- if (s !== undefined) {
199
- return s;
200
- } else {
201
- return Stdlib_Option.map(Stdlib_JSON.Decode.float(v), f => f.toString());
194
+ let decodeLocalId = id => Stdlib_Option.map(DomainGraphQL_Server$ReventlessLocal.decodeGlobalId(id), param => param[1]);
195
+ let listPage = Bus.getQueryDbListPage(name);
196
+ if (listPage !== undefined) {
197
+ let conn = listPage(argsDict, capability, labelField);
198
+ if (conn !== undefined) {
199
+ return conn;
202
200
  }
203
- });
204
- let perFieldChecks = capability.filterFields.flatMap(f => {
205
- let checks = [];
206
- let v = filterDict[f.name + "Eq"];
207
- if (v !== undefined && v !== null) {
208
- let s = Stdlib_JSON.Decode.string(v);
209
- let expected = s !== undefined ? s : Stdlib_Option.getOr(Stdlib_Option.map(Stdlib_JSON.Decode.float(v), f => f.toString()), "");
210
- checks.push(item => Stdlib_Option.mapOr(getFieldString(item, f.name), false, v => v === expected));
211
- }
212
- if (f.range) {
213
- let v$1 = filterDict[f.name + "From"];
214
- if (v$1 !== undefined && v$1 !== null) {
215
- let from = Stdlib_Option.getOr(Stdlib_JSON.Decode.string(v$1), Stdlib_Option.getOr(Stdlib_Option.map(Stdlib_JSON.Decode.float(v$1), f => f.toString()), ""));
216
- checks.push(item => Stdlib_Option.mapOr(getFieldString(item, f.name), false, v => v >= from));
217
- }
218
- let v$2 = filterDict[f.name + "To"];
219
- if (v$2 !== undefined && v$2 !== null) {
220
- let to_ = Stdlib_Option.getOr(Stdlib_JSON.Decode.string(v$2), Stdlib_Option.getOr(Stdlib_Option.map(Stdlib_JSON.Decode.float(v$2), f => f.toString()), ""));
221
- checks.push(item => Stdlib_Option.mapOr(getFieldString(item, f.name), false, v => v <= to_));
222
- }
223
- }
224
- return checks;
225
- });
226
- let filtered = items.filter(item => {
227
- let passSearch = search !== undefined && search.length > 0 ? getLabel(item).toLowerCase().includes(search.toLowerCase()) : true;
228
- let passPrefix = searchPrefix !== undefined && searchPrefix.length > 0 ? getLabel(item).toLowerCase().startsWith(searchPrefix.toLowerCase()) : true;
229
- let passIds;
230
- if (ids !== undefined && ids.length !== 0) {
231
- let itemId = getId(item);
232
- let itemLocalId = Stdlib_Option.map(DomainGraphQL_Server$ReventlessLocal.decodeGlobalId(itemId), param => param[1]);
233
- passIds = ids.some(i => {
234
- if (i === itemId) {
235
- return true;
236
- } else {
237
- return Primitive_object.equal(itemLocalId, i);
238
- }
239
- });
240
- } else {
241
- passIds = true;
242
- }
243
- let passPerField = perFieldChecks.every(check => check(item));
244
- if (passSearch && passPrefix && passIds) {
245
- return passPerField;
246
- } else {
247
- return false;
248
- }
249
- });
250
- let orderByDict = Stdlib_Option.flatMap(argsDict["orderBy"], Stdlib_JSON.Decode.object);
251
- let orderByField = Stdlib_Option.flatMap(Stdlib_Option.flatMap(orderByDict, ob => ob["field"]), Stdlib_JSON.Decode.string);
252
- let direction = Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_Option.flatMap(orderByDict, ob => ob["direction"]), Stdlib_JSON.Decode.string), "ASC");
253
- let isDesc = direction === "DESC";
254
- let sorted;
255
- if (orderByField !== undefined) {
256
- sorted = filtered.toSorted((a, b) => {
257
- let av = Stdlib_Option.getOr(getFieldString(a, orderByField), "");
258
- let bv = Stdlib_Option.getOr(getFieldString(b, orderByField), "");
259
- let primary = av < bv ? -1 : (
260
- av > bv ? 1 : 0
261
- );
262
- let primary$1 = isDesc ? -primary | 0 : primary;
263
- if (primary$1 !== 0) {
264
- return primary$1;
265
- }
266
- let aid = getId(a);
267
- let bid = getId(b);
268
- if (aid < bid) {
269
- return -1;
270
- } else if (aid > bid) {
271
- return 1;
272
- } else {
273
- return 0;
274
- }
275
- });
276
- } else {
277
- sorted = filtered.toSorted((a, b) => {
278
- let aid = getId(a);
279
- let bid = getId(b);
280
- if (aid < bid) {
281
- return -1;
282
- } else if (aid > bid) {
283
- return 1;
284
- } else {
285
- return 0;
286
- }
287
- });
201
+ let items = await fetchAllItems();
202
+ return QueryDbListQuery$ReventlessLocal.run(items, argsDict, capability, labelField, decodeLocalId);
288
203
  }
289
- let first = Stdlib_Option.map(Stdlib_Option.flatMap(argsDict["first"], Stdlib_JSON.Decode.float), prim => prim | 0);
290
- let after = Stdlib_Option.flatMap(argsDict["after"], Stdlib_JSON.Decode.string);
291
- let last = Stdlib_Option.map(Stdlib_Option.flatMap(argsDict["last"], Stdlib_JSON.Decode.float), prim => prim | 0);
292
- let before = Stdlib_Option.flatMap(argsDict["before"], Stdlib_JSON.Decode.string);
293
- let isBackward = Stdlib_Option.isSome(last);
294
- let cursorField = Stdlib_Option.getOr(orderByField, "id");
295
- let getCursorValue = item => Stdlib_Option.getOr(getFieldString(item, cursorField), getId(item));
296
- let cursorBounded;
297
- if (isBackward) {
298
- if (before !== undefined) {
299
- let cv = atob(before);
300
- cursorBounded = sorted.filter(item => {
301
- let v = getCursorValue(item);
302
- if (isDesc) {
303
- return v > cv;
304
- } else {
305
- return v < cv;
306
- }
307
- });
308
- } else {
309
- cursorBounded = sorted;
310
- }
311
- } else if (after !== undefined) {
312
- let cv$1 = atob(after);
313
- cursorBounded = sorted.filter(item => {
314
- let v = getCursorValue(item);
315
- if (isDesc) {
316
- return v < cv$1;
317
- } else {
318
- return v > cv$1;
319
- }
320
- });
321
- } else {
322
- cursorBounded = sorted;
323
- }
324
- let pageSize = isBackward ? Stdlib_Option.getOr(last, 50) : Stdlib_Option.getOr(first, 50);
325
- let take = pageSize + 1 | 0;
326
- let match$1;
327
- if (isBackward) {
328
- let len = cursorBounded.length;
329
- let startIdx = len > take ? len - take | 0 : 0;
330
- let arr = cursorBounded.slice(startIdx, len);
331
- let hasMore = arr.length > pageSize;
332
- let result = hasMore ? arr.slice(1, arr.length) : arr;
333
- match$1 = [
334
- result,
335
- hasMore
336
- ];
337
- } else {
338
- let arr$1 = cursorBounded.slice(0, take);
339
- let hasMore$1 = arr$1.length > pageSize;
340
- match$1 = [
341
- arr$1.slice(0, pageSize),
342
- hasMore$1
343
- ];
344
- }
345
- let hasMore$2 = match$1[1];
346
- let pageItems = match$1[0];
347
- let edges = pageItems.map(item => ({
348
- node: item,
349
- cursor: btoa(getCursorValue(item))
350
- }));
351
- let startCursor = Stdlib_Option.map(pageItems[0], item => btoa(getCursorValue(item)));
352
- let endCursor = Stdlib_Option.map(pageItems[pageItems.length - 1 | 0], item => btoa(getCursorValue(item)));
353
- return {
354
- edges: edges,
355
- pageInfo: {
356
- hasNextPage: !isBackward && hasMore$2,
357
- hasPreviousPage: isBackward && hasMore$2,
358
- startCursor: Stdlib_Nullable.fromOption(startCursor),
359
- endCursor: Stdlib_Nullable.fromOption(endCursor)
360
- }
361
- };
204
+ let items$1 = await fetchAllItems();
205
+ return QueryDbListQuery$ReventlessLocal.run(items$1, argsDict, capability, labelField, decodeLocalId);
362
206
  };
363
207
  match = [
364
208
  sdl,
@@ -375,14 +219,7 @@ function Make(Bus) {
375
219
  items: []
376
220
  };
377
221
  }
378
- let makeStream = Bus.getQueryDbStream(name);
379
- let items;
380
- if (makeStream !== undefined) {
381
- items = await Effect.runPromise(Stream.runCollect(makeStream()));
382
- } else {
383
- let scanAll = Bus.getQueryDbScan(name);
384
- items = scanAll !== undefined ? scanAll() : [];
385
- }
222
+ let items = await fetchAllItems();
386
223
  return {
387
224
  nextToken: null,
388
225
  scannedCount: items.length,
@@ -517,6 +354,10 @@ function Make(Bus) {
517
354
  return [];
518
355
  }
519
356
  let value = Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(args), d => d[index]), Stdlib_JSON.Decode.string), "");
357
+ let lookup = Bus.getQueryDbIndexLookup(name);
358
+ if (lookup !== undefined) {
359
+ return lookup(filterField, value);
360
+ }
520
361
  let scanAll = Bus.getQueryDbScan(name);
521
362
  if (scanAll !== undefined) {
522
363
  return scanAll().filter(item => Stdlib_Option.getOr(Stdlib_Option.map(Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(item), d => d[filterField]), Stdlib_JSON.Decode.string), v => v === value), false));
@@ -558,12 +399,9 @@ function Make(Bus) {
558
399
  };
559
400
  }
560
401
 
561
- let defaultListPageSize = 50;
562
-
563
402
  export {
564
403
  encodeCursor,
565
404
  decodeCursor,
566
- defaultListPageSize,
567
405
  Make,
568
406
  }
569
407
  /* Stream Not a pure module */