@reventlessdev/reventless-aws 3.0.0-alpha.282 → 3.0.0-alpha.283
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 +10 -0
- package/package.json +9 -9
- package/src/adapter/QueryDb/PgQueryResolver_Lambda.res +30 -11
- package/src/adapter/QueryDb/PgQueryResolver_Lambda.res.mjs +48 -33
- package/src/adapter/QueryDb/QueryDbResolvers_AppSync.res +1 -1
- package/src/adapter/QueryDb/QueryDbResolvers_AppSync.res.mjs +1 -1
- package/src/adapter/Runtime/PgQueryResolverEntryPoint_Ops.res +1 -0
- package/src/adapter/Runtime/PgQueryResolverEntryPoint_Ops.res.mjs +1 -1
- package/tests/PgQueryResolver_LambdaTest.res +90 -0
- package/tests/PgQueryResolver_LambdaTest.res.mjs +58 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,16 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# 3.0.0-alpha.283 (2026-08-11)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **api:** one id contract across the read-side query doors ([c52fac2](https://github.com/ReventlessDev/reventless-core/commit/c52fac2bee87b3dd1fa33b4970a74f3cd3866280))
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* **api:** infer a queryable's key field and publish its provenance ([c835a42](https://github.com/ReventlessDev/reventless-core/commit/c835a42a0da07cdc4a3f010212e1f340a4a0ca27))
|
|
14
|
+
|
|
15
|
+
|
|
6
16
|
# 3.0.0-alpha.282 (2026-08-10)
|
|
7
17
|
|
|
8
18
|
### Bug Fixes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-aws",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.283",
|
|
4
4
|
"description": "AWS adapters for Reventless",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"dependencies": {
|
|
@@ -12,18 +12,18 @@
|
|
|
12
12
|
"@aws-sdk/s3-request-presigner": "3.970.0",
|
|
13
13
|
"sury": "11.0.0-alpha.4",
|
|
14
14
|
"uuid": "^13.0.0",
|
|
15
|
-
"@reventlessdev/rescript-aws-sdk": "3.0.0-alpha.
|
|
15
|
+
"@reventlessdev/rescript-aws-sdk": "3.0.0-alpha.7",
|
|
16
16
|
"@reventlessdev/rescript-effect": "0.1.0-alpha.32",
|
|
17
|
+
"@reventlessdev/rescript-pulumi-aws": "2.4.0-alpha.70",
|
|
18
|
+
"@reventlessdev/rescript-node": "2.0.0-alpha.4",
|
|
17
19
|
"@reventlessdev/rescript-jest": "1.0.0-alpha.10",
|
|
18
|
-
"@reventlessdev/rescript-node": "2.0.0-alpha.3",
|
|
19
|
-
"@reventlessdev/rescript-pulumi-aws": "2.4.0-alpha.69",
|
|
20
20
|
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.18",
|
|
21
|
-
"@reventlessdev/
|
|
22
|
-
"@reventlessdev/reventless-infra": "3.0.0-alpha.133",
|
|
23
|
-
"@reventlessdev/reventless-postgres": "3.0.0-alpha.87",
|
|
24
|
-
"@reventlessdev/reventless-spec": "3.0.0-alpha.107",
|
|
21
|
+
"@reventlessdev/reventless-core": "3.0.0-alpha.224",
|
|
25
22
|
"@reventlessdev/reventless-interop": "3.0.0-alpha.30",
|
|
26
|
-
"@reventlessdev/reventless-
|
|
23
|
+
"@reventlessdev/reventless-postgres": "3.0.0-alpha.88",
|
|
24
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.108",
|
|
25
|
+
"@reventlessdev/rescript-uuid": "2.0.0-alpha.0",
|
|
26
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.134"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"rescript": "12.3.0",
|
|
@@ -165,18 +165,39 @@ let dispatch = async (
|
|
|
165
165
|
switch payload.kind {
|
|
166
166
|
| "getById" =>
|
|
167
167
|
let id = payload.arguments->argStr("id")->Option.getOr("")
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
|
172
|
-
| None => JSON.Encode.null
|
|
168
|
+
let loadKey = async key =>
|
|
169
|
+
switch await binding.ops.load(key) {
|
|
170
|
+
| Ok(items) => items->Array.get(0)
|
|
171
|
+
| Error(_) => None
|
|
173
172
|
}
|
|
174
|
-
|
|
173
|
+
// A caller holding a row's Relay global id must reach the row through this
|
|
174
|
+
// door too. Raw key first — it is what this door has always taken, and a
|
|
175
|
+
// key that merely looks like base64 must keep resolving to its own row.
|
|
176
|
+
let (resolvedKey, found) = switch (
|
|
177
|
+
await loadKey(id),
|
|
178
|
+
ReventlessCore.Api_Ids.alternateKey(id),
|
|
179
|
+
) {
|
|
180
|
+
| (None, Some(localId)) => (localId, await loadKey(localId))
|
|
181
|
+
| (found, _) => (id, found)
|
|
182
|
+
}
|
|
183
|
+
switch found {
|
|
184
|
+
| Some(item) => binding.includeIdParam ? withId(item, resolvedKey) : item
|
|
185
|
+
| None => JSON.Encode.null
|
|
175
186
|
}
|
|
176
187
|
|
|
177
188
|
| "byIds" =>
|
|
178
189
|
let ids = payload.arguments->argStrs("ids")
|
|
179
|
-
|
|
190
|
+
let found = await binding.pushdowns.byIds(~readModelName=rm, ids)
|
|
191
|
+
// Only pay for the second lookup when the first came up short, and only for
|
|
192
|
+
// the ids that are global ones.
|
|
193
|
+
let missing =
|
|
194
|
+
found->Array.length < ids->Array.length
|
|
195
|
+
? ids->Array.filterMap(ReventlessCore.Api_Ids.alternateKey)
|
|
196
|
+
: []
|
|
197
|
+
let extra = missing->Array.length > 0
|
|
198
|
+
? await binding.pushdowns.byIds(~readModelName=rm, missing)
|
|
199
|
+
: []
|
|
200
|
+
JSON.Encode.array(Array.concat(found, extra))
|
|
180
201
|
|
|
181
202
|
| "items" =>
|
|
182
203
|
// Sub-id connection: {single}Items(id, filter, first/after/last/before).
|
|
@@ -243,16 +264,14 @@ let dispatch = async (
|
|
|
243
264
|
| Some(conn) => conn
|
|
244
265
|
| None =>
|
|
245
266
|
// Shapes listPage declines (search/searchPrefix/ids/backward) → run the
|
|
246
|
-
// shared spec over the materialised model
|
|
247
|
-
//
|
|
248
|
-
// matches raw item ids).
|
|
267
|
+
// shared spec over the materialised model, which decodes Relay global
|
|
268
|
+
// ids the same way every other door now does.
|
|
249
269
|
let items = await binding.pushdowns.scanAll(~readModelName=rm)
|
|
250
270
|
ReventlessCore.QueryDbListQuery.run(
|
|
251
271
|
~items,
|
|
252
272
|
~argsDict,
|
|
253
273
|
~capability=binding.capability,
|
|
254
274
|
~labelField=binding.labelField,
|
|
255
|
-
~decodeLocalId=_ => None,
|
|
256
275
|
)
|
|
257
276
|
}
|
|
258
277
|
|
|
@@ -6,6 +6,7 @@ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
|
6
6
|
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
7
7
|
import * as Identity$Reventless from "@reventlessdev/reventless-spec/src/types/Identity.res.mjs";
|
|
8
8
|
import * as Logger$ReventlessCore from "@reventlessdev/reventless-core/src/util/Logger.res.mjs";
|
|
9
|
+
import * as Api_Ids$ReventlessCore from "@reventlessdev/reventless-core/src/components/Api/Api_Ids.res.mjs";
|
|
9
10
|
import * as Authorization$Reventless from "@reventlessdev/reventless-spec/src/types/Authorization.res.mjs";
|
|
10
11
|
import * as QueryDbListQuery$ReventlessCore from "@reventlessdev/reventless-core/src/components/Api/QueryDbListQuery.res.mjs";
|
|
11
12
|
import * as QueryDb_Callback$ReventlessCore from "@reventlessdev/reventless-core/src/components/QueryDb/QueryDb_Callback.res.mjs";
|
|
@@ -74,19 +75,33 @@ async function dispatch(binding, lookupBindingOpt, payload) {
|
|
|
74
75
|
switch (other) {
|
|
75
76
|
case "byIds" :
|
|
76
77
|
let ids = argStrs(payload.arguments, "ids");
|
|
77
|
-
|
|
78
|
+
let found = await binding.pushdowns.byIds(rm, ids);
|
|
79
|
+
let missing = found.length < ids.length ? Stdlib_Array.filterMap(ids, Api_Ids$ReventlessCore.alternateKey) : [];
|
|
80
|
+
let extra = missing.length !== 0 ? await binding.pushdowns.byIds(rm, missing) : [];
|
|
81
|
+
return found.concat(extra);
|
|
78
82
|
case "getById" :
|
|
79
83
|
let id = Stdlib_Option.getOr(argStr(payload.arguments, "id"), "");
|
|
80
|
-
let
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
84
|
+
let loadKey = async key => {
|
|
85
|
+
let items = await binding.ops.load(key);
|
|
86
|
+
if (items.TAG === "Ok") {
|
|
87
|
+
return items._0[0];
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
let match$1 = await loadKey(id);
|
|
91
|
+
let match$2 = Api_Ids$ReventlessCore.alternateKey(id);
|
|
92
|
+
let match$3 = match$1 !== undefined || match$2 === undefined ? [
|
|
93
|
+
id,
|
|
94
|
+
match$1
|
|
95
|
+
] : [
|
|
96
|
+
match$2,
|
|
97
|
+
await loadKey(match$2)
|
|
98
|
+
];
|
|
99
|
+
let found$1 = match$3[1];
|
|
100
|
+
if (found$1 !== undefined) {
|
|
86
101
|
if (binding.includeIdParam) {
|
|
87
|
-
return withId(
|
|
102
|
+
return withId(found$1, match$3[0]);
|
|
88
103
|
} else {
|
|
89
|
-
return
|
|
104
|
+
return found$1;
|
|
90
105
|
}
|
|
91
106
|
} else {
|
|
92
107
|
return null;
|
|
@@ -95,14 +110,14 @@ async function dispatch(binding, lookupBindingOpt, payload) {
|
|
|
95
110
|
let indexName = Stdlib_Option.getOr(payload.index, "");
|
|
96
111
|
let field = Stdlib_Option.getOr(Stdlib_Option.flatMap(binding.indexes.find(ic => ic.index === indexName), ic => ic.idField), indexName);
|
|
97
112
|
let value = Stdlib_Option.getOr(argStr(payload.arguments, indexName), "");
|
|
98
|
-
let match$
|
|
99
|
-
let match$
|
|
113
|
+
let match$4 = payload.authGroup;
|
|
114
|
+
let match$5 = payload.authTable;
|
|
100
115
|
let authorized;
|
|
101
|
-
if (match$
|
|
102
|
-
let authBinding = lookupBinding(match$
|
|
116
|
+
if (match$4 !== undefined && match$5 !== undefined && Identity$Reventless.hasGroup(payload.identity, match$4)) {
|
|
117
|
+
let authBinding = lookupBinding(match$5);
|
|
103
118
|
if (authBinding !== undefined) {
|
|
104
|
-
let items
|
|
105
|
-
authorized = items
|
|
119
|
+
let items = await authBinding.ops.load(value);
|
|
120
|
+
authorized = items.TAG === "Ok" ? Stdlib_Option.mapOr(Stdlib_Option.flatMap(items._0[0], row => argStr(row, authIdField(match$4))), false, owner => owner === payload.identity.username) : false;
|
|
106
121
|
} else {
|
|
107
122
|
authorized = false;
|
|
108
123
|
}
|
|
@@ -127,8 +142,8 @@ async function dispatch(binding, lookupBindingOpt, payload) {
|
|
|
127
142
|
if (conn !== undefined) {
|
|
128
143
|
return conn;
|
|
129
144
|
}
|
|
130
|
-
let items$
|
|
131
|
-
return QueryDbListQuery$ReventlessCore.run(items$
|
|
145
|
+
let items$1 = await binding.pushdowns.scanAll(rm);
|
|
146
|
+
return QueryDbListQuery$ReventlessCore.run(items$1, argsDict, binding.capability, binding.labelField, undefined);
|
|
132
147
|
case "resolveMany" :
|
|
133
148
|
let target = Stdlib_Option.getOr(payload.target, rm);
|
|
134
149
|
let source = Stdlib_Option.getOr(payload.source, null);
|
|
@@ -139,30 +154,30 @@ async function dispatch(binding, lookupBindingOpt, payload) {
|
|
|
139
154
|
let source$1 = Stdlib_Option.getOr(payload.source, null);
|
|
140
155
|
let key = Stdlib_Option.getOr(argStr(source$1, Stdlib_Option.getOr(payload.sourceIdField, "")), "");
|
|
141
156
|
let ix = payload.targetIndex;
|
|
142
|
-
let items$
|
|
157
|
+
let items$2;
|
|
143
158
|
if (ix !== undefined) {
|
|
144
|
-
items$
|
|
159
|
+
items$2 = await binding.pushdowns.indexLookup(target$1, Stdlib_Option.getOr(payload.targetIndexIdField, ix), key);
|
|
145
160
|
} else {
|
|
146
|
-
let items$
|
|
147
|
-
items$
|
|
161
|
+
let items$3 = await binding.ops.load(key);
|
|
162
|
+
items$2 = items$3.TAG === "Ok" ? items$3._0 : [];
|
|
148
163
|
}
|
|
149
|
-
let match$
|
|
150
|
-
let match$
|
|
164
|
+
let match$6 = payload.sourceSubId;
|
|
165
|
+
let match$7 = binding.subIdField;
|
|
151
166
|
let filtered;
|
|
152
|
-
if (match$
|
|
153
|
-
let name = match$
|
|
154
|
-
let tmp = match$
|
|
167
|
+
if (match$6 !== undefined && match$7 !== undefined) {
|
|
168
|
+
let name = match$6.name;
|
|
169
|
+
let tmp = match$6.kind === "arg" ? argStr(payload.arguments, name) : argStr(source$1, name);
|
|
155
170
|
let subVal = Stdlib_Option.getOr(tmp, "");
|
|
156
|
-
filtered = items$
|
|
171
|
+
filtered = items$2.filter(it => Stdlib_Option.getOr(argStr(it, match$7), "") === subVal);
|
|
157
172
|
} else {
|
|
158
|
-
filtered = items$
|
|
173
|
+
filtered = items$2;
|
|
159
174
|
}
|
|
160
175
|
if (Stdlib_Option.getOr(payload.multi, false)) {
|
|
161
176
|
return filtered;
|
|
162
177
|
}
|
|
163
|
-
let item
|
|
164
|
-
if (item
|
|
165
|
-
return item
|
|
178
|
+
let item = filtered[0];
|
|
179
|
+
if (item !== undefined) {
|
|
180
|
+
return item;
|
|
166
181
|
} else {
|
|
167
182
|
return null;
|
|
168
183
|
}
|
|
@@ -172,8 +187,8 @@ async function dispatch(binding, lookupBindingOpt, payload) {
|
|
|
172
187
|
}
|
|
173
188
|
} else {
|
|
174
189
|
let isMulti = Stdlib_Option.getOr(payload.multi, false);
|
|
175
|
-
let match$
|
|
176
|
-
switch (match$
|
|
190
|
+
let match$8 = payload.kind;
|
|
191
|
+
switch (match$8) {
|
|
177
192
|
case "getById" :
|
|
178
193
|
return null;
|
|
179
194
|
case "items" :
|
|
@@ -205,7 +205,7 @@ let make: ReventlessCore.QueryDb_Adapter.resolversMaker<api, role> = (
|
|
|
205
205
|
// with the SDL emitted by GraphQL_FragmentGenerator at runtime.
|
|
206
206
|
let stateSchemaOpt = ReventlessCore.Plugin_Helpers.stateSchemaRegistry->Dict.get(name)
|
|
207
207
|
let capability = switch stateSchemaOpt {
|
|
208
|
-
| Some(s) => ReventlessCore.GraphQL_FragmentGenerator.deriveServerCapability(s)
|
|
208
|
+
| Some(s) => ReventlessCore.GraphQL_FragmentGenerator.deriveServerCapability(~entityName=name, s)
|
|
209
209
|
| None => ReventlessCore.GraphQL_FragmentGenerator.emptyCapability
|
|
210
210
|
}
|
|
211
211
|
let filterFieldNames = capability.filterFields->Array.map(f => f.name)
|
|
@@ -91,7 +91,7 @@ function make(name, api, apiRole, dataSourceName, indexes, subIdField, idResolve
|
|
|
91
91
|
let resolverByIdMultiple = includeIdParam ? Stdlib_Option.map(subIdField, sortField => makeQueryResolver(Stdlib_String.capitalize(fieldNameForSingle) + "Items", fieldNameForSingle + "Items", AppSync_Resolver_Functions$PulumiAws.queryItemsWithSortConditions(sortField))) : undefined;
|
|
92
92
|
let labelField = registryEntry !== undefined ? Stdlib_Option.getOr(registryEntry.labelField, "id") : "id";
|
|
93
93
|
let stateSchemaOpt = Plugin_Helpers$ReventlessCore.stateSchemaRegistry[name$1];
|
|
94
|
-
let capability = stateSchemaOpt !== undefined ? GraphQL_FragmentGenerator$ReventlessCore.deriveServerCapability(stateSchemaOpt) : GraphQL_FragmentGenerator$ReventlessCore.emptyCapability;
|
|
94
|
+
let capability = stateSchemaOpt !== undefined ? GraphQL_FragmentGenerator$ReventlessCore.deriveServerCapability(name$1, stateSchemaOpt) : GraphQL_FragmentGenerator$ReventlessCore.emptyCapability;
|
|
95
95
|
let filterFieldNames = capability.filterFields.map(f => f.name);
|
|
96
96
|
let rangeFieldNames = Stdlib_Array.filterMap(capability.filterFields, f => {
|
|
97
97
|
if (f.range) {
|
|
@@ -69,7 +69,7 @@ function registerBinding(pushdowns, pgConnection, entry, spec) {
|
|
|
69
69
|
pushdowns: pushdowns,
|
|
70
70
|
indexes: indexes,
|
|
71
71
|
subIdField: subIdField,
|
|
72
|
-
capability: GraphQL_FragmentGenerator$ReventlessCore.deriveServerCapability(spec.stateSchema),
|
|
72
|
+
capability: GraphQL_FragmentGenerator$ReventlessCore.deriveServerCapability(entry.readModelName, spec.stateSchema),
|
|
73
73
|
labelField: entry.labelField,
|
|
74
74
|
includeIdParam: entry.includeIdParam,
|
|
75
75
|
authorization: spec.authorization
|
|
@@ -365,3 +365,93 @@ describe("PgQueryResolver_Lambda.dispatch", () => {
|
|
|
365
365
|
expect(threw)->toBe(true)
|
|
366
366
|
})
|
|
367
367
|
})
|
|
368
|
+
|
|
369
|
+
// A client holding a row reads its `id` and passes it back. On the local platform
|
|
370
|
+
// that `id` is a Relay global id; the typed doors used to take only the storage
|
|
371
|
+
// key and answered null, with no error to point at. These pin the either-form
|
|
372
|
+
// rule at the door, so the same client call works whichever form it holds.
|
|
373
|
+
describe("id form — the typed doors take either", () => {
|
|
374
|
+
let globalIdFor = localId => ReventlessCore.Api_Ids.encode(~typeName="Thing", ~localId)
|
|
375
|
+
|
|
376
|
+
testPromise("getById resolves a Relay global id", async () => {
|
|
377
|
+
let r = await PgQueryResolver_Lambda.dispatch(
|
|
378
|
+
~binding=makeBinding(),
|
|
379
|
+
~payload=mkPayload(
|
|
380
|
+
~kind="getById",
|
|
381
|
+
~args=objArgs([("id", JSON.Encode.string(globalIdFor("p-2")))]),
|
|
382
|
+
(),
|
|
383
|
+
),
|
|
384
|
+
)
|
|
385
|
+
expect(r->str("name"))->toEqual(Some("Bravo"))
|
|
386
|
+
})
|
|
387
|
+
|
|
388
|
+
// The row's `id` must come back as the storage key it was found under — echoing
|
|
389
|
+
// the argument would hand back an id nothing else accepts.
|
|
390
|
+
testPromise("getById reports the storage key it resolved to", async () => {
|
|
391
|
+
let r = await PgQueryResolver_Lambda.dispatch(
|
|
392
|
+
~binding=makeBinding(),
|
|
393
|
+
~payload=mkPayload(
|
|
394
|
+
~kind="getById",
|
|
395
|
+
~args=objArgs([("id", JSON.Encode.string(globalIdFor("p-2")))]),
|
|
396
|
+
(),
|
|
397
|
+
),
|
|
398
|
+
)
|
|
399
|
+
expect(r->str("id"))->toEqual(Some("p-2"))
|
|
400
|
+
})
|
|
401
|
+
|
|
402
|
+
testPromise("getById still resolves a plain storage key", async () => {
|
|
403
|
+
let r = await PgQueryResolver_Lambda.dispatch(
|
|
404
|
+
~binding=makeBinding(),
|
|
405
|
+
~payload=mkPayload(~kind="getById", ~args=objArgs([("id", JSON.Encode.string("p-2"))]), ()),
|
|
406
|
+
)
|
|
407
|
+
expect(r->str("name"))->toEqual(Some("Bravo"))
|
|
408
|
+
})
|
|
409
|
+
|
|
410
|
+
testPromise("getById on a global id for a row that does not exist is still null", async () => {
|
|
411
|
+
let r = await PgQueryResolver_Lambda.dispatch(
|
|
412
|
+
~binding=makeBinding(),
|
|
413
|
+
~payload=mkPayload(
|
|
414
|
+
~kind="getById",
|
|
415
|
+
~args=objArgs([("id", JSON.Encode.string(globalIdFor("absent")))]),
|
|
416
|
+
(),
|
|
417
|
+
),
|
|
418
|
+
)
|
|
419
|
+
expect(r)->toBe(JSON.Encode.null)
|
|
420
|
+
})
|
|
421
|
+
|
|
422
|
+
testPromise("byIds accepts the two forms mixed in one call", async () => {
|
|
423
|
+
let r = await PgQueryResolver_Lambda.dispatch(
|
|
424
|
+
~binding=makeBinding(),
|
|
425
|
+
~payload=mkPayload(
|
|
426
|
+
~kind="byIds",
|
|
427
|
+
~args=objArgs([
|
|
428
|
+
(
|
|
429
|
+
"ids",
|
|
430
|
+
JSON.Encode.array([
|
|
431
|
+
JSON.Encode.string("p-1"),
|
|
432
|
+
JSON.Encode.string(globalIdFor("p-3")),
|
|
433
|
+
]),
|
|
434
|
+
),
|
|
435
|
+
]),
|
|
436
|
+
(),
|
|
437
|
+
),
|
|
438
|
+
)
|
|
439
|
+
expect(r->ids)->toEqual(["p-1", "p-3"])
|
|
440
|
+
})
|
|
441
|
+
|
|
442
|
+
// The fallback lookup must not fire when the first pass already answered — an
|
|
443
|
+
// all-raw call is the common path and pays for one round trip, not two.
|
|
444
|
+
testPromise("byIds with every id found does not double-count", async () => {
|
|
445
|
+
let r = await PgQueryResolver_Lambda.dispatch(
|
|
446
|
+
~binding=makeBinding(),
|
|
447
|
+
~payload=mkPayload(
|
|
448
|
+
~kind="byIds",
|
|
449
|
+
~args=objArgs([
|
|
450
|
+
("ids", JSON.Encode.array(["p-1", "p-3"]->Array.map(JSON.Encode.string))),
|
|
451
|
+
]),
|
|
452
|
+
(),
|
|
453
|
+
),
|
|
454
|
+
)
|
|
455
|
+
expect(r->ids)->toEqual(["p-1", "p-3"])
|
|
456
|
+
})
|
|
457
|
+
})
|
|
@@ -6,6 +6,7 @@ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
|
6
6
|
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
7
7
|
import * as Primitive_string from "@rescript/runtime/lib/es6/Primitive_string.js";
|
|
8
8
|
import * as Identity$Reventless from "@reventlessdev/reventless-spec/src/types/Identity.res.mjs";
|
|
9
|
+
import * as Api_Ids$ReventlessCore from "@reventlessdev/reventless-core/src/components/Api/Api_Ids.res.mjs";
|
|
9
10
|
import * as QueryDb_Callback$ReventlessCore from "@reventlessdev/reventless-core/src/components/QueryDb/QueryDb_Callback.res.mjs";
|
|
10
11
|
import * as PgQueryResolver_Lambda$ReventlessAws from "../src/adapter/QueryDb/PgQueryResolver_Lambda.res.mjs";
|
|
11
12
|
|
|
@@ -452,6 +453,63 @@ globalThis.describe("PgQueryResolver_Lambda.dispatch", () => {
|
|
|
452
453
|
});
|
|
453
454
|
});
|
|
454
455
|
|
|
456
|
+
globalThis.describe("id form — the typed doors take either", () => {
|
|
457
|
+
globalThis.test("getById resolves a Relay global id", async () => {
|
|
458
|
+
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined), undefined, mkPayload("getById", undefined, Object.fromEntries([[
|
|
459
|
+
"id",
|
|
460
|
+
Api_Ids$ReventlessCore.encode("Thing", "p-2")
|
|
461
|
+
]]), undefined));
|
|
462
|
+
globalThis.expect(str(r, "name")).toEqual("Bravo");
|
|
463
|
+
});
|
|
464
|
+
globalThis.test("getById reports the storage key it resolved to", async () => {
|
|
465
|
+
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined), undefined, mkPayload("getById", undefined, Object.fromEntries([[
|
|
466
|
+
"id",
|
|
467
|
+
Api_Ids$ReventlessCore.encode("Thing", "p-2")
|
|
468
|
+
]]), undefined));
|
|
469
|
+
globalThis.expect(str(r, "id")).toEqual("p-2");
|
|
470
|
+
});
|
|
471
|
+
globalThis.test("getById still resolves a plain storage key", async () => {
|
|
472
|
+
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined), undefined, mkPayload("getById", undefined, Object.fromEntries([[
|
|
473
|
+
"id",
|
|
474
|
+
"p-2"
|
|
475
|
+
]]), undefined));
|
|
476
|
+
globalThis.expect(str(r, "name")).toEqual("Bravo");
|
|
477
|
+
});
|
|
478
|
+
globalThis.test("getById on a global id for a row that does not exist is still null", async () => {
|
|
479
|
+
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined), undefined, mkPayload("getById", undefined, Object.fromEntries([[
|
|
480
|
+
"id",
|
|
481
|
+
Api_Ids$ReventlessCore.encode("Thing", "absent")
|
|
482
|
+
]]), undefined));
|
|
483
|
+
globalThis.expect(r).toBe(null);
|
|
484
|
+
});
|
|
485
|
+
globalThis.test("byIds accepts the two forms mixed in one call", async () => {
|
|
486
|
+
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined), undefined, mkPayload("byIds", undefined, Object.fromEntries([[
|
|
487
|
+
"ids",
|
|
488
|
+
[
|
|
489
|
+
"p-1",
|
|
490
|
+
Api_Ids$ReventlessCore.encode("Thing", "p-3")
|
|
491
|
+
]
|
|
492
|
+
]]), undefined));
|
|
493
|
+
globalThis.expect(ids(r)).toEqual([
|
|
494
|
+
"p-1",
|
|
495
|
+
"p-3"
|
|
496
|
+
]);
|
|
497
|
+
});
|
|
498
|
+
globalThis.test("byIds with every id found does not double-count", async () => {
|
|
499
|
+
let r = await PgQueryResolver_Lambda$ReventlessAws.dispatch(makeBinding(undefined, undefined, undefined), undefined, mkPayload("byIds", undefined, Object.fromEntries([[
|
|
500
|
+
"ids",
|
|
501
|
+
[
|
|
502
|
+
"p-1",
|
|
503
|
+
"p-3"
|
|
504
|
+
].map(prim => prim)
|
|
505
|
+
]]), undefined));
|
|
506
|
+
globalThis.expect(ids(r)).toEqual([
|
|
507
|
+
"p-1",
|
|
508
|
+
"p-3"
|
|
509
|
+
]);
|
|
510
|
+
});
|
|
511
|
+
});
|
|
512
|
+
|
|
455
513
|
export {
|
|
456
514
|
mk,
|
|
457
515
|
store,
|