@reventlessdev/reventless-local 3.0.0-alpha.225 → 3.0.0-alpha.227

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.
@@ -2,12 +2,14 @@
2
2
 
3
3
  import * as S from "sury/src/S.res.mjs";
4
4
  import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
5
+ import * as Stdlib_JsExn from "@rescript/runtime/lib/es6/Stdlib_JsExn.js";
5
6
  import * as Id$Reventless from "@reventlessdev/reventless-spec/src/types/Id.res.mjs";
6
7
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
7
8
  import * as Pulumi from "@pulumi/pulumi";
8
9
  import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
9
10
  import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/components/DcbTag.res.mjs";
10
11
  import * as Identity$Reventless from "@reventlessdev/reventless-spec/src/types/Identity.res.mjs";
12
+ import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
11
13
  import * as ReadModel$Reventless from "@reventlessdev/reventless-spec/src/components/ReadModel.res.mjs";
12
14
  import * as Component$ReventlessCore from "@reventlessdev/reventless-core/src/components/Component.res.mjs";
13
15
  import * as LocalBus$ReventlessLocal from "../../src/adapter/LocalBus.res.mjs";
@@ -112,7 +114,8 @@ function pageInfoString(response, key) {
112
114
  return getString(pageInfo(response), key);
113
115
  }
114
116
 
115
- async function buildFixture(name) {
117
+ async function buildFixture(name, indexesOpt) {
118
+ let indexes = indexesOpt !== undefined ? indexesOpt : [];
116
119
  let Bus = LocalBus$ReventlessLocal.Make({});
117
120
  let Storage = LocalQueryDbStorage$ReventlessLocal.Make(Bus);
118
121
  let Resolvers = QueryDbResolvers_GraphQL$ReventlessLocal.Make(Bus);
@@ -151,7 +154,7 @@ async function buildFixture(name) {
151
154
  Plugin_Helpers$ReventlessCore.stateSchemaRegistry[name] = rowStateSchemaWithAnnotations;
152
155
  let queryDb = Maker.make(undefined, undefined, undefined, undefined, undefined);
153
156
  let ops = await TestRunner$ReventlessLocal.resolve(Component$ReventlessCore.operations(queryDb));
154
- Resolvers.make(name, undefined, undefined, Pulumi.output(""), [], undefined, [], [], "AllowAuthenticated", {});
157
+ Resolvers.make(name, undefined, undefined, Pulumi.output(""), indexes, undefined, [], [], "AllowAuthenticated", {});
155
158
  let r = DomainGraphQL_Server$ReventlessLocal.getQueryResolver(listFieldName);
156
159
  let resolver = r !== undefined ? r : Stdlib_JsError.throwWithMessage("resolver not registered: " + listFieldName);
157
160
  let rows = [
@@ -199,7 +202,7 @@ async function buildFixture(name) {
199
202
  globalThis.describe("QueryDb list resolver — keyset pagination", () => {
200
203
  globalThis.beforeEach(() => DomainGraphQL_Server$ReventlessLocal.reset());
201
204
  globalThis.test("first: 2 over a 5-row fixture returns 2 edges and hasNextPage = true", async () => {
202
- let match = await buildFixture("PageA");
205
+ let match = await buildFixture("PageA", undefined);
203
206
  let response = await match[0](null, Object.fromEntries([[
204
207
  "first",
205
208
  2
@@ -212,7 +215,7 @@ globalThis.describe("QueryDb list resolver — keyset pagination", () => {
212
215
  globalThis.expect(edgeNodeField(edges[1], "productId")).toBe("p-2");
213
216
  });
214
217
  globalThis.test("after: <endCursor> walks forward without overlap", async () => {
215
- let match = await buildFixture("PageB");
218
+ let match = await buildFixture("PageB", undefined);
216
219
  let resolver = match[0];
217
220
  let page1 = await resolver(null, Object.fromEntries([[
218
221
  "first",
@@ -251,7 +254,7 @@ globalThis.describe("QueryDb list resolver — keyset pagination", () => {
251
254
  globalThis.expect(pageInfoBool(page3, "hasNextPage")).toBe(false);
252
255
  });
253
256
  globalThis.test("last + before walks backward and flips hasPreviousPage", async () => {
254
- let match = await buildFixture("PageC");
257
+ let match = await buildFixture("PageC", undefined);
255
258
  let resolver = match[0];
256
259
  let page1 = await resolver(null, Object.fromEntries([[
257
260
  "first",
@@ -314,7 +317,7 @@ globalThis.describe("QueryDb list resolver — keyset pagination", () => {
314
317
  globalThis.expect(pageInfoBool(firstPage, "hasPreviousPage")).toBe(false);
315
318
  });
316
319
  globalThis.test("filter.<field>Eq + first / after paginates only the narrowed subset", async () => {
317
- let match = await buildFixture("PageD");
320
+ let match = await buildFixture("PageD", undefined);
318
321
  let resolver = match[0];
319
322
  let activeFilter = Object.fromEntries([[
320
323
  "statusEq",
@@ -356,7 +359,7 @@ globalThis.describe("QueryDb list resolver — keyset pagination", () => {
356
359
  globalThis.expect(pageInfoBool(page2, "hasNextPage")).toBe(false);
357
360
  });
358
361
  globalThis.test("orderBy DESC + first / after paginates the reverse-sorted view", async () => {
359
- let match = await buildFixture("PageE");
362
+ let match = await buildFixture("PageE", undefined);
360
363
  let resolver = match[0];
361
364
  let orderBy = Object.fromEntries([
362
365
  [
@@ -404,7 +407,7 @@ globalThis.describe("QueryDb list resolver — keyset pagination", () => {
404
407
  globalThis.expect(edgeNodeField(edges2[1], "name")).toBe("Bravo");
405
408
  });
406
409
  globalThis.test("bare request (no first / after) returns a single bounded page", async () => {
407
- let match = await buildFixture("PageF");
410
+ let match = await buildFixture("PageF", undefined);
408
411
  let response = await match[0](null, Object.fromEntries([]), emptyCtx);
409
412
  let edges = getEdges(response);
410
413
  globalThis.expect(edges.length).toBe(5);
@@ -424,7 +427,7 @@ globalThis.describe("QueryDb list resolver — SQLite push-down path", () => {
424
427
  return r;
425
428
  };
426
429
  globalThis.test("first:2 then after walks forward without overlap", () => withSqlite(async () => {
427
- let match = await buildFixture("SqlPageA");
430
+ let match = await buildFixture("SqlPageA", undefined);
428
431
  let resolver = match[0];
429
432
  let page1 = await resolver(null, Object.fromEntries([[
430
433
  "first",
@@ -452,7 +455,7 @@ globalThis.describe("QueryDb list resolver — SQLite push-down path", () => {
452
455
  globalThis.expect(edgeNodeField(edges2[1], "productId")).toBe("p-4");
453
456
  }));
454
457
  globalThis.test("orderBy name DESC + first:2 returns the reverse-sorted head", () => withSqlite(async () => {
455
- let match = await buildFixture("SqlPageB");
458
+ let match = await buildFixture("SqlPageB", undefined);
456
459
  let orderBy = Object.fromEntries([
457
460
  [
458
461
  "field",
@@ -479,7 +482,7 @@ globalThis.describe("QueryDb list resolver — SQLite push-down path", () => {
479
482
  globalThis.expect(edgeNodeField(edges1[1], "name")).toBe("Delta");
480
483
  }));
481
484
  globalThis.test("statusEq active narrows the pushed page", () => withSqlite(async () => {
482
- let match = await buildFixture("SqlPageC");
485
+ let match = await buildFixture("SqlPageC", undefined);
483
486
  let activeFilter = Object.fromEntries([[
484
487
  "statusEq",
485
488
  "active"
@@ -495,6 +498,98 @@ globalThis.describe("QueryDb list resolver — SQLite push-down path", () => {
495
498
  }));
496
499
  });
497
500
 
501
+ globalThis.describe("QueryDb by-index resolver", () => {
502
+ let statusIndex = [{
503
+ index: "status",
504
+ type_: "S",
505
+ projectionType: "ALL"
506
+ }];
507
+ let indexResolver = async name => {
508
+ await buildFixture(name, statusIndex);
509
+ let r = DomainGraphQL_Server$ReventlessLocal.getQueryResolver(name + "ByStatus");
510
+ if (r !== undefined) {
511
+ return r;
512
+ } else {
513
+ return Stdlib_JsError.throwWithMessage("index resolver not registered: " + name + "ByStatus");
514
+ }
515
+ };
516
+ globalThis.beforeEach(() => DomainGraphQL_Server$ReventlessLocal.reset());
517
+ globalThis.test("answers the rows carrying the index value, as edges", async () => {
518
+ let resolver = await indexResolver("IdxA");
519
+ let response = await resolver(null, Object.fromEntries([[
520
+ "status",
521
+ "active"
522
+ ]]), emptyCtx);
523
+ let edges = getEdges(response);
524
+ globalThis.expect(edges.length).toBe(3);
525
+ globalThis.expect(edgeNodeField(edges[0], "productId")).toBe("p-1");
526
+ });
527
+ globalThis.test("pages forward on first/after like every other connection", async () => {
528
+ let resolver = await indexResolver("IdxB");
529
+ let page1 = await resolver(null, Object.fromEntries([
530
+ [
531
+ "status",
532
+ "active"
533
+ ],
534
+ [
535
+ "first",
536
+ 2
537
+ ]
538
+ ]), emptyCtx);
539
+ globalThis.expect(getEdges(page1).length).toBe(2);
540
+ globalThis.expect(pageInfoBool(page1, "hasNextPage")).toBe(true);
541
+ let page2 = await resolver(null, Object.fromEntries([
542
+ [
543
+ "status",
544
+ "active"
545
+ ],
546
+ [
547
+ "first",
548
+ 2
549
+ ],
550
+ [
551
+ "after",
552
+ Stdlib_Option.getOr(getString(pageInfo(page1), "endCursor"), "")
553
+ ]
554
+ ]), emptyCtx);
555
+ let edges2 = getEdges(page2);
556
+ globalThis.expect(edges2.length).toBe(1);
557
+ globalThis.expect(edgeNodeField(edges2[0], "productId")).toBe("p-4");
558
+ globalThis.expect(pageInfoBool(page2, "hasNextPage")).toBe(false);
559
+ });
560
+ globalThis.test("refuses backward paging instead of answering forward", async () => {
561
+ let resolver = await indexResolver("IdxC");
562
+ let outcome;
563
+ try {
564
+ await resolver(null, Object.fromEntries([
565
+ [
566
+ "status",
567
+ "active"
568
+ ],
569
+ [
570
+ "last",
571
+ 2
572
+ ]
573
+ ]), emptyCtx);
574
+ outcome = {
575
+ TAG: "Ok",
576
+ _0: undefined
577
+ };
578
+ } catch (raw_e) {
579
+ let e = Primitive_exceptions.internalToException(raw_e);
580
+ outcome = {
581
+ TAG: "Error",
582
+ _0: Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_JsExn.fromException(e), Stdlib_JsExn.message), "")
583
+ };
584
+ }
585
+ if (outcome.TAG === "Ok") {
586
+ globalThis.expect("no error").toBe("a BAD_USER_INPUT refusal");
587
+ return;
588
+ }
589
+ globalThis.expect(outcome._0.includes("Backward pagination (last/before) is not supported")).toBe(true);
590
+ });
591
+ });
592
+
498
593
  export {
499
594
  rowStateSchema,
500
595
  rowStateSchemaWithAnnotations,
@@ -5,7 +5,7 @@
5
5
  // `meta.service` with the `allEventTopics` dict key, plus the Phase 3
6
6
  // `Reventless.Projection.DcbSource` helper.
7
7
  //
8
- // Plan 03: docs/plans/mixed-source-readmodel.md
8
+ // Plan 03: docs/plans/done/mixed-source-readmodel.md
9
9
 
10
10
  open TestFixtures
11
11
  open Reventless