@reventlessdev/reventless-aws 3.0.0-alpha.176 → 3.0.0-alpha.178
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 +23 -0
- package/docker-compose.postgres.yml +21 -0
- package/package.json +7 -6
- package/scripts/run-pg-integration-tests.sh +58 -0
- package/src/Platform.res +49 -0
- package/src/Platform.res.mjs +48 -2
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res +7 -1
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res.mjs +10 -1
- package/src/adapter/Api/Platform_UIFragments_Lambda.res +7 -1
- package/src/adapter/Api/Platform_UIFragments_Lambda.res.mjs +10 -1
- package/src/adapter/Counter/CounterHandler_DynamoDbStream.res +6 -21
- package/src/adapter/Counter/CounterHandler_DynamoDbStream.res.mjs +2 -17
- package/src/adapter/DcbEventLog/DcbBackend.res +4 -0
- package/src/adapter/DcbEventLog/DcbEventLogStorage_Postgres.res +2 -2
- package/src/adapter/DcbEventLog/DcbEventLogStorage_Postgres.res.mjs +7 -1
- package/src/adapter/Postgres/PgChangeFeedRelay_Builder.res +1 -11
- package/src/adapter/Postgres/PgChangeFeedRelay_Builder.res.mjs +2 -24
- package/src/adapter/Postgres/PgConnection.res +70 -10
- package/src/adapter/Postgres/PgConnection.res.mjs +48 -3
- package/src/adapter/Postgres/PgMigration_Builder.res +127 -0
- package/src/adapter/Postgres/PgMigration_Builder.res.mjs +51 -0
- package/src/adapter/QueryDb/PgQueryResolver_Builder.res +279 -0
- package/src/adapter/QueryDb/PgQueryResolver_Builder.res.mjs +170 -0
- package/src/adapter/QueryDb/PgQueryResolver_Lambda.res +202 -11
- package/src/adapter/QueryDb/PgQueryResolver_Lambda.res.mjs +150 -9
- package/src/adapter/QueryDb/QueryDbBackend.res +8 -0
- package/src/adapter/QueryDb/QueryDbBackend.res.mjs +4 -1
- package/src/adapter/QueryDb/QueryDbResolvers.res +6 -5
- package/src/adapter/QueryDb/QueryDbResolvers.res.mjs +6 -3
- package/src/adapter/QueryDb/QueryDbResolvers_Lambda.res +288 -0
- package/src/adapter/QueryDb/QueryDbResolvers_Lambda.res.mjs +170 -0
- package/src/adapter/QueryDb/QueryDbStorage.res +6 -1
- package/src/adapter/QueryDb/QueryDbStorage.res.mjs +2 -1
- package/src/adapter/QueryDb/QueryDbStorage_Postgres.res +5 -1
- package/src/adapter/QueryDb/QueryDbStorage_Postgres.res.mjs +3 -3
- package/src/adapter/Runtime/AggregateRuntime_Builder_Single.res +3 -14
- package/src/adapter/Runtime/AggregateRuntime_Builder_Single.res.mjs +2 -25
- package/src/adapter/Runtime/AggregateRuntime_Builder_Single_Async.res +3 -14
- package/src/adapter/Runtime/AggregateRuntime_Builder_Single_Async.res.mjs +2 -25
- package/src/adapter/Runtime/DcbCommandTopicEntryPoint.mjs +7 -1
- package/src/adapter/Runtime/EventCollectorRuntime_Builder_Single.res +78 -15
- package/src/adapter/Runtime/EventCollectorRuntime_Builder_Single.res.mjs +43 -26
- package/src/adapter/Runtime/PgMigrationEntryPoint.mjs +40 -0
- package/src/adapter/Runtime/PgQueryResolverEntryPoint.mjs +88 -0
- package/src/adapter/Runtime/ReadModelEntryPoint.mjs +17 -5
- package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res +10 -0
- package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res.mjs +3 -0
- package/src/adapter/Runtime/StateChangeSliceRuntime_Builder_Single.res +9 -14
- package/src/adapter/Runtime/StateChangeSliceRuntime_Builder_Single.res.mjs +2 -25
- package/src/adapter/Runtime/StateTopicPublish.mjs +204 -0
- package/src/adapter/Runtime/StateViewSliceEntryPoint.mjs +17 -3
- package/src/adapter/Runtime/StateViewSliceRuntime_Builder_Single.res +86 -17
- package/src/adapter/Runtime/StateViewSliceRuntime_Builder_Single.res.mjs +50 -33
- package/src/adapter/StateTopic/StateTopic_AppSync.res +7 -1
- package/src/adapter/StateTopic/StateTopic_AppSync.res.mjs +10 -1
- package/src/plugin/cloner/ClonerRunner_Fargate.res +7 -1
- package/src/plugin/cloner/ClonerRunner_Fargate.res.mjs +10 -1
- package/src/util/Util_Bundle.res +71 -1
- package/src/util/Util_Bundle.res.mjs +49 -1
- package/tests/PgChangeFeedRelay_IntegrationTest.res +3 -3
- package/tests/PgConnectionJsonTest.res +84 -0
- package/tests/PgConnectionJsonTest.res.mjs +68 -0
- package/tests/PgMigrationEntryPoint_IntegrationTest.res +82 -0
- package/tests/PgMigrationEntryPoint_IntegrationTest.res.mjs +63 -0
- package/tests/PgPipeline_IntegrationTest.res +3 -1
- package/tests/PgQueryResolver_LambdaTest.res +163 -2
- package/tests/PgQueryResolver_LambdaTest.res.mjs +226 -14
- package/tests/StateTopicPublishTest.res +162 -0
- package/tests/StateTopicPublishTest.res.mjs +158 -0
- package/tests/Util_BundleEsmLoaderTest.res +57 -0
- package/tests/Util_BundleEsmLoaderTest.res.mjs +44 -0
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
open JestGlobals
|
|
10
10
|
open ReventlessCore
|
|
11
11
|
|
|
12
|
+
@val external btoa: string => string = "btoa"
|
|
13
|
+
|
|
12
14
|
let mk = (id, status, name) => {
|
|
13
15
|
let o = Dict.make()
|
|
14
16
|
o->Dict.set("id", JSON.Encode.string(id))
|
|
@@ -51,6 +53,9 @@ let pushdowns: PgQueryResolver_Lambda.pushdowns = {
|
|
|
51
53
|
ids->Array.filterMap(id => store->Dict.get(id)),
|
|
52
54
|
listPage: async (~readModelName as _, ~argsDict as _, ~capability as _, ~labelField as _) =>
|
|
53
55
|
listPageReturn.contents,
|
|
56
|
+
itemsPage: async (~readModelName as _, ~subIdField as _, ~id, ~argsDict as _) =>
|
|
57
|
+
// Sentinel echoing the requested id, so the dispatch routing is observable.
|
|
58
|
+
JSON.Encode.object(Dict.fromArray([("itemsFor", JSON.Encode.string(id))])),
|
|
54
59
|
scanAll: async (~readModelName as _) => allItems(),
|
|
55
60
|
}
|
|
56
61
|
|
|
@@ -59,14 +64,18 @@ let capability: GraphQL_FragmentGenerator.serverCapability = {
|
|
|
59
64
|
sortFields: ["name", "status"],
|
|
60
65
|
}
|
|
61
66
|
|
|
62
|
-
let makeBinding = (
|
|
67
|
+
let makeBinding = (
|
|
68
|
+
~authorization=Reventless.Authorization.AllowAnonymous,
|
|
69
|
+
~subIdField=None,
|
|
70
|
+
(),
|
|
71
|
+
): PgQueryResolver_Lambda.binding => {
|
|
63
72
|
ops,
|
|
64
73
|
pushdowns,
|
|
65
74
|
// "byStatus" index stored on the `status` field.
|
|
66
75
|
indexes: [
|
|
67
76
|
{index: "byStatus", type_: "S", idField: "status", projectionType: Reventless.ReadModel.ALL},
|
|
68
77
|
],
|
|
69
|
-
subIdField
|
|
78
|
+
subIdField,
|
|
70
79
|
capability,
|
|
71
80
|
labelField: "name",
|
|
72
81
|
includeIdParam: true,
|
|
@@ -163,6 +172,22 @@ describe("PgQueryResolver_Lambda.dispatch", () => {
|
|
|
163
172
|
expect(edgeIds)->toEqual(["p-1", "p-2", "p-3"])
|
|
164
173
|
})
|
|
165
174
|
|
|
175
|
+
testPromise("items with subId routes to itemsPage (id echoed)", async () => {
|
|
176
|
+
let r = await PgQueryResolver_Lambda.dispatch(
|
|
177
|
+
~binding=makeBinding(~subIdField=Some("seq"), ()),
|
|
178
|
+
~payload=mkPayload(~kind="items", ~args=objArgs([("id", JSON.Encode.string("p-2"))]), ()),
|
|
179
|
+
)
|
|
180
|
+
expect(r->str("itemsFor"))->toEqual(Some("p-2"))
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
testPromise("items without subId → empty connection", async () => {
|
|
184
|
+
let r = await PgQueryResolver_Lambda.dispatch(
|
|
185
|
+
~binding=makeBinding(),
|
|
186
|
+
~payload=mkPayload(~kind="items", ~args=objArgs([("id", JSON.Encode.string("p-2"))]), ()),
|
|
187
|
+
)
|
|
188
|
+
expect(r->field("edges")->Option.flatMap(JSON.Decode.array)->Option.getOr([])->Array.length)->toBe(0)
|
|
189
|
+
})
|
|
190
|
+
|
|
166
191
|
testPromise("authorization DenyAll → empty shape per kind", async () => {
|
|
167
192
|
let b = makeBinding(~authorization=DenyAll, ())
|
|
168
193
|
let single = await PgQueryResolver_Lambda.dispatch(
|
|
@@ -191,6 +216,142 @@ describe("PgQueryResolver_Lambda.dispatch", () => {
|
|
|
191
216
|
expect(r)->toBe(JSON.Encode.null)
|
|
192
217
|
})
|
|
193
218
|
|
|
219
|
+
// ── cross-table (@resolves / @resolvesMany / node) ────────────────────────
|
|
220
|
+
// For resolveOne/resolveMany the dispatch binding IS the target binding
|
|
221
|
+
// (the handler selects it); source carries the parent object's key(s).
|
|
222
|
+
testPromise("resolveOne loads target by parent's id field", async () => {
|
|
223
|
+
let payload: PgQueryResolver_Lambda.payload = {
|
|
224
|
+
readModelName: "Things",
|
|
225
|
+
kind: "resolveOne",
|
|
226
|
+
target: "Things",
|
|
227
|
+
source: objArgs([("ref", JSON.Encode.string("p-2"))]),
|
|
228
|
+
sourceIdField: "ref",
|
|
229
|
+
multi: false,
|
|
230
|
+
arguments: objArgs([]),
|
|
231
|
+
identity: Reventless.Identity.anonymous,
|
|
232
|
+
}
|
|
233
|
+
let r = await PgQueryResolver_Lambda.dispatch(~binding=makeBinding(), ~payload)
|
|
234
|
+
expect(r->str("id"))->toEqual(Some("p-2"))
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
testPromise("resolveOne multi via target index → array", async () => {
|
|
238
|
+
let payload: PgQueryResolver_Lambda.payload = {
|
|
239
|
+
readModelName: "Things",
|
|
240
|
+
kind: "resolveOne",
|
|
241
|
+
target: "Things",
|
|
242
|
+
source: objArgs([("st", JSON.Encode.string("active"))]),
|
|
243
|
+
sourceIdField: "st",
|
|
244
|
+
targetIndex: "byStatus",
|
|
245
|
+
targetIndexIdField: "status",
|
|
246
|
+
multi: true,
|
|
247
|
+
arguments: objArgs([]),
|
|
248
|
+
identity: Reventless.Identity.anonymous,
|
|
249
|
+
}
|
|
250
|
+
let r = await PgQueryResolver_Lambda.dispatch(~binding=makeBinding(), ~payload)
|
|
251
|
+
expect(r->ids)->toEqual(["p-1", "p-3"])
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
testPromise("resolveMany batch-loads target by parent's ids field", async () => {
|
|
255
|
+
let payload: PgQueryResolver_Lambda.payload = {
|
|
256
|
+
readModelName: "Things",
|
|
257
|
+
kind: "resolveMany",
|
|
258
|
+
target: "Things",
|
|
259
|
+
source: objArgs([
|
|
260
|
+
("refs", JSON.Encode.array(["p-1", "x", "p-3"]->Array.map(JSON.Encode.string))),
|
|
261
|
+
]),
|
|
262
|
+
sourceIdsField: "refs",
|
|
263
|
+
arguments: objArgs([]),
|
|
264
|
+
identity: Reventless.Identity.anonymous,
|
|
265
|
+
}
|
|
266
|
+
let r = await PgQueryResolver_Lambda.dispatch(~binding=makeBinding(), ~payload)
|
|
267
|
+
expect(r->ids)->toEqual(["p-1", "p-3"])
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
testPromise("node decodes global id → __typename + item", async () => {
|
|
271
|
+
// Register a binding + node type in the module-level registries.
|
|
272
|
+
PgQueryResolver_Lambda.register(~readModelName="Things", makeBinding())
|
|
273
|
+
PgQueryResolver_Lambda.registerNodeType(~typeName="Thing", ~readModelName="Things")
|
|
274
|
+
let payload: PgQueryResolver_Lambda.payload = {
|
|
275
|
+
readModelName: "",
|
|
276
|
+
kind: "node",
|
|
277
|
+
arguments: objArgs([("id", JSON.Encode.string(btoa("Thing:p-2")))]),
|
|
278
|
+
identity: Reventless.Identity.anonymous,
|
|
279
|
+
}
|
|
280
|
+
let r = await PgQueryResolver_Lambda.handler(payload, Obj.magic(Nullable.null))
|
|
281
|
+
expect(r->str("__typename"))->toEqual(Some("Thing"))
|
|
282
|
+
expect(r->str("name"))->toEqual(Some("Bravo"))
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
testPromise("node with unknown type → null", async () => {
|
|
286
|
+
let payload: PgQueryResolver_Lambda.payload = {
|
|
287
|
+
readModelName: "",
|
|
288
|
+
kind: "node",
|
|
289
|
+
arguments: objArgs([("id", JSON.Encode.string(btoa("Nope:p-1")))]),
|
|
290
|
+
identity: Reventless.Identity.anonymous,
|
|
291
|
+
}
|
|
292
|
+
let r = await PgQueryResolver_Lambda.handler(payload, Obj.magic(Nullable.null))
|
|
293
|
+
expect(r)->toBe(JSON.Encode.null)
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
// ── auth-table pipeline (group-restricted index) ──────────────────────────
|
|
297
|
+
// Auth table (another read model) maps index value → { ownerId: <username> }.
|
|
298
|
+
let authStore = Dict.fromArray([
|
|
299
|
+
("active", JSON.Encode.object(Dict.fromArray([("ownerId", JSON.Encode.string("alice"))]))),
|
|
300
|
+
])
|
|
301
|
+
let authOps: QueryDb_Adapter.operations = {
|
|
302
|
+
load: async id => Ok(authStore->Dict.get(id)->Option.mapOr([], r => [r])),
|
|
303
|
+
loadStream: Obj.magic(0),
|
|
304
|
+
save: Obj.magic(0),
|
|
305
|
+
saveBatch: Obj.magic(0),
|
|
306
|
+
count: Obj.magic(0),
|
|
307
|
+
delete: Obj.magic(0),
|
|
308
|
+
deleteBatch: Obj.magic(0),
|
|
309
|
+
}
|
|
310
|
+
let authBinding = {...makeBinding(), PgQueryResolver_Lambda.ops: authOps}
|
|
311
|
+
let lookupAuth = name => name == "AuthTable" ? Some(authBinding) : None
|
|
312
|
+
let ident = (~username, ~groups): Reventless.Identity.t => {
|
|
313
|
+
userId: username,
|
|
314
|
+
username,
|
|
315
|
+
groups,
|
|
316
|
+
provider: InMemory,
|
|
317
|
+
}
|
|
318
|
+
let authIndexPayload = (~username, ~groups): PgQueryResolver_Lambda.payload => {
|
|
319
|
+
readModelName: "Things",
|
|
320
|
+
kind: "index",
|
|
321
|
+
index: "byStatus",
|
|
322
|
+
authTable: "AuthTable",
|
|
323
|
+
authGroup: "Owner",
|
|
324
|
+
arguments: objArgs([("byStatus", JSON.Encode.string("active"))]),
|
|
325
|
+
identity: ident(~username, ~groups),
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
testPromise("auth index: group member who owns → results", async () => {
|
|
329
|
+
let r = await PgQueryResolver_Lambda.dispatch(
|
|
330
|
+
~binding=makeBinding(),
|
|
331
|
+
~lookupBinding=lookupAuth,
|
|
332
|
+
~payload=authIndexPayload(~username="alice", ~groups=["Owner"]),
|
|
333
|
+
)
|
|
334
|
+
expect(r->ids)->toEqual(["p-1", "p-3"])
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
testPromise("auth index: group member who does NOT own → empty", async () => {
|
|
338
|
+
let r = await PgQueryResolver_Lambda.dispatch(
|
|
339
|
+
~binding=makeBinding(),
|
|
340
|
+
~lookupBinding=lookupAuth,
|
|
341
|
+
~payload=authIndexPayload(~username="bob", ~groups=["Owner"]),
|
|
342
|
+
)
|
|
343
|
+
expect(r->JSON.Decode.array->Option.getOr([])->Array.length)->toBe(0)
|
|
344
|
+
})
|
|
345
|
+
|
|
346
|
+
testPromise("auth index: non-member passes through → results", async () => {
|
|
347
|
+
let r = await PgQueryResolver_Lambda.dispatch(
|
|
348
|
+
~binding=makeBinding(),
|
|
349
|
+
~lookupBinding=lookupAuth,
|
|
350
|
+
~payload=authIndexPayload(~username="bob", ~groups=[]),
|
|
351
|
+
)
|
|
352
|
+
expect(r->ids)->toEqual(["p-1", "p-3"])
|
|
353
|
+
})
|
|
354
|
+
|
|
194
355
|
testPromise("unmapped kind throws", async () => {
|
|
195
356
|
let threw = try {
|
|
196
357
|
let _ = await PgQueryResolver_Lambda.dispatch(
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
|
|
4
4
|
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
5
5
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
6
|
+
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
6
7
|
import * as Primitive_string from "@rescript/runtime/lib/es6/Primitive_string.js";
|
|
7
8
|
import * as Identity$Reventless from "@reventlessdev/reventless-spec/src/types/Identity.res.mjs";
|
|
8
9
|
import * as QueryDb_Callback$ReventlessCore from "@reventlessdev/reventless-core/src/components/QueryDb/QueryDb_Callback.res.mjs";
|
|
@@ -72,6 +73,13 @@ async function pushdowns_listPage(param, param$1, param$2, param$3) {
|
|
|
72
73
|
return listPageReturn.contents;
|
|
73
74
|
}
|
|
74
75
|
|
|
76
|
+
async function pushdowns_itemsPage(param, param$1, id, param$2) {
|
|
77
|
+
return Object.fromEntries([[
|
|
78
|
+
"itemsFor",
|
|
79
|
+
id
|
|
80
|
+
]]);
|
|
81
|
+
}
|
|
82
|
+
|
|
75
83
|
async function pushdowns_scanAll(param) {
|
|
76
84
|
return Object.values(store);
|
|
77
85
|
}
|
|
@@ -80,6 +88,7 @@ let pushdowns = {
|
|
|
80
88
|
indexLookup: pushdowns_indexLookup,
|
|
81
89
|
byIds: pushdowns_byIds,
|
|
82
90
|
listPage: pushdowns_listPage,
|
|
91
|
+
itemsPage: pushdowns_itemsPage,
|
|
83
92
|
scanAll: pushdowns_scanAll
|
|
84
93
|
};
|
|
85
94
|
|
|
@@ -99,8 +108,9 @@ let capability = {
|
|
|
99
108
|
sortFields: capability_sortFields
|
|
100
109
|
};
|
|
101
110
|
|
|
102
|
-
function makeBinding(authorizationOpt, param) {
|
|
111
|
+
function makeBinding(authorizationOpt, subIdFieldOpt, param) {
|
|
103
112
|
let authorization = authorizationOpt !== undefined ? authorizationOpt : "AllowAnonymous";
|
|
113
|
+
let subIdField = subIdFieldOpt !== undefined ? Primitive_option.valFromOption(subIdFieldOpt) : undefined;
|
|
104
114
|
return {
|
|
105
115
|
ops: ops,
|
|
106
116
|
pushdowns: pushdowns,
|
|
@@ -110,7 +120,7 @@ function makeBinding(authorizationOpt, param) {
|
|
|
110
120
|
idField: "status",
|
|
111
121
|
projectionType: "ALL"
|
|
112
122
|
}],
|
|
113
|
-
subIdField:
|
|
123
|
+
subIdField: subIdField,
|
|
114
124
|
capability: capability,
|
|
115
125
|
labelField: "name",
|
|
116
126
|
includeIdParam: true,
|
|
@@ -147,7 +157,7 @@ function ids(arr) {
|
|
|
147
157
|
|
|
148
158
|
globalThis.describe("PgQueryResolver_Lambda.dispatch", () => {
|
|
149
159
|
globalThis.test("getById returns the item (id-carrying)", async () => {
|
|
150
|
-
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined), mkPayload("getById", undefined, Object.fromEntries([[
|
|
160
|
+
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined), undefined, mkPayload("getById", undefined, Object.fromEntries([[
|
|
151
161
|
"id",
|
|
152
162
|
"p-2"
|
|
153
163
|
]]), undefined));
|
|
@@ -155,14 +165,14 @@ globalThis.describe("PgQueryResolver_Lambda.dispatch", () => {
|
|
|
155
165
|
globalThis.expect(str(r, "name")).toEqual("Bravo");
|
|
156
166
|
});
|
|
157
167
|
globalThis.test("getById miss → null", async () => {
|
|
158
|
-
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined), mkPayload("getById", undefined, Object.fromEntries([[
|
|
168
|
+
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined), undefined, mkPayload("getById", undefined, Object.fromEntries([[
|
|
159
169
|
"id",
|
|
160
170
|
"absent"
|
|
161
171
|
]]), undefined));
|
|
162
172
|
globalThis.expect(r).toBe(null);
|
|
163
173
|
});
|
|
164
174
|
globalThis.test("byIds returns matches, drops missing", async () => {
|
|
165
|
-
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined), mkPayload("byIds", undefined, Object.fromEntries([[
|
|
175
|
+
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined), undefined, mkPayload("byIds", undefined, Object.fromEntries([[
|
|
166
176
|
"ids",
|
|
167
177
|
[
|
|
168
178
|
"p-1",
|
|
@@ -176,7 +186,7 @@ globalThis.describe("PgQueryResolver_Lambda.dispatch", () => {
|
|
|
176
186
|
]);
|
|
177
187
|
});
|
|
178
188
|
globalThis.test("index routes to indexLookup on the idField", async () => {
|
|
179
|
-
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined), mkPayload("index", "byStatus", Object.fromEntries([[
|
|
189
|
+
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined), undefined, mkPayload("index", "byStatus", Object.fromEntries([[
|
|
180
190
|
"byStatus",
|
|
181
191
|
"active"
|
|
182
192
|
]]), undefined));
|
|
@@ -187,13 +197,13 @@ globalThis.describe("PgQueryResolver_Lambda.dispatch", () => {
|
|
|
187
197
|
});
|
|
188
198
|
globalThis.test("list with push-down → returns the connection listPage gives", async () => {
|
|
189
199
|
listPageReturn.contents = "PUSHED";
|
|
190
|
-
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined), mkPayload("list", undefined, undefined, undefined));
|
|
200
|
+
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined), undefined, mkPayload("list", undefined, undefined, undefined));
|
|
191
201
|
listPageReturn.contents = undefined;
|
|
192
202
|
globalThis.expect(r).toEqual("PUSHED");
|
|
193
203
|
});
|
|
194
204
|
globalThis.test("list without push-down → falls back to the shared spec", async () => {
|
|
195
205
|
listPageReturn.contents = undefined;
|
|
196
|
-
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined), mkPayload("list", undefined, undefined, undefined));
|
|
206
|
+
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined), undefined, mkPayload("list", undefined, undefined, undefined));
|
|
197
207
|
let edgeIds = Stdlib_Array.filterMap(Stdlib_Option.getOr(Stdlib_Option.flatMap(field(r, "edges"), Stdlib_JSON.Decode.array), []), e => Stdlib_Option.flatMap(field(e, "node"), n => str(n, "id")));
|
|
198
208
|
globalThis.expect(edgeIds).toEqual([
|
|
199
209
|
"p-1",
|
|
@@ -201,16 +211,30 @@ globalThis.describe("PgQueryResolver_Lambda.dispatch", () => {
|
|
|
201
211
|
"p-3"
|
|
202
212
|
]);
|
|
203
213
|
});
|
|
214
|
+
globalThis.test("items with subId routes to itemsPage (id echoed)", async () => {
|
|
215
|
+
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, "seq", undefined), undefined, mkPayload("items", undefined, Object.fromEntries([[
|
|
216
|
+
"id",
|
|
217
|
+
"p-2"
|
|
218
|
+
]]), undefined));
|
|
219
|
+
globalThis.expect(str(r, "itemsFor")).toEqual("p-2");
|
|
220
|
+
});
|
|
221
|
+
globalThis.test("items without subId → empty connection", async () => {
|
|
222
|
+
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined), undefined, mkPayload("items", undefined, Object.fromEntries([[
|
|
223
|
+
"id",
|
|
224
|
+
"p-2"
|
|
225
|
+
]]), undefined));
|
|
226
|
+
globalThis.expect(Stdlib_Option.getOr(Stdlib_Option.flatMap(field(r, "edges"), Stdlib_JSON.Decode.array), []).length).toBe(0);
|
|
227
|
+
});
|
|
204
228
|
globalThis.test("authorization DenyAll → empty shape per kind", async () => {
|
|
205
|
-
let b = makeBinding("DenyAll", undefined);
|
|
206
|
-
let single = await PgQueryResolver_Lambda$ReventlessAws.dispatch(b, mkPayload("getById", undefined, Object.fromEntries([[
|
|
229
|
+
let b = makeBinding("DenyAll", undefined, undefined);
|
|
230
|
+
let single = await PgQueryResolver_Lambda$ReventlessAws.dispatch(b, undefined, mkPayload("getById", undefined, Object.fromEntries([[
|
|
207
231
|
"id",
|
|
208
232
|
"p-1"
|
|
209
233
|
]]), undefined));
|
|
210
234
|
globalThis.expect(single).toBe(null);
|
|
211
|
-
let list = await PgQueryResolver_Lambda$ReventlessAws.dispatch(b, mkPayload("list", undefined, undefined, undefined));
|
|
235
|
+
let list = await PgQueryResolver_Lambda$ReventlessAws.dispatch(b, undefined, mkPayload("list", undefined, undefined, undefined));
|
|
212
236
|
globalThis.expect(Stdlib_Option.getOr(Stdlib_Option.flatMap(field(list, "edges"), Stdlib_JSON.Decode.array), []).length).toBe(0);
|
|
213
|
-
let byIds = await PgQueryResolver_Lambda$ReventlessAws.dispatch(b, mkPayload("byIds", undefined, Object.fromEntries([[
|
|
237
|
+
let byIds = await PgQueryResolver_Lambda$ReventlessAws.dispatch(b, undefined, mkPayload("byIds", undefined, Object.fromEntries([[
|
|
214
238
|
"ids",
|
|
215
239
|
[]
|
|
216
240
|
]]), undefined));
|
|
@@ -221,17 +245,205 @@ globalThis.describe("PgQueryResolver_Lambda.dispatch", () => {
|
|
|
221
245
|
TAG: "Deny",
|
|
222
246
|
_0: "nope"
|
|
223
247
|
}));
|
|
224
|
-
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined), mkPayload("getById", undefined, Object.fromEntries([[
|
|
248
|
+
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined), undefined, mkPayload("getById", undefined, Object.fromEntries([[
|
|
225
249
|
"id",
|
|
226
250
|
"p-1"
|
|
227
251
|
]]), undefined));
|
|
228
252
|
QueryDb_Callback$ReventlessCore.clearQueryInterceptor();
|
|
229
253
|
globalThis.expect(r).toBe(null);
|
|
230
254
|
});
|
|
255
|
+
globalThis.test("resolveOne loads target by parent's id field", async () => {
|
|
256
|
+
let payload_target = "Things";
|
|
257
|
+
let payload_source = Object.fromEntries([[
|
|
258
|
+
"ref",
|
|
259
|
+
"p-2"
|
|
260
|
+
]]);
|
|
261
|
+
let payload_sourceIdField = "ref";
|
|
262
|
+
let payload_multi = false;
|
|
263
|
+
let payload_arguments = Object.fromEntries([]);
|
|
264
|
+
let payload = {
|
|
265
|
+
readModelName: "Things",
|
|
266
|
+
kind: "resolveOne",
|
|
267
|
+
target: payload_target,
|
|
268
|
+
source: payload_source,
|
|
269
|
+
sourceIdField: payload_sourceIdField,
|
|
270
|
+
multi: payload_multi,
|
|
271
|
+
arguments: payload_arguments,
|
|
272
|
+
identity: Identity$Reventless.anonymous
|
|
273
|
+
};
|
|
274
|
+
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined), undefined, payload);
|
|
275
|
+
globalThis.expect(str(r, "id")).toEqual("p-2");
|
|
276
|
+
});
|
|
277
|
+
globalThis.test("resolveOne multi via target index → array", async () => {
|
|
278
|
+
let payload_target = "Things";
|
|
279
|
+
let payload_source = Object.fromEntries([[
|
|
280
|
+
"st",
|
|
281
|
+
"active"
|
|
282
|
+
]]);
|
|
283
|
+
let payload_sourceIdField = "st";
|
|
284
|
+
let payload_targetIndex = "byStatus";
|
|
285
|
+
let payload_targetIndexIdField = "status";
|
|
286
|
+
let payload_multi = true;
|
|
287
|
+
let payload_arguments = Object.fromEntries([]);
|
|
288
|
+
let payload = {
|
|
289
|
+
readModelName: "Things",
|
|
290
|
+
kind: "resolveOne",
|
|
291
|
+
target: payload_target,
|
|
292
|
+
source: payload_source,
|
|
293
|
+
sourceIdField: payload_sourceIdField,
|
|
294
|
+
targetIndex: payload_targetIndex,
|
|
295
|
+
targetIndexIdField: payload_targetIndexIdField,
|
|
296
|
+
multi: payload_multi,
|
|
297
|
+
arguments: payload_arguments,
|
|
298
|
+
identity: Identity$Reventless.anonymous
|
|
299
|
+
};
|
|
300
|
+
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined), undefined, payload);
|
|
301
|
+
globalThis.expect(ids(r)).toEqual([
|
|
302
|
+
"p-1",
|
|
303
|
+
"p-3"
|
|
304
|
+
]);
|
|
305
|
+
});
|
|
306
|
+
globalThis.test("resolveMany batch-loads target by parent's ids field", async () => {
|
|
307
|
+
let payload_target = "Things";
|
|
308
|
+
let payload_source = Object.fromEntries([[
|
|
309
|
+
"refs",
|
|
310
|
+
[
|
|
311
|
+
"p-1",
|
|
312
|
+
"x",
|
|
313
|
+
"p-3"
|
|
314
|
+
].map(prim => prim)
|
|
315
|
+
]]);
|
|
316
|
+
let payload_sourceIdsField = "refs";
|
|
317
|
+
let payload_arguments = Object.fromEntries([]);
|
|
318
|
+
let payload = {
|
|
319
|
+
readModelName: "Things",
|
|
320
|
+
kind: "resolveMany",
|
|
321
|
+
target: payload_target,
|
|
322
|
+
source: payload_source,
|
|
323
|
+
sourceIdsField: payload_sourceIdsField,
|
|
324
|
+
arguments: payload_arguments,
|
|
325
|
+
identity: Identity$Reventless.anonymous
|
|
326
|
+
};
|
|
327
|
+
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined), undefined, payload);
|
|
328
|
+
globalThis.expect(ids(r)).toEqual([
|
|
329
|
+
"p-1",
|
|
330
|
+
"p-3"
|
|
331
|
+
]);
|
|
332
|
+
});
|
|
333
|
+
globalThis.test("node decodes global id → __typename + item", async () => {
|
|
334
|
+
PgQueryResolver_Lambda$ReventlessAws.register("Things", makeBinding(undefined, undefined, undefined));
|
|
335
|
+
PgQueryResolver_Lambda$ReventlessAws.registerNodeType("Thing", "Things");
|
|
336
|
+
let payload_arguments = Object.fromEntries([[
|
|
337
|
+
"id",
|
|
338
|
+
btoa("Thing:p-2")
|
|
339
|
+
]]);
|
|
340
|
+
let payload = {
|
|
341
|
+
readModelName: "",
|
|
342
|
+
kind: "node",
|
|
343
|
+
arguments: payload_arguments,
|
|
344
|
+
identity: Identity$Reventless.anonymous
|
|
345
|
+
};
|
|
346
|
+
let r = await PgQueryResolver_Lambda$ReventlessAws.handler(payload, null);
|
|
347
|
+
globalThis.expect(str(r, "__typename")).toEqual("Thing");
|
|
348
|
+
globalThis.expect(str(r, "name")).toEqual("Bravo");
|
|
349
|
+
});
|
|
350
|
+
globalThis.test("node with unknown type → null", async () => {
|
|
351
|
+
let payload_arguments = Object.fromEntries([[
|
|
352
|
+
"id",
|
|
353
|
+
btoa("Nope:p-1")
|
|
354
|
+
]]);
|
|
355
|
+
let payload = {
|
|
356
|
+
readModelName: "",
|
|
357
|
+
kind: "node",
|
|
358
|
+
arguments: payload_arguments,
|
|
359
|
+
identity: Identity$Reventless.anonymous
|
|
360
|
+
};
|
|
361
|
+
let r = await PgQueryResolver_Lambda$ReventlessAws.handler(payload, null);
|
|
362
|
+
globalThis.expect(r).toBe(null);
|
|
363
|
+
});
|
|
364
|
+
let authStore = Object.fromEntries([[
|
|
365
|
+
"active",
|
|
366
|
+
Object.fromEntries([[
|
|
367
|
+
"ownerId",
|
|
368
|
+
"alice"
|
|
369
|
+
]])
|
|
370
|
+
]]);
|
|
371
|
+
let authOps_load = async id => ({
|
|
372
|
+
TAG: "Ok",
|
|
373
|
+
_0: Stdlib_Option.mapOr(authStore[id], [], r => [r])
|
|
374
|
+
});
|
|
375
|
+
let authOps = {
|
|
376
|
+
load: authOps_load,
|
|
377
|
+
loadStream: 0,
|
|
378
|
+
save: 0,
|
|
379
|
+
saveBatch: 0,
|
|
380
|
+
count: 0,
|
|
381
|
+
delete: 0,
|
|
382
|
+
deleteBatch: 0
|
|
383
|
+
};
|
|
384
|
+
let init = makeBinding(undefined, undefined, undefined);
|
|
385
|
+
let authBinding_pushdowns = init.pushdowns;
|
|
386
|
+
let authBinding_indexes = init.indexes;
|
|
387
|
+
let authBinding_subIdField = init.subIdField;
|
|
388
|
+
let authBinding_capability = init.capability;
|
|
389
|
+
let authBinding_labelField = init.labelField;
|
|
390
|
+
let authBinding_includeIdParam = init.includeIdParam;
|
|
391
|
+
let authBinding_authorization = init.authorization;
|
|
392
|
+
let authBinding = {
|
|
393
|
+
ops: authOps,
|
|
394
|
+
pushdowns: authBinding_pushdowns,
|
|
395
|
+
indexes: authBinding_indexes,
|
|
396
|
+
subIdField: authBinding_subIdField,
|
|
397
|
+
capability: authBinding_capability,
|
|
398
|
+
labelField: authBinding_labelField,
|
|
399
|
+
includeIdParam: authBinding_includeIdParam,
|
|
400
|
+
authorization: authBinding_authorization
|
|
401
|
+
};
|
|
402
|
+
let lookupAuth = name => {
|
|
403
|
+
if (name === "AuthTable") {
|
|
404
|
+
return authBinding;
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
let ident = (username, groups) => ({
|
|
408
|
+
userId: username,
|
|
409
|
+
username: username,
|
|
410
|
+
groups: groups,
|
|
411
|
+
provider: "InMemory"
|
|
412
|
+
});
|
|
413
|
+
let authIndexPayload = (username, groups) => ({
|
|
414
|
+
readModelName: "Things",
|
|
415
|
+
kind: "index",
|
|
416
|
+
index: "byStatus",
|
|
417
|
+
authTable: "AuthTable",
|
|
418
|
+
authGroup: "Owner",
|
|
419
|
+
arguments: Object.fromEntries([[
|
|
420
|
+
"byStatus",
|
|
421
|
+
"active"
|
|
422
|
+
]]),
|
|
423
|
+
identity: ident(username, groups)
|
|
424
|
+
});
|
|
425
|
+
globalThis.test("auth index: group member who owns → results", async () => {
|
|
426
|
+
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined), lookupAuth, authIndexPayload("alice", ["Owner"]));
|
|
427
|
+
globalThis.expect(ids(r)).toEqual([
|
|
428
|
+
"p-1",
|
|
429
|
+
"p-3"
|
|
430
|
+
]);
|
|
431
|
+
});
|
|
432
|
+
globalThis.test("auth index: group member who does NOT own → empty", async () => {
|
|
433
|
+
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined), lookupAuth, authIndexPayload("bob", ["Owner"]));
|
|
434
|
+
globalThis.expect(Stdlib_Option.getOr(Stdlib_JSON.Decode.array(r), []).length).toBe(0);
|
|
435
|
+
});
|
|
436
|
+
globalThis.test("auth index: non-member passes through → results", async () => {
|
|
437
|
+
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined), lookupAuth, authIndexPayload("bob", []));
|
|
438
|
+
globalThis.expect(ids(r)).toEqual([
|
|
439
|
+
"p-1",
|
|
440
|
+
"p-3"
|
|
441
|
+
]);
|
|
442
|
+
});
|
|
231
443
|
globalThis.test("unmapped kind throws", async () => {
|
|
232
444
|
let threw;
|
|
233
445
|
try {
|
|
234
|
-
await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined), mkPayload("frobnicate", undefined, undefined, undefined));
|
|
446
|
+
await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined), undefined, mkPayload("frobnicate", undefined, undefined, undefined));
|
|
235
447
|
threw = false;
|
|
236
448
|
} catch (exn) {
|
|
237
449
|
threw = true;
|