@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.
- package/CHANGELOG.md +19 -0
- package/package.json +11 -11
- package/src/Platform.res +5 -6
- package/src/Platform.res.mjs +3 -5
- package/src/adapter/EventLog/EventLogStorage_Sqlite.res +1 -1
- package/src/adapter/QueryDb/LocalQueryDbStorage.res +1 -1
- package/src/adapter/QueryDb/QueryDbResolvers_GraphQL.res +223 -22
- package/src/adapter/QueryDb/QueryDbResolvers_GraphQL.res.mjs +176 -18
- package/tests/UiHintsTest.res +33 -6
- package/tests/UiHintsTest.res.mjs +15 -2
- package/tests/adapter/DcbEventLogStorageSqliteTest.res +1 -1
- package/tests/adapter/DcbEventLogStorageTest.res +1 -1
- package/tests/adapter/EventLogSnapshotParityTest.res +1 -1
- package/tests/adapter/GraphQL_SchemaInspectorTest.res +9 -7
- package/tests/adapter/GraphQL_SchemaInspectorTest.res.mjs +5 -5
- package/tests/adapter/QueryDbListResolverTest.res +82 -2
- package/tests/adapter/QueryDbListResolverTest.res.mjs +106 -11
- package/tests/components/readmodel/DcbReadModelE2EFixtures.res +1 -1
|
@@ -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(""),
|
|
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
|