graphddb 0.7.6 → 0.7.8
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/dist/cdc/index.d.ts +2 -2
- package/dist/cdc/index.js +2 -2
- package/dist/{chunk-F2DI3GTI.js → chunk-HFFIB77D.js} +4 -1
- package/dist/{chunk-5NXNEW43.js → chunk-IA6MW2HP.js} +1 -1
- package/dist/{chunk-QOA5MSMD.js → chunk-NZRCBEWS.js} +841 -327
- package/dist/cli.js +2 -2
- package/dist/{from-change-bwwGGQDB.d.ts → from-change-w2Ih8fkm.d.ts} +1 -1
- package/dist/{index-CFySSIg4.d.ts → index-Deugy2sa.d.ts} +10 -5
- package/dist/index.d.ts +28 -9
- package/dist/index.js +15 -14
- package/dist/linter/index.d.ts +4 -4
- package/dist/{maintenance-view-adapter-B4oU6udW.d.ts → maintenance-view-adapter-BCbgKG5d.d.ts} +207 -41
- package/dist/{registry-uYUbg3If.d.ts → registry-pAnFcc62.d.ts} +1 -1
- package/dist/{relation-depth-JFdnwU7k.d.ts → relation-depth-C9t4s9bt.d.ts} +1 -1
- package/dist/spec/index.d.ts +3 -3
- package/dist/spec/index.js +2 -2
- package/dist/testing/index.d.ts +1 -1
- package/dist/testing/index.js +2 -2
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
BATCH_GET_MAX_KEYS,
|
|
3
|
+
CONDITION_OPERATOR_KEYS,
|
|
3
4
|
ChangeCaptureRegistry,
|
|
4
5
|
ClientManager,
|
|
5
6
|
MAX_TRANSACT_ITEMS,
|
|
@@ -21,6 +22,7 @@ import {
|
|
|
21
22
|
chunkArray,
|
|
22
23
|
commitTransaction,
|
|
23
24
|
compileRawCondition,
|
|
25
|
+
condParamName,
|
|
24
26
|
ctxModelFor,
|
|
25
27
|
detectRelationFields,
|
|
26
28
|
execItemKeySignature,
|
|
@@ -49,7 +51,7 @@ import {
|
|
|
49
51
|
serializeRawCondition,
|
|
50
52
|
skTemplate,
|
|
51
53
|
validateInlineSnapshotSelect
|
|
52
|
-
} from "./chunk-
|
|
54
|
+
} from "./chunk-HFFIB77D.js";
|
|
53
55
|
import {
|
|
54
56
|
TableMapping,
|
|
55
57
|
createColumnMap,
|
|
@@ -201,6 +203,246 @@ function buildManifest(registry = MetadataRegistry) {
|
|
|
201
203
|
};
|
|
202
204
|
}
|
|
203
205
|
|
|
206
|
+
// src/spec/guard.ts
|
|
207
|
+
function conditionInputToSpec(condition, context, renderLeaf2, renderTreeLeaf, renderRawSlot) {
|
|
208
|
+
if (condition === void 0 || condition === null) return void 0;
|
|
209
|
+
if (isRawCondition(condition)) {
|
|
210
|
+
const { expression, names, values } = serializeRawCondition(
|
|
211
|
+
condition,
|
|
212
|
+
renderRawSlot
|
|
213
|
+
);
|
|
214
|
+
return { kind: "raw", expression, names, values };
|
|
215
|
+
}
|
|
216
|
+
if (typeof condition !== "object") {
|
|
217
|
+
throw new Error(
|
|
218
|
+
`${context}: write condition must be an object (\`{ notExists: true }\`, \`{ attributeExists: '<field>' }\`, \`{ attributeNotExists: '<field>' }\`, or a declarative field condition).`
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
const obj = condition;
|
|
222
|
+
if (obj.notExists === true) return { kind: "notExists" };
|
|
223
|
+
if ("attributeExists" in obj && typeof obj.attributeExists === "string") {
|
|
224
|
+
if (obj.attributeExists.length === 0) {
|
|
225
|
+
throw new Error(
|
|
226
|
+
`${context}: \`attributeExists\` must name a non-empty attribute field.`
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
return { kind: "attributeExists", field: obj.attributeExists };
|
|
230
|
+
}
|
|
231
|
+
if ("attributeNotExists" in obj) {
|
|
232
|
+
if (typeof obj.attributeNotExists !== "string" || obj.attributeNotExists.length === 0) {
|
|
233
|
+
throw new Error(
|
|
234
|
+
`${context}: \`attributeNotExists\` must name a non-empty attribute field.`
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
return { kind: "attributeNotExists", field: obj.attributeNotExists };
|
|
238
|
+
}
|
|
239
|
+
if (usesOperatorTree(obj)) {
|
|
240
|
+
const leaf = renderTreeLeaf ?? ((value, name) => defaultTreeLeaf(value, name, context));
|
|
241
|
+
return { kind: "expr", declarative: buildTree(obj, context, leaf) };
|
|
242
|
+
}
|
|
243
|
+
const fields = {};
|
|
244
|
+
for (const key of Object.keys(obj).sort()) {
|
|
245
|
+
fields[key] = renderLeaf2(key, obj[key]);
|
|
246
|
+
}
|
|
247
|
+
return { kind: "equals", fields };
|
|
248
|
+
}
|
|
249
|
+
function assertNotNestedRaw(sub, context) {
|
|
250
|
+
if (isRawCondition(sub)) {
|
|
251
|
+
throw new Error(
|
|
252
|
+
`${context}: a raw \`cond\` write condition may be used as a whole condition, but not nested inside an \`and\` / \`or\` / \`not\` group of a serializable (public-contract / Python-bridge) command. Move the \`cond\` to the top level, or express the group declaratively.`
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
var LOGICAL_KEYS = /* @__PURE__ */ new Set(["and", "or", "not"]);
|
|
257
|
+
var OPERATOR_KEYS = /* @__PURE__ */ new Set([
|
|
258
|
+
"eq",
|
|
259
|
+
"ne",
|
|
260
|
+
"gt",
|
|
261
|
+
"ge",
|
|
262
|
+
"lt",
|
|
263
|
+
"le",
|
|
264
|
+
"between",
|
|
265
|
+
"in",
|
|
266
|
+
"beginsWith",
|
|
267
|
+
"contains",
|
|
268
|
+
"notContains",
|
|
269
|
+
"attributeExists",
|
|
270
|
+
"attributeType",
|
|
271
|
+
"size"
|
|
272
|
+
]);
|
|
273
|
+
function isOperatorObject(value) {
|
|
274
|
+
if (typeof value !== "object" || value === null || Array.isArray(value) || value instanceof Date) {
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
const keys = Object.keys(value);
|
|
278
|
+
if (keys.length === 0) return false;
|
|
279
|
+
return keys.every((k) => OPERATOR_KEYS.has(k));
|
|
280
|
+
}
|
|
281
|
+
function usesOperatorTree(obj) {
|
|
282
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
283
|
+
if (LOGICAL_KEYS.has(key)) return true;
|
|
284
|
+
if (isOperatorObject(value)) return true;
|
|
285
|
+
}
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
function defaultTreeLeaf(value, name, context) {
|
|
289
|
+
void name;
|
|
290
|
+
if (value instanceof Date) return value.toISOString();
|
|
291
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
292
|
+
return value;
|
|
293
|
+
}
|
|
294
|
+
throw new Error(
|
|
295
|
+
`${context}: condition operand must be a concrete literal or a param; got ${typeof value}.`
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
function conditionParamName(field, op, index) {
|
|
299
|
+
if (op === "between") return `${field}_${index === 0 ? "lo" : "hi"}`;
|
|
300
|
+
if (op === "in") return `${field}_${index ?? 0}`;
|
|
301
|
+
if (op === "size") return `${field}_size`;
|
|
302
|
+
return field;
|
|
303
|
+
}
|
|
304
|
+
function buildTree(obj, context, renderTreeLeaf) {
|
|
305
|
+
const out = {};
|
|
306
|
+
for (const key of Object.keys(obj).sort()) {
|
|
307
|
+
const value = obj[key];
|
|
308
|
+
if (value === void 0) continue;
|
|
309
|
+
if (key === "and" || key === "or") {
|
|
310
|
+
out[key] = value.map((sub) => {
|
|
311
|
+
assertNotNestedRaw(sub, context);
|
|
312
|
+
return buildTree(sub, context, renderTreeLeaf);
|
|
313
|
+
});
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
if (key === "not") {
|
|
317
|
+
assertNotNestedRaw(value, context);
|
|
318
|
+
out[key] = buildTree(
|
|
319
|
+
value,
|
|
320
|
+
context,
|
|
321
|
+
renderTreeLeaf
|
|
322
|
+
);
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
if (!isOperatorObject(value)) {
|
|
326
|
+
out[key] = { eq: renderTreeLeaf(value, conditionParamName(key, "eq")) };
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
const ops = value;
|
|
330
|
+
const rendered = {};
|
|
331
|
+
for (const op of Object.keys(ops).sort()) {
|
|
332
|
+
const opVal = ops[op];
|
|
333
|
+
if (op === "between") {
|
|
334
|
+
const [lo, hi] = opVal;
|
|
335
|
+
rendered[op] = [
|
|
336
|
+
renderTreeLeaf(lo, conditionParamName(key, op, 0)),
|
|
337
|
+
renderTreeLeaf(hi, conditionParamName(key, op, 1))
|
|
338
|
+
];
|
|
339
|
+
} else if (op === "in") {
|
|
340
|
+
rendered[op] = opVal.map(
|
|
341
|
+
(v, i) => renderTreeLeaf(v, conditionParamName(key, op, i))
|
|
342
|
+
);
|
|
343
|
+
} else if (op === "attributeExists") {
|
|
344
|
+
rendered[op] = opVal;
|
|
345
|
+
} else if (op === "attributeType") {
|
|
346
|
+
rendered[op] = String(opVal);
|
|
347
|
+
} else {
|
|
348
|
+
rendered[op] = renderTreeLeaf(opVal, conditionParamName(key, op));
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
out[key] = rendered;
|
|
352
|
+
}
|
|
353
|
+
return out;
|
|
354
|
+
}
|
|
355
|
+
function assertSupportedCondition(commandName, condition) {
|
|
356
|
+
if (condition.kind === "notExists") return;
|
|
357
|
+
if (condition.kind === "attributeExists" || condition.kind === "attributeNotExists") {
|
|
358
|
+
if (typeof condition.field !== "string" || condition.field.length === 0) {
|
|
359
|
+
throw new Error(
|
|
360
|
+
`Command '${commandName}' has an ${condition.kind} write condition with no attribute field. The Python bridge requires a non-empty field name.`
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
if (condition.kind === "equals") {
|
|
366
|
+
const entries = Object.entries(condition.fields);
|
|
367
|
+
if (entries.length === 0) {
|
|
368
|
+
throw new Error(
|
|
369
|
+
`Command '${commandName}' has an empty equality write condition. The Python bridge supports '{ notExists }' or non-empty field equality conditions only.`
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
for (const [field, value] of entries) {
|
|
373
|
+
if (typeof value !== "string") {
|
|
374
|
+
throw new Error(
|
|
375
|
+
`Command '${commandName}' write condition on '${field}' must be a template string value; the Python bridge supports equality on param/literal values only.`
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
if (condition.kind === "raw") {
|
|
382
|
+
if (typeof condition.expression !== "string" || condition.expression.length === 0) {
|
|
383
|
+
throw new Error(
|
|
384
|
+
`Command '${commandName}' has an empty raw \`cond\` write condition.`
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
if (condition.kind === "expr") {
|
|
390
|
+
const tree = condition.declarative;
|
|
391
|
+
if (!tree || Object.keys(tree).length === 0) {
|
|
392
|
+
throw new Error(
|
|
393
|
+
`Command '${commandName}' has an empty declarative write condition.`
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
throw new Error(
|
|
399
|
+
`Command '${commandName}' has an unsupported write condition.`
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
function assertJsonSerializable(value, path = "$") {
|
|
403
|
+
if (value === null) return;
|
|
404
|
+
switch (typeof value) {
|
|
405
|
+
case "string":
|
|
406
|
+
case "boolean":
|
|
407
|
+
return;
|
|
408
|
+
case "number":
|
|
409
|
+
if (!Number.isFinite(value)) {
|
|
410
|
+
throw new Error(
|
|
411
|
+
`Bridge spec is not JSON-serializable: non-finite number at ${path}.`
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
return;
|
|
415
|
+
case "object":
|
|
416
|
+
break;
|
|
417
|
+
default:
|
|
418
|
+
throw new Error(
|
|
419
|
+
`Bridge spec is not JSON-serializable: '${typeof value}' at ${path}.`
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
if (Array.isArray(value)) {
|
|
423
|
+
value.forEach((v, i) => assertJsonSerializable(v, `${path}[${i}]`));
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
const proto = Object.getPrototypeOf(value);
|
|
427
|
+
if (proto !== Object.prototype && proto !== null) {
|
|
428
|
+
throw new Error(
|
|
429
|
+
`Bridge spec is not JSON-serializable: non-plain object (${value.constructor?.name ?? "unknown"}) at ${path}.`
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
for (const [k, v] of Object.entries(value)) {
|
|
433
|
+
if (v === void 0) {
|
|
434
|
+
throw new Error(
|
|
435
|
+
`Bridge spec is not JSON-serializable: 'undefined' value at ${path}.${k}.`
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
assertJsonSerializable(v, `${path}.${k}`);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
function assertBundleSerializable(bundle) {
|
|
442
|
+
assertJsonSerializable(bundle.manifest, "$.manifest");
|
|
443
|
+
assertJsonSerializable(bundle.operations, "$.operations");
|
|
444
|
+
}
|
|
445
|
+
|
|
204
446
|
// src/planner/projection.ts
|
|
205
447
|
function buildProjection(select, additionalFields) {
|
|
206
448
|
const paths = [];
|
|
@@ -244,8 +486,8 @@ function buildProjection(select, additionalFields) {
|
|
|
244
486
|
}
|
|
245
487
|
|
|
246
488
|
// src/expression/filter-expression.ts
|
|
247
|
-
var
|
|
248
|
-
var
|
|
489
|
+
var LOGICAL_KEYS2 = /* @__PURE__ */ new Set(["and", "or", "not"]);
|
|
490
|
+
var OPERATOR_KEYS2 = /* @__PURE__ */ new Set([
|
|
249
491
|
"eq",
|
|
250
492
|
"ne",
|
|
251
493
|
"gt",
|
|
@@ -277,7 +519,7 @@ function valueAlias(ctx, field, raw) {
|
|
|
277
519
|
}
|
|
278
520
|
function compileField(ctx, field, condition) {
|
|
279
521
|
const nAlias = nameAlias(ctx, field);
|
|
280
|
-
if (!
|
|
522
|
+
if (!isOperatorObject2(condition)) {
|
|
281
523
|
const vAlias = valueAlias(ctx, field, condition);
|
|
282
524
|
return `${nAlias} = ${vAlias}`;
|
|
283
525
|
}
|
|
@@ -346,13 +588,13 @@ function compileField(ctx, field, condition) {
|
|
|
346
588
|
}
|
|
347
589
|
return joinAnd(clauses);
|
|
348
590
|
}
|
|
349
|
-
function
|
|
591
|
+
function isOperatorObject2(value) {
|
|
350
592
|
if (typeof value !== "object" || value === null || Array.isArray(value) || value instanceof Date || value instanceof Uint8Array || value instanceof Set) {
|
|
351
593
|
return false;
|
|
352
594
|
}
|
|
353
595
|
const keys = Object.keys(value);
|
|
354
596
|
if (keys.length === 0) return false;
|
|
355
|
-
return keys.every((k) =>
|
|
597
|
+
return keys.every((k) => OPERATOR_KEYS2.has(k));
|
|
356
598
|
}
|
|
357
599
|
function joinAnd(clauses) {
|
|
358
600
|
if (clauses.length === 1) return clauses[0];
|
|
@@ -395,7 +637,7 @@ function compileNode(ctx, node) {
|
|
|
395
637
|
const clauses = [];
|
|
396
638
|
for (const [key, value] of Object.entries(node)) {
|
|
397
639
|
if (value === void 0) continue;
|
|
398
|
-
if (
|
|
640
|
+
if (LOGICAL_KEYS2.has(key)) {
|
|
399
641
|
if (key === "and" || key === "or") {
|
|
400
642
|
const parts = value.map((sub) => compileNode(ctx, sub)).filter((s) => s.length > 0);
|
|
401
643
|
if (parts.length === 0) continue;
|
|
@@ -476,7 +718,7 @@ function eq(a, b) {
|
|
|
476
718
|
return a === b;
|
|
477
719
|
}
|
|
478
720
|
function evaluateFieldCondition(actual, item, field, condition) {
|
|
479
|
-
if (!
|
|
721
|
+
if (!isOperatorObject2(condition)) {
|
|
480
722
|
return eq(actual, condition);
|
|
481
723
|
}
|
|
482
724
|
const ops = condition;
|
|
@@ -2597,18 +2839,66 @@ function renderInputLeaf(leaf, isKeyFieldInPut, consumerIndex, consumerField, re
|
|
|
2597
2839
|
}
|
|
2598
2840
|
return leaf;
|
|
2599
2841
|
}
|
|
2600
|
-
function
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
const
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2842
|
+
function isConditionOperatorObject(value) {
|
|
2843
|
+
if (typeof value !== "object" || value === null || Array.isArray(value) || value instanceof Date || isMutationInputRef(value)) {
|
|
2844
|
+
return false;
|
|
2845
|
+
}
|
|
2846
|
+
const keys = Object.keys(value);
|
|
2847
|
+
if (keys.length === 0) return false;
|
|
2848
|
+
return keys.every((k) => CONDITION_OPERATOR_KEYS.has(k));
|
|
2849
|
+
}
|
|
2850
|
+
function lowerConditionLeaf(leaf) {
|
|
2851
|
+
if (isMutationInputRef(leaf)) return mintContractParamRef(leaf.field);
|
|
2852
|
+
return leaf;
|
|
2853
|
+
}
|
|
2854
|
+
function lowerFragmentCondition(condition) {
|
|
2855
|
+
const obj = condition;
|
|
2856
|
+
const out = {};
|
|
2857
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
2858
|
+
if (value === void 0) continue;
|
|
2859
|
+
if (key === "and" || key === "or") {
|
|
2860
|
+
out[key] = value.map((sub) => lowerFragmentCondition(sub));
|
|
2861
|
+
continue;
|
|
2862
|
+
}
|
|
2863
|
+
if (key === "not") {
|
|
2864
|
+
out[key] = lowerFragmentCondition(value);
|
|
2865
|
+
continue;
|
|
2866
|
+
}
|
|
2867
|
+
if (!isConditionOperatorObject(value)) {
|
|
2868
|
+
out[key] = lowerConditionLeaf(value);
|
|
2869
|
+
continue;
|
|
2870
|
+
}
|
|
2871
|
+
const ops = value;
|
|
2872
|
+
const rendered = {};
|
|
2873
|
+
for (const [op, opVal] of Object.entries(ops)) {
|
|
2874
|
+
if (op === "between") {
|
|
2875
|
+
const [lo, hi] = opVal;
|
|
2876
|
+
rendered[op] = [lowerConditionLeaf(lo), lowerConditionLeaf(hi)];
|
|
2877
|
+
} else if (op === "in") {
|
|
2878
|
+
rendered[op] = opVal.map((v) => lowerConditionLeaf(v));
|
|
2879
|
+
} else if (op === "attributeExists" || op === "attributeType") {
|
|
2880
|
+
rendered[op] = opVal;
|
|
2881
|
+
} else {
|
|
2882
|
+
rendered[op] = lowerConditionLeaf(opVal);
|
|
2883
|
+
}
|
|
2884
|
+
}
|
|
2885
|
+
out[key] = rendered;
|
|
2886
|
+
}
|
|
2887
|
+
return out;
|
|
2888
|
+
}
|
|
2889
|
+
function compileFragment(fragment, index = 0, resolveEntityRef = null, maintenanceGraph) {
|
|
2890
|
+
const keyFields = primaryKeyFields(fragment);
|
|
2891
|
+
const keyFieldSet = new Set(keyFields);
|
|
2892
|
+
const operation = INTENT_OPERATION[fragment.intent];
|
|
2893
|
+
const lifecycle = resolveLifecycle(fragment);
|
|
2894
|
+
for (const field of keyFields) {
|
|
2895
|
+
if (!(field in fragment.input)) {
|
|
2896
|
+
throw new Error(
|
|
2897
|
+
`mutation: the '${fragment.intent}' fragment on '${fragment.entity.name}' must bind the primary-key field '${field}' in its \`input\` (it identifies the row), e.g. \`{ ${field}: $.${field}, \u2026 }\`.`
|
|
2898
|
+
);
|
|
2899
|
+
}
|
|
2611
2900
|
}
|
|
2901
|
+
const userCondition = fragment.condition !== void 0 ? lowerFragmentCondition(fragment.condition) : void 0;
|
|
2612
2902
|
let op;
|
|
2613
2903
|
if (fragment.intent === "create") {
|
|
2614
2904
|
const item = {};
|
|
@@ -2621,6 +2911,7 @@ function compileFragment(fragment, index = 0, resolveEntityRef = null, maintenan
|
|
|
2621
2911
|
resolveEntityRef
|
|
2622
2912
|
);
|
|
2623
2913
|
}
|
|
2914
|
+
const condition = userCondition !== void 0 ? { and: [{ PK: { attributeExists: false } }, userCondition] } : { notExists: true };
|
|
2624
2915
|
op = {
|
|
2625
2916
|
__isContractMethodOp: true,
|
|
2626
2917
|
entity: fragment.entity,
|
|
@@ -2628,9 +2919,7 @@ function compileFragment(fragment, index = 0, resolveEntityRef = null, maintenan
|
|
|
2628
2919
|
keys: wholeKeysSentinel(),
|
|
2629
2920
|
// a `put` derives identity from the item.
|
|
2630
2921
|
item,
|
|
2631
|
-
|
|
2632
|
-
// the proposal specifies (create → Put `attribute_not_exists(PK)`).
|
|
2633
|
-
condition: { notExists: true }
|
|
2922
|
+
condition
|
|
2634
2923
|
};
|
|
2635
2924
|
} else {
|
|
2636
2925
|
const changes = {};
|
|
@@ -2644,7 +2933,11 @@ function compileFragment(fragment, index = 0, resolveEntityRef = null, maintenan
|
|
|
2644
2933
|
operation,
|
|
2645
2934
|
keys: wholeKeysSentinel(),
|
|
2646
2935
|
keyFields,
|
|
2647
|
-
...fragment.intent === "update" ? { changes } : {}
|
|
2936
|
+
...fragment.intent === "update" ? { changes } : {},
|
|
2937
|
+
// #242: a conditioned update / remove — the write is a CAS-style gate (the
|
|
2938
|
+
// row must satisfy the condition or the write is rejected, not silently
|
|
2939
|
+
// committed). Absent → an unconditional update / remove (unchanged).
|
|
2940
|
+
...userCondition !== void 0 ? { condition: userCondition } : {}
|
|
2648
2941
|
};
|
|
2649
2942
|
}
|
|
2650
2943
|
const conditionChecks = lifecycle?.effects.requires !== void 0 && lifecycle.effects.requires.length > 0 ? deriveConditionChecks(fragment, lifecycle.effects.requires) : void 0;
|
|
@@ -2832,13 +3125,62 @@ function renderCondition(condition, key, params, context) {
|
|
|
2832
3125
|
return { attributeNotExists: obj.attributeNotExists };
|
|
2833
3126
|
}
|
|
2834
3127
|
}
|
|
2835
|
-
return
|
|
3128
|
+
return renderConditionTree(
|
|
2836
3129
|
condition,
|
|
2837
3130
|
key,
|
|
2838
3131
|
params,
|
|
2839
3132
|
`${context} condition`
|
|
2840
3133
|
);
|
|
2841
3134
|
}
|
|
3135
|
+
function isConditionOperatorObject2(value) {
|
|
3136
|
+
if (typeof value !== "object" || value === null || Array.isArray(value) || value instanceof Date || isContractParamRef(value) || isContractKeyFieldRef(value)) {
|
|
3137
|
+
return false;
|
|
3138
|
+
}
|
|
3139
|
+
const keys = Object.keys(value);
|
|
3140
|
+
if (keys.length === 0) return false;
|
|
3141
|
+
return keys.every((k) => CONDITION_OPERATOR_KEYS.has(k));
|
|
3142
|
+
}
|
|
3143
|
+
function renderConditionTree(node, key, params, context) {
|
|
3144
|
+
const out = {};
|
|
3145
|
+
for (const [k, value] of Object.entries(node)) {
|
|
3146
|
+
if (value === void 0) continue;
|
|
3147
|
+
if (k === "and" || k === "or") {
|
|
3148
|
+
out[k] = value.map(
|
|
3149
|
+
(sub) => renderConditionTree(sub, key, params, `${context} ${k}`)
|
|
3150
|
+
);
|
|
3151
|
+
continue;
|
|
3152
|
+
}
|
|
3153
|
+
if (k === "not") {
|
|
3154
|
+
out[k] = renderConditionTree(value, key, params, `${context} not`);
|
|
3155
|
+
continue;
|
|
3156
|
+
}
|
|
3157
|
+
if (!isConditionOperatorObject2(value)) {
|
|
3158
|
+
out[k] = renderLeaf(value, key, params, `${context} field '${k}'`);
|
|
3159
|
+
continue;
|
|
3160
|
+
}
|
|
3161
|
+
const ops = value;
|
|
3162
|
+
const rendered = {};
|
|
3163
|
+
for (const [op, opVal] of Object.entries(ops)) {
|
|
3164
|
+
if (op === "between") {
|
|
3165
|
+
const [lo, hi] = opVal;
|
|
3166
|
+
rendered[op] = [
|
|
3167
|
+
renderLeaf(lo, key, params, `${context} field '${k}' between[0]`),
|
|
3168
|
+
renderLeaf(hi, key, params, `${context} field '${k}' between[1]`)
|
|
3169
|
+
];
|
|
3170
|
+
} else if (op === "in") {
|
|
3171
|
+
rendered[op] = opVal.map(
|
|
3172
|
+
(v, i) => renderLeaf(v, key, params, `${context} field '${k}' in[${i}]`)
|
|
3173
|
+
);
|
|
3174
|
+
} else if (op === "attributeExists" || op === "attributeType") {
|
|
3175
|
+
rendered[op] = opVal;
|
|
3176
|
+
} else {
|
|
3177
|
+
rendered[op] = renderLeaf(opVal, key, params, `${context} field '${k}' ${op}`);
|
|
3178
|
+
}
|
|
3179
|
+
}
|
|
3180
|
+
out[k] = rendered;
|
|
3181
|
+
}
|
|
3182
|
+
return out;
|
|
3183
|
+
}
|
|
2842
3184
|
function renderWrite(op, key, params, contextLabel) {
|
|
2843
3185
|
if (!isContractKeyRef(op.keys) && op.operation !== "put") {
|
|
2844
3186
|
throw new Error(
|
|
@@ -2924,6 +3266,7 @@ function renderConditionCheck(check, key, params, contextLabel) {
|
|
|
2924
3266
|
}
|
|
2925
3267
|
var RUNTIME_TOKEN_RE = /\{([^{}]+)\}/g;
|
|
2926
3268
|
function renderTemplate(template, key, params, contextLabel) {
|
|
3269
|
+
if (typeof template !== "string") return template;
|
|
2927
3270
|
const resolveToken = (token) => {
|
|
2928
3271
|
if (token in params) return params[token];
|
|
2929
3272
|
if (token in key) return key[token];
|
|
@@ -4579,6 +4922,19 @@ var DDBModel = class {
|
|
|
4579
4922
|
static setClient(client) {
|
|
4580
4923
|
ClientManager.setClient(client);
|
|
4581
4924
|
}
|
|
4925
|
+
/**
|
|
4926
|
+
* Read back the registered `DynamoDBClient` — the resolved client last passed to
|
|
4927
|
+
* {@link setClient}. Symmetric with {@link setClient} and backed by the same
|
|
4928
|
+
* {@link ClientManager} store, so it always reflects the client GraphDDB sends
|
|
4929
|
+
* through.
|
|
4930
|
+
*
|
|
4931
|
+
* Throws a clear error if no client has been registered yet (call
|
|
4932
|
+
* {@link setClient} first) — matching {@link ClientManager.getClient}'s "loud,
|
|
4933
|
+
* never silently `undefined`" convention shared with the rest of the client API.
|
|
4934
|
+
*/
|
|
4935
|
+
static getClient() {
|
|
4936
|
+
return ClientManager.getClient();
|
|
4937
|
+
}
|
|
4582
4938
|
static setTableMapping(mapping) {
|
|
4583
4939
|
TableMapping.set(mapping);
|
|
4584
4940
|
}
|
|
@@ -4932,23 +5288,107 @@ function assertFaithfulInput(input, intent, entityName) {
|
|
|
4932
5288
|
}
|
|
4933
5289
|
return input;
|
|
4934
5290
|
}
|
|
5291
|
+
function isConditionOperatorObject3(value) {
|
|
5292
|
+
if (typeof value !== "object" || value === null || Array.isArray(value) || value instanceof Date || isMutationInputRef(value)) {
|
|
5293
|
+
return false;
|
|
5294
|
+
}
|
|
5295
|
+
const keys = Object.keys(value);
|
|
5296
|
+
if (keys.length === 0) return false;
|
|
5297
|
+
return keys.every((k) => CONDITION_OPERATOR_KEYS.has(k));
|
|
5298
|
+
}
|
|
5299
|
+
function assertFaithfulConditionLeaf(leaf, intent, entityName, where) {
|
|
5300
|
+
if (isMutationInputRef(leaf) || isConcreteLiteral(leaf)) return;
|
|
5301
|
+
if (isAliasFieldRef(leaf)) {
|
|
5302
|
+
throw new Error(
|
|
5303
|
+
`mutation: the '${intent}' fragment on '${entityName}' uses a \`$.alias.field\` cross-fragment reference in its \`condition\` (${where}). A write condition gates a single row against the command INPUT (or the row's own persisted attributes) \u2014 a \`TransactWriteItems\` cannot read another item's value mid-transaction to gate this write. Use a \`$.field\` input reference or a literal.`
|
|
5304
|
+
);
|
|
5305
|
+
}
|
|
5306
|
+
throw new Error(
|
|
5307
|
+
`mutation: the '${intent}' fragment on '${entityName}' binds a \`condition\` operand (${where}) to a value that is neither a \`$.field\` input reference nor a concrete literal. A write condition is declarative \u2014 only \`$.field\` references or literals are allowed, never a computed value.`
|
|
5308
|
+
);
|
|
5309
|
+
}
|
|
5310
|
+
function assertFaithfulCondition(condition, intent, entityName) {
|
|
5311
|
+
if (condition === null || typeof condition !== "object" || Array.isArray(condition)) {
|
|
5312
|
+
throw new Error(
|
|
5313
|
+
`mutation: the '${intent}' fragment on '${entityName}' has a non-object \`condition\`. A write condition is a declarative field-clause / logical-group object (e.g. \`{ status: 'PENDING', version: { in: [$.v] } }\` or \`{ and: [ \u2026 ] }\`).`
|
|
5314
|
+
);
|
|
5315
|
+
}
|
|
5316
|
+
const obj = condition;
|
|
5317
|
+
if (Object.keys(obj).length === 0) {
|
|
5318
|
+
throw new Error(
|
|
5319
|
+
`mutation: the '${intent}' fragment on '${entityName}' declared an EMPTY \`condition\` object. Omit \`condition\` entirely for an unconditional write, or declare a real gate.`
|
|
5320
|
+
);
|
|
5321
|
+
}
|
|
5322
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
5323
|
+
if (value === void 0) continue;
|
|
5324
|
+
if (key === "and" || key === "or") {
|
|
5325
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
5326
|
+
throw new Error(
|
|
5327
|
+
`mutation: the '${intent}' fragment on '${entityName}' \`condition\` \`${key}\` must be a non-empty array of sub-conditions.`
|
|
5328
|
+
);
|
|
5329
|
+
}
|
|
5330
|
+
for (const sub of value) assertFaithfulCondition(sub, intent, entityName);
|
|
5331
|
+
continue;
|
|
5332
|
+
}
|
|
5333
|
+
if (key === "not") {
|
|
5334
|
+
assertFaithfulCondition(value, intent, entityName);
|
|
5335
|
+
continue;
|
|
5336
|
+
}
|
|
5337
|
+
if (!isConditionOperatorObject3(value)) {
|
|
5338
|
+
assertFaithfulConditionLeaf(value, intent, entityName, `field '${key}'`);
|
|
5339
|
+
continue;
|
|
5340
|
+
}
|
|
5341
|
+
const ops = value;
|
|
5342
|
+
for (const [op, opVal] of Object.entries(ops)) {
|
|
5343
|
+
if (op === "between") {
|
|
5344
|
+
if (!Array.isArray(opVal) || opVal.length !== 2) {
|
|
5345
|
+
throw new Error(
|
|
5346
|
+
`mutation: the '${intent}' fragment on '${entityName}' \`condition\` field '${key}' \`between\` takes a \`[lo, hi]\` pair.`
|
|
5347
|
+
);
|
|
5348
|
+
}
|
|
5349
|
+
assertFaithfulConditionLeaf(opVal[0], intent, entityName, `field '${key}' between[0]`);
|
|
5350
|
+
assertFaithfulConditionLeaf(opVal[1], intent, entityName, `field '${key}' between[1]`);
|
|
5351
|
+
} else if (op === "in") {
|
|
5352
|
+
if (!Array.isArray(opVal) || opVal.length === 0) {
|
|
5353
|
+
throw new Error(
|
|
5354
|
+
`mutation: the '${intent}' fragment on '${entityName}' \`condition\` field '${key}' \`in\` takes a non-empty array of operands.`
|
|
5355
|
+
);
|
|
5356
|
+
}
|
|
5357
|
+
opVal.forEach(
|
|
5358
|
+
(v, i) => assertFaithfulConditionLeaf(v, intent, entityName, `field '${key}' in[${i}]`)
|
|
5359
|
+
);
|
|
5360
|
+
} else if (op === "attributeExists" || op === "attributeType") {
|
|
5361
|
+
if (isMutationInputRef(opVal)) {
|
|
5362
|
+
throw new Error(
|
|
5363
|
+
`mutation: the '${intent}' fragment on '${entityName}' \`condition\` field '${key}' \`${op}\` was given a \`$.field\` reference. \`${op}\` takes a ${op === "attributeExists" ? "concrete boolean" : "concrete DynamoDB type-code string"} (a structural operand, not a value-bound param). Use a literal.`
|
|
5364
|
+
);
|
|
5365
|
+
}
|
|
5366
|
+
} else {
|
|
5367
|
+
assertFaithfulConditionLeaf(opVal, intent, entityName, `field '${key}' ${op}`);
|
|
5368
|
+
}
|
|
5369
|
+
}
|
|
5370
|
+
}
|
|
5371
|
+
return condition;
|
|
5372
|
+
}
|
|
4935
5373
|
function isConcreteLiteral(value) {
|
|
4936
5374
|
const t = typeof value;
|
|
4937
5375
|
return value === null || value instanceof Date || t === "string" || t === "number" || t === "boolean";
|
|
4938
5376
|
}
|
|
4939
|
-
function makeFragment(intent, entity, input, use) {
|
|
5377
|
+
function makeFragment(intent, entity, input, use, condition) {
|
|
4940
5378
|
const checked = assertFaithfulInput(input ?? {}, intent, entity.name);
|
|
4941
5379
|
if (use !== void 0 && !isEntityWritesDefinition(use)) {
|
|
4942
5380
|
throw new Error(
|
|
4943
5381
|
`mutation: the '${intent}' fragment on '${entity.name}' was given a \`use:\` that is not an \`entityWrites(...)\` save contract. \`use:\` takes the WHOLE \`writes\` set (an \`entityWrites(...)\` value), never a single lifecycle (e.g. \`Model.writes.create\`) \u2014 the fragment's intent already selects the lifecycle.`
|
|
4944
5382
|
);
|
|
4945
5383
|
}
|
|
5384
|
+
const checkedCondition = condition !== void 0 ? assertFaithfulCondition(condition, intent, entity.name) : void 0;
|
|
4946
5385
|
return {
|
|
4947
5386
|
[FRAGMENT_BRAND]: true,
|
|
4948
5387
|
intent,
|
|
4949
5388
|
entity,
|
|
4950
5389
|
input: checked,
|
|
4951
|
-
...use !== void 0 ? { use } : {}
|
|
5390
|
+
...use !== void 0 ? { use } : {},
|
|
5391
|
+
...checkedCondition !== void 0 ? { condition: checkedCondition } : {}
|
|
4952
5392
|
};
|
|
4953
5393
|
}
|
|
4954
5394
|
var COMMAND_PLAN_BRAND = /* @__PURE__ */ Symbol("graphddb:commandPlan");
|
|
@@ -5042,7 +5482,7 @@ function mutation(nameOrBody, maybeBody) {
|
|
|
5042
5482
|
const { intent, entity } = resolveDescriptorTarget(descriptor, alias);
|
|
5043
5483
|
const merged = mergeBindings(descriptor, intent, alias);
|
|
5044
5484
|
const resolved = resolveAliasRefs(merged, aliasToIndex, alias);
|
|
5045
|
-
return makeFragment(intent, entity, resolved, descriptor.use);
|
|
5485
|
+
return makeFragment(intent, entity, resolved, descriptor.use, descriptor.condition);
|
|
5046
5486
|
});
|
|
5047
5487
|
return { [COMMAND_PLAN_BRAND]: true, name, fragments };
|
|
5048
5488
|
}
|
|
@@ -5362,320 +5802,148 @@ function descriptorKeyRecord(record, name, what) {
|
|
|
5362
5802
|
}
|
|
5363
5803
|
return out;
|
|
5364
5804
|
}
|
|
5365
|
-
function
|
|
5366
|
-
|
|
5367
|
-
for (const [field, leaf] of Object.entries(record)) {
|
|
5368
|
-
if (isParam(leaf)) {
|
|
5369
|
-
out[field] = mintContractParamRef(field);
|
|
5370
|
-
} else {
|
|
5371
|
-
out[field] = leaf;
|
|
5372
|
-
}
|
|
5373
|
-
}
|
|
5374
|
-
return out;
|
|
5375
|
-
}
|
|
5376
|
-
function readDescriptorToOp(name, d) {
|
|
5377
|
-
const isQuery = d.query !== void 0;
|
|
5378
|
-
const isList = d.list !== void 0;
|
|
5379
|
-
if (isQuery === isList) {
|
|
5380
|
-
throw new Error(
|
|
5381
|
-
`publicQueryModel: method '${name}' must carry exactly one of \`query\` or \`list\` (the target model); it declared ${isQuery && isList ? "both" : "neither"}.`
|
|
5382
|
-
);
|
|
5383
|
-
}
|
|
5384
|
-
if (d.key === void 0 || d.select === void 0) {
|
|
5385
|
-
throw new Error(
|
|
5386
|
-
`publicQueryModel: the '${isQuery ? "query" : "list"}' descriptor for method '${name}' must declare both \`key\` and \`select\`.`
|
|
5387
|
-
);
|
|
5388
|
-
}
|
|
5389
|
-
const model = resolveDescriptorModel(isQuery ? d.query : d.list, name);
|
|
5390
|
-
const entity = entityRef(model);
|
|
5391
|
-
const operation = isQuery ? "query" : "list";
|
|
5392
|
-
const keyFieldNames2 = Object.keys(d.key);
|
|
5393
|
-
const keyRecord = descriptorKeyRecord(d.key, name, `${operation} key`);
|
|
5394
|
-
const { select, compose } = extractCompose({ ...d.select }, operation, entity.name);
|
|
5395
|
-
const keys = isQuery ? wholeKeysSentinel() : keyRecord;
|
|
5396
|
-
return {
|
|
5397
|
-
__isContractMethodOp: true,
|
|
5398
|
-
entity,
|
|
5399
|
-
operation,
|
|
5400
|
-
keys,
|
|
5401
|
-
...isQuery ? { keyFields: keyFieldNames2 } : {},
|
|
5402
|
-
...select !== void 0 ? { select } : {},
|
|
5403
|
-
...compose.length > 0 ? { compose } : {},
|
|
5404
|
-
...d.description !== void 0 ? { description: d.description } : {}
|
|
5405
|
-
};
|
|
5406
|
-
}
|
|
5407
|
-
function writeDescriptorToPlan(name, d) {
|
|
5408
|
-
const present = WRITE_INTENT_KEYS.filter((k) => d[k] !== void 0);
|
|
5409
|
-
if (present.length !== 1) {
|
|
5410
|
-
throw new Error(
|
|
5411
|
-
`publicCommandModel: method '${name}' must carry exactly one intent key (\`create\` / \`update\` / \`remove\`), but declared ${present.length === 0 ? "none" : present.join(", ")}.`
|
|
5412
|
-
);
|
|
5413
|
-
}
|
|
5414
|
-
const intent = present[0];
|
|
5415
|
-
if (d.key === void 0 || d.key === null || typeof d.key !== "object") {
|
|
5416
|
-
throw new Error(
|
|
5417
|
-
`publicCommandModel: the '${intent}' descriptor for method '${name}' must declare a \`key\` object binding the target's primary-key fields.`
|
|
5418
|
-
);
|
|
5419
|
-
}
|
|
5420
|
-
const model = resolveDescriptorModel(d[intent], name);
|
|
5421
|
-
const keyFieldNames2 = Object.keys(d.key);
|
|
5422
|
-
const inputFieldNames = d.input !== void 0 ? Object.keys(d.input) : [];
|
|
5423
|
-
const plan2 = mutation(name, ($) => {
|
|
5424
|
-
const refMap = (fields) => {
|
|
5425
|
-
const out = {};
|
|
5426
|
-
for (const f of fields) out[f] = $[f];
|
|
5427
|
-
return out;
|
|
5428
|
-
};
|
|
5429
|
-
const descriptor = {
|
|
5430
|
-
[intent]: (() => model),
|
|
5431
|
-
key: refMap(keyFieldNames2),
|
|
5432
|
-
...inputFieldNames.length > 0 ? { input: refMap(inputFieldNames) } : {}
|
|
5433
|
-
};
|
|
5434
|
-
return { [name]: descriptor };
|
|
5435
|
-
});
|
|
5436
|
-
const select = d.result !== void 0 && d.result.select !== void 0 ? d.result.select : void 0;
|
|
5437
|
-
const condition = d.condition !== void 0 ? descriptorConditionRecord(d.condition, name) : void 0;
|
|
5438
|
-
return { plan: plan2, select, condition, mode: d.mode ?? "transaction", description: d.description };
|
|
5439
|
-
}
|
|
5440
|
-
|
|
5441
|
-
// src/spec/guard.ts
|
|
5442
|
-
function conditionInputToSpec(condition, context, renderLeaf2, renderTreeLeaf, renderRawSlot) {
|
|
5443
|
-
if (condition === void 0 || condition === null) return void 0;
|
|
5444
|
-
if (isRawCondition(condition)) {
|
|
5445
|
-
const { expression, names, values } = serializeRawCondition(
|
|
5446
|
-
condition,
|
|
5447
|
-
renderRawSlot
|
|
5448
|
-
);
|
|
5449
|
-
return { kind: "raw", expression, names, values };
|
|
5450
|
-
}
|
|
5451
|
-
if (typeof condition !== "object") {
|
|
5452
|
-
throw new Error(
|
|
5453
|
-
`${context}: write condition must be an object (\`{ notExists: true }\`, \`{ attributeExists: '<field>' }\`, \`{ attributeNotExists: '<field>' }\`, or a declarative field condition).`
|
|
5454
|
-
);
|
|
5455
|
-
}
|
|
5456
|
-
const obj = condition;
|
|
5457
|
-
if (obj.notExists === true) return { kind: "notExists" };
|
|
5458
|
-
if ("attributeExists" in obj && typeof obj.attributeExists === "string") {
|
|
5459
|
-
if (obj.attributeExists.length === 0) {
|
|
5460
|
-
throw new Error(
|
|
5461
|
-
`${context}: \`attributeExists\` must name a non-empty attribute field.`
|
|
5462
|
-
);
|
|
5463
|
-
}
|
|
5464
|
-
return { kind: "attributeExists", field: obj.attributeExists };
|
|
5465
|
-
}
|
|
5466
|
-
if ("attributeNotExists" in obj) {
|
|
5467
|
-
if (typeof obj.attributeNotExists !== "string" || obj.attributeNotExists.length === 0) {
|
|
5468
|
-
throw new Error(
|
|
5469
|
-
`${context}: \`attributeNotExists\` must name a non-empty attribute field.`
|
|
5470
|
-
);
|
|
5471
|
-
}
|
|
5472
|
-
return { kind: "attributeNotExists", field: obj.attributeNotExists };
|
|
5473
|
-
}
|
|
5474
|
-
if (usesOperatorTree(obj)) {
|
|
5475
|
-
const leaf = renderTreeLeaf ?? ((value, name) => defaultTreeLeaf(value, name, context));
|
|
5476
|
-
return { kind: "expr", declarative: buildTree(obj, context, leaf) };
|
|
5477
|
-
}
|
|
5478
|
-
const fields = {};
|
|
5479
|
-
for (const key of Object.keys(obj).sort()) {
|
|
5480
|
-
fields[key] = renderLeaf2(key, obj[key]);
|
|
5481
|
-
}
|
|
5482
|
-
return { kind: "equals", fields };
|
|
5483
|
-
}
|
|
5484
|
-
function assertNotNestedRaw(sub, context) {
|
|
5485
|
-
if (isRawCondition(sub)) {
|
|
5486
|
-
throw new Error(
|
|
5487
|
-
`${context}: a raw \`cond\` write condition may be used as a whole condition, but not nested inside an \`and\` / \`or\` / \`not\` group of a serializable (public-contract / Python-bridge) command. Move the \`cond\` to the top level, or express the group declaratively.`
|
|
5488
|
-
);
|
|
5489
|
-
}
|
|
5490
|
-
}
|
|
5491
|
-
var LOGICAL_KEYS2 = /* @__PURE__ */ new Set(["and", "or", "not"]);
|
|
5492
|
-
var OPERATOR_KEYS2 = /* @__PURE__ */ new Set([
|
|
5493
|
-
"eq",
|
|
5494
|
-
"ne",
|
|
5495
|
-
"gt",
|
|
5496
|
-
"ge",
|
|
5497
|
-
"lt",
|
|
5498
|
-
"le",
|
|
5499
|
-
"between",
|
|
5500
|
-
"in",
|
|
5501
|
-
"beginsWith",
|
|
5502
|
-
"contains",
|
|
5503
|
-
"notContains",
|
|
5504
|
-
"attributeExists",
|
|
5505
|
-
"attributeType",
|
|
5506
|
-
"size"
|
|
5507
|
-
]);
|
|
5508
|
-
function isOperatorObject2(value) {
|
|
5509
|
-
if (typeof value !== "object" || value === null || Array.isArray(value) || value instanceof Date) {
|
|
5805
|
+
function isConditionOperatorObject4(value) {
|
|
5806
|
+
if (typeof value !== "object" || value === null || Array.isArray(value) || value instanceof Date || isParam(value) || isContractParamRef(value) || isContractKeyFieldRef(value)) {
|
|
5510
5807
|
return false;
|
|
5511
5808
|
}
|
|
5512
5809
|
const keys = Object.keys(value);
|
|
5513
5810
|
if (keys.length === 0) return false;
|
|
5514
|
-
return keys.every((k) =>
|
|
5811
|
+
return keys.every((k) => CONDITION_OPERATOR_KEYS.has(k));
|
|
5515
5812
|
}
|
|
5516
|
-
function
|
|
5517
|
-
|
|
5518
|
-
|
|
5519
|
-
if (isOperatorObject2(value)) return true;
|
|
5520
|
-
}
|
|
5521
|
-
return false;
|
|
5813
|
+
function mintConditionOperand(leaf, field, op, index) {
|
|
5814
|
+
if (isParam(leaf)) return mintContractParamRef(conditionParamName(field, op, index));
|
|
5815
|
+
return leaf;
|
|
5522
5816
|
}
|
|
5523
|
-
function
|
|
5524
|
-
|
|
5525
|
-
|
|
5526
|
-
|
|
5527
|
-
return
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
|
|
5531
|
-
);
|
|
5817
|
+
function mintRawConditionParams(raw) {
|
|
5818
|
+
let paramIndex = 0;
|
|
5819
|
+
const parts = raw.parts.map((part) => {
|
|
5820
|
+
if (isColumn(part)) return part;
|
|
5821
|
+
if (isParam(part)) return mintContractParamRef(condParamName(paramIndex++));
|
|
5822
|
+
return part;
|
|
5823
|
+
});
|
|
5824
|
+
return { ...raw, parts };
|
|
5532
5825
|
}
|
|
5533
|
-
function
|
|
5534
|
-
if (
|
|
5535
|
-
|
|
5536
|
-
|
|
5537
|
-
|
|
5826
|
+
function descriptorConditionRecord(record, name) {
|
|
5827
|
+
if (isRawCondition(record)) {
|
|
5828
|
+
return mintRawConditionParams(record);
|
|
5829
|
+
}
|
|
5830
|
+
const top = record;
|
|
5831
|
+
if (top.notExists === true || typeof top.attributeExists === "string" || typeof top.attributeNotExists === "string") {
|
|
5832
|
+
return { ...top };
|
|
5833
|
+
}
|
|
5834
|
+
return descriptorConditionTree(top, name, `publicCommandModel: method '${name}' condition`);
|
|
5538
5835
|
}
|
|
5539
|
-
function
|
|
5836
|
+
function descriptorConditionTree(obj, name, context) {
|
|
5540
5837
|
const out = {};
|
|
5541
|
-
for (const key of Object.
|
|
5542
|
-
const value = obj[key];
|
|
5838
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
5543
5839
|
if (value === void 0) continue;
|
|
5544
5840
|
if (key === "and" || key === "or") {
|
|
5545
5841
|
out[key] = value.map((sub) => {
|
|
5546
|
-
|
|
5547
|
-
return
|
|
5842
|
+
assertNoNestedRawCond(sub, name);
|
|
5843
|
+
return descriptorConditionTree(sub, name, `${context} ${key}`);
|
|
5548
5844
|
});
|
|
5549
5845
|
continue;
|
|
5550
5846
|
}
|
|
5551
5847
|
if (key === "not") {
|
|
5552
|
-
|
|
5553
|
-
out[key] =
|
|
5554
|
-
value,
|
|
5555
|
-
context,
|
|
5556
|
-
renderTreeLeaf
|
|
5557
|
-
);
|
|
5848
|
+
assertNoNestedRawCond(value, name);
|
|
5849
|
+
out[key] = descriptorConditionTree(value, name, `${context} not`);
|
|
5558
5850
|
continue;
|
|
5559
5851
|
}
|
|
5560
|
-
if (!
|
|
5561
|
-
out[key] =
|
|
5852
|
+
if (!isConditionOperatorObject4(value)) {
|
|
5853
|
+
out[key] = mintConditionOperand(value, key, "eq");
|
|
5562
5854
|
continue;
|
|
5563
5855
|
}
|
|
5564
5856
|
const ops = value;
|
|
5565
5857
|
const rendered = {};
|
|
5566
|
-
for (const op of Object.
|
|
5567
|
-
const opVal = ops[op];
|
|
5858
|
+
for (const [op, opVal] of Object.entries(ops)) {
|
|
5568
5859
|
if (op === "between") {
|
|
5569
5860
|
const [lo, hi] = opVal;
|
|
5570
5861
|
rendered[op] = [
|
|
5571
|
-
|
|
5572
|
-
|
|
5862
|
+
mintConditionOperand(lo, key, op, 0),
|
|
5863
|
+
mintConditionOperand(hi, key, op, 1)
|
|
5573
5864
|
];
|
|
5574
5865
|
} else if (op === "in") {
|
|
5575
|
-
rendered[op] = opVal.map(
|
|
5576
|
-
|
|
5577
|
-
);
|
|
5578
|
-
} else if (op === "attributeExists") {
|
|
5866
|
+
rendered[op] = opVal.map((v, i) => mintConditionOperand(v, key, op, i));
|
|
5867
|
+
} else if (op === "attributeExists" || op === "attributeType") {
|
|
5579
5868
|
rendered[op] = opVal;
|
|
5580
|
-
} else
|
|
5581
|
-
rendered[op] =
|
|
5582
|
-
} else {
|
|
5583
|
-
rendered[op] = renderTreeLeaf(opVal, conditionParamName(key, op));
|
|
5584
|
-
}
|
|
5585
|
-
}
|
|
5586
|
-
out[key] = rendered;
|
|
5587
|
-
}
|
|
5588
|
-
return out;
|
|
5589
|
-
}
|
|
5590
|
-
function assertSupportedCondition(commandName, condition) {
|
|
5591
|
-
if (condition.kind === "notExists") return;
|
|
5592
|
-
if (condition.kind === "attributeExists" || condition.kind === "attributeNotExists") {
|
|
5593
|
-
if (typeof condition.field !== "string" || condition.field.length === 0) {
|
|
5594
|
-
throw new Error(
|
|
5595
|
-
`Command '${commandName}' has an ${condition.kind} write condition with no attribute field. The Python bridge requires a non-empty field name.`
|
|
5596
|
-
);
|
|
5597
|
-
}
|
|
5598
|
-
return;
|
|
5599
|
-
}
|
|
5600
|
-
if (condition.kind === "equals") {
|
|
5601
|
-
const entries = Object.entries(condition.fields);
|
|
5602
|
-
if (entries.length === 0) {
|
|
5603
|
-
throw new Error(
|
|
5604
|
-
`Command '${commandName}' has an empty equality write condition. The Python bridge supports '{ notExists }' or non-empty field equality conditions only.`
|
|
5605
|
-
);
|
|
5606
|
-
}
|
|
5607
|
-
for (const [field, value] of entries) {
|
|
5608
|
-
if (typeof value !== "string") {
|
|
5609
|
-
throw new Error(
|
|
5610
|
-
`Command '${commandName}' write condition on '${field}' must be a template string value; the Python bridge supports equality on param/literal values only.`
|
|
5611
|
-
);
|
|
5869
|
+
} else {
|
|
5870
|
+
rendered[op] = mintConditionOperand(opVal, key, op);
|
|
5612
5871
|
}
|
|
5613
5872
|
}
|
|
5614
|
-
|
|
5615
|
-
}
|
|
5616
|
-
if (condition.kind === "raw") {
|
|
5617
|
-
if (typeof condition.expression !== "string" || condition.expression.length === 0) {
|
|
5618
|
-
throw new Error(
|
|
5619
|
-
`Command '${commandName}' has an empty raw \`cond\` write condition.`
|
|
5620
|
-
);
|
|
5621
|
-
}
|
|
5622
|
-
return;
|
|
5873
|
+
out[key] = rendered;
|
|
5623
5874
|
}
|
|
5624
|
-
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
|
|
5628
|
-
|
|
5629
|
-
|
|
5630
|
-
|
|
5631
|
-
return;
|
|
5875
|
+
return out;
|
|
5876
|
+
}
|
|
5877
|
+
function assertNoNestedRawCond(sub, name) {
|
|
5878
|
+
if (isRawCondition(sub)) {
|
|
5879
|
+
throw new Error(
|
|
5880
|
+
`publicCommandModel: method '${name}' condition nests a raw \`cond\` fragment inside an \`and\` / \`or\` / \`not\` group, which is not serializable. Use a \`cond\` as the whole condition, or express the group with declarative operators.`
|
|
5881
|
+
);
|
|
5632
5882
|
}
|
|
5633
|
-
throw new Error(
|
|
5634
|
-
`Command '${commandName}' has an unsupported write condition.`
|
|
5635
|
-
);
|
|
5636
5883
|
}
|
|
5637
|
-
function
|
|
5638
|
-
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
if (!Number.isFinite(value)) {
|
|
5645
|
-
throw new Error(
|
|
5646
|
-
`Bridge spec is not JSON-serializable: non-finite number at ${path}.`
|
|
5647
|
-
);
|
|
5648
|
-
}
|
|
5649
|
-
return;
|
|
5650
|
-
case "object":
|
|
5651
|
-
break;
|
|
5652
|
-
default:
|
|
5653
|
-
throw new Error(
|
|
5654
|
-
`Bridge spec is not JSON-serializable: '${typeof value}' at ${path}.`
|
|
5655
|
-
);
|
|
5884
|
+
function readDescriptorToOp(name, d) {
|
|
5885
|
+
const isQuery = d.query !== void 0;
|
|
5886
|
+
const isList = d.list !== void 0;
|
|
5887
|
+
if (isQuery === isList) {
|
|
5888
|
+
throw new Error(
|
|
5889
|
+
`publicQueryModel: method '${name}' must carry exactly one of \`query\` or \`list\` (the target model); it declared ${isQuery && isList ? "both" : "neither"}.`
|
|
5890
|
+
);
|
|
5656
5891
|
}
|
|
5657
|
-
if (
|
|
5658
|
-
|
|
5659
|
-
|
|
5892
|
+
if (d.key === void 0 || d.select === void 0) {
|
|
5893
|
+
throw new Error(
|
|
5894
|
+
`publicQueryModel: the '${isQuery ? "query" : "list"}' descriptor for method '${name}' must declare both \`key\` and \`select\`.`
|
|
5895
|
+
);
|
|
5660
5896
|
}
|
|
5661
|
-
const
|
|
5662
|
-
|
|
5897
|
+
const model = resolveDescriptorModel(isQuery ? d.query : d.list, name);
|
|
5898
|
+
const entity = entityRef(model);
|
|
5899
|
+
const operation = isQuery ? "query" : "list";
|
|
5900
|
+
const keyFieldNames2 = Object.keys(d.key);
|
|
5901
|
+
const keyRecord = descriptorKeyRecord(d.key, name, `${operation} key`);
|
|
5902
|
+
const { select, compose } = extractCompose({ ...d.select }, operation, entity.name);
|
|
5903
|
+
const keys = isQuery ? wholeKeysSentinel() : keyRecord;
|
|
5904
|
+
return {
|
|
5905
|
+
__isContractMethodOp: true,
|
|
5906
|
+
entity,
|
|
5907
|
+
operation,
|
|
5908
|
+
keys,
|
|
5909
|
+
...isQuery ? { keyFields: keyFieldNames2 } : {},
|
|
5910
|
+
...select !== void 0 ? { select } : {},
|
|
5911
|
+
...compose.length > 0 ? { compose } : {},
|
|
5912
|
+
...d.description !== void 0 ? { description: d.description } : {}
|
|
5913
|
+
};
|
|
5914
|
+
}
|
|
5915
|
+
function writeDescriptorToPlan(name, d) {
|
|
5916
|
+
const present = WRITE_INTENT_KEYS.filter((k) => d[k] !== void 0);
|
|
5917
|
+
if (present.length !== 1) {
|
|
5663
5918
|
throw new Error(
|
|
5664
|
-
`
|
|
5919
|
+
`publicCommandModel: method '${name}' must carry exactly one intent key (\`create\` / \`update\` / \`remove\`), but declared ${present.length === 0 ? "none" : present.join(", ")}.`
|
|
5665
5920
|
);
|
|
5666
5921
|
}
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
}
|
|
5673
|
-
assertJsonSerializable(v, `${path}.${k}`);
|
|
5922
|
+
const intent = present[0];
|
|
5923
|
+
if (d.key === void 0 || d.key === null || typeof d.key !== "object") {
|
|
5924
|
+
throw new Error(
|
|
5925
|
+
`publicCommandModel: the '${intent}' descriptor for method '${name}' must declare a \`key\` object binding the target's primary-key fields.`
|
|
5926
|
+
);
|
|
5674
5927
|
}
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
5928
|
+
const model = resolveDescriptorModel(d[intent], name);
|
|
5929
|
+
const keyFieldNames2 = Object.keys(d.key);
|
|
5930
|
+
const inputFieldNames = d.input !== void 0 ? Object.keys(d.input) : [];
|
|
5931
|
+
const plan2 = mutation(name, ($) => {
|
|
5932
|
+
const refMap = (fields) => {
|
|
5933
|
+
const out = {};
|
|
5934
|
+
for (const f of fields) out[f] = $[f];
|
|
5935
|
+
return out;
|
|
5936
|
+
};
|
|
5937
|
+
const descriptor = {
|
|
5938
|
+
[intent]: (() => model),
|
|
5939
|
+
key: refMap(keyFieldNames2),
|
|
5940
|
+
...inputFieldNames.length > 0 ? { input: refMap(inputFieldNames) } : {}
|
|
5941
|
+
};
|
|
5942
|
+
return { [name]: descriptor };
|
|
5943
|
+
});
|
|
5944
|
+
const select = d.result !== void 0 && d.result.select !== void 0 ? d.result.select : void 0;
|
|
5945
|
+
const condition = d.condition !== void 0 ? descriptorConditionRecord(d.condition, name) : void 0;
|
|
5946
|
+
return { plan: plan2, select, condition, mode: d.mode ?? "transaction", description: d.description };
|
|
5679
5947
|
}
|
|
5680
5948
|
|
|
5681
5949
|
// src/define/transaction.ts
|
|
@@ -6047,7 +6315,7 @@ function verifyWrite(instrs, surfaced) {
|
|
|
6047
6315
|
verifyRecord(instrs.map((x) => x.item), surfaced, `${op} item`);
|
|
6048
6316
|
verifyRecord(instrs.map((x) => x.key), surfaced, `${op} key`);
|
|
6049
6317
|
verifyRecord(instrs.map((x) => x.changes), surfaced, `${op} changes`);
|
|
6050
|
-
|
|
6318
|
+
verifyCondition(
|
|
6051
6319
|
instrs.map((x) => x.condition),
|
|
6052
6320
|
surfaced,
|
|
6053
6321
|
`${op} condition`
|
|
@@ -6079,6 +6347,148 @@ function verifyRecord(records, surfaced, context) {
|
|
|
6079
6347
|
);
|
|
6080
6348
|
}
|
|
6081
6349
|
}
|
|
6350
|
+
function isConditionOperatorObject5(value) {
|
|
6351
|
+
if (typeof value !== "object" || value === null || Array.isArray(value) || value instanceof Date || isTransactionRef(value)) {
|
|
6352
|
+
return false;
|
|
6353
|
+
}
|
|
6354
|
+
const keys = Object.keys(value);
|
|
6355
|
+
if (keys.length === 0) return false;
|
|
6356
|
+
return keys.every((k) => CONDITION_OPERATOR_KEYS.has(k));
|
|
6357
|
+
}
|
|
6358
|
+
function verifyCondition(conditions, surfaced, context) {
|
|
6359
|
+
const present = conditions.filter(
|
|
6360
|
+
(c) => c !== void 0 && c !== null
|
|
6361
|
+
);
|
|
6362
|
+
if (present.length === 0) return;
|
|
6363
|
+
if (present.length !== conditions.length) {
|
|
6364
|
+
throw new Error(
|
|
6365
|
+
`defineTransaction: ${context} present in some evaluation passes but not others; the callback must be a pure, declarative description.`
|
|
6366
|
+
);
|
|
6367
|
+
}
|
|
6368
|
+
if (present.some((c) => isRawCondition(c))) return;
|
|
6369
|
+
if (present.some((c) => typeof c !== "object" || Array.isArray(c))) {
|
|
6370
|
+
throw new Error(
|
|
6371
|
+
`defineTransaction: ${context} must be a declarative condition object (field clause / operator object / \`and\` / \`or\` / \`not\` group).`
|
|
6372
|
+
);
|
|
6373
|
+
}
|
|
6374
|
+
verifyConditionTree(
|
|
6375
|
+
present.map((c) => c),
|
|
6376
|
+
surfaced,
|
|
6377
|
+
context
|
|
6378
|
+
);
|
|
6379
|
+
}
|
|
6380
|
+
function verifyConditionTree(nodes, surfaced, context) {
|
|
6381
|
+
const keys = Object.keys(nodes[0]).sort();
|
|
6382
|
+
for (const n of nodes) {
|
|
6383
|
+
const k = Object.keys(n).sort();
|
|
6384
|
+
if (k.length !== keys.length || k.some((x, i) => x !== keys[i])) {
|
|
6385
|
+
throw new Error(
|
|
6386
|
+
`defineTransaction: ${context} key set diverged between evaluation passes; the callback must be a pure, declarative description.`
|
|
6387
|
+
);
|
|
6388
|
+
}
|
|
6389
|
+
}
|
|
6390
|
+
for (const key of keys) {
|
|
6391
|
+
const values = nodes.map((n) => n[key]);
|
|
6392
|
+
if (key === "and" || key === "or") {
|
|
6393
|
+
const arrays = values.map((v) => {
|
|
6394
|
+
if (!Array.isArray(v) || v.length === 0) {
|
|
6395
|
+
throw new Error(
|
|
6396
|
+
`defineTransaction: ${context} \`${key}\` must be a non-empty array of sub-conditions.`
|
|
6397
|
+
);
|
|
6398
|
+
}
|
|
6399
|
+
return v;
|
|
6400
|
+
});
|
|
6401
|
+
const len = arrays[0].length;
|
|
6402
|
+
if (arrays.some((a) => a.length !== len)) {
|
|
6403
|
+
throw new Error(
|
|
6404
|
+
`defineTransaction: ${context} \`${key}\` length diverged between evaluation passes; the callback must be declarative.`
|
|
6405
|
+
);
|
|
6406
|
+
}
|
|
6407
|
+
for (let i = 0; i < len; i++) {
|
|
6408
|
+
verifyCondition(
|
|
6409
|
+
arrays.map((a) => a[i]),
|
|
6410
|
+
surfaced,
|
|
6411
|
+
`${context} ${key}[${i}]`
|
|
6412
|
+
);
|
|
6413
|
+
}
|
|
6414
|
+
continue;
|
|
6415
|
+
}
|
|
6416
|
+
if (key === "not") {
|
|
6417
|
+
verifyCondition(
|
|
6418
|
+
values,
|
|
6419
|
+
surfaced,
|
|
6420
|
+
`${context} not`
|
|
6421
|
+
);
|
|
6422
|
+
continue;
|
|
6423
|
+
}
|
|
6424
|
+
if (!values.every((v) => isConditionOperatorObject5(v))) {
|
|
6425
|
+
if (values.some((v) => isConditionOperatorObject5(v))) {
|
|
6426
|
+
throw new Error(
|
|
6427
|
+
`defineTransaction: ${context} field '${key}' is an operator object in some evaluation passes but a bare value in others; the callback must be declarative.`
|
|
6428
|
+
);
|
|
6429
|
+
}
|
|
6430
|
+
verifyLeaf(values, surfaced, `${context} field '${key}'`);
|
|
6431
|
+
continue;
|
|
6432
|
+
}
|
|
6433
|
+
verifyOperatorObject(
|
|
6434
|
+
values.map((v) => v),
|
|
6435
|
+
surfaced,
|
|
6436
|
+
`${context} field '${key}'`
|
|
6437
|
+
);
|
|
6438
|
+
}
|
|
6439
|
+
}
|
|
6440
|
+
function verifyOperatorObject(objs, surfaced, context) {
|
|
6441
|
+
const ops = Object.keys(objs[0]).sort();
|
|
6442
|
+
for (const o of objs) {
|
|
6443
|
+
const k = Object.keys(o).sort();
|
|
6444
|
+
if (k.length !== ops.length || k.some((x, i) => x !== ops[i])) {
|
|
6445
|
+
throw new Error(
|
|
6446
|
+
`defineTransaction: ${context} operator set diverged between evaluation passes; the callback must be a pure, declarative description.`
|
|
6447
|
+
);
|
|
6448
|
+
}
|
|
6449
|
+
}
|
|
6450
|
+
for (const op of ops) {
|
|
6451
|
+
const opVals = objs.map((o) => o[op]);
|
|
6452
|
+
if (op === "between") {
|
|
6453
|
+
const pairs = opVals.map((v) => {
|
|
6454
|
+
if (!Array.isArray(v) || v.length !== 2) {
|
|
6455
|
+
throw new Error(
|
|
6456
|
+
`defineTransaction: ${context} \`between\` takes a \`[lo, hi]\` pair.`
|
|
6457
|
+
);
|
|
6458
|
+
}
|
|
6459
|
+
return v;
|
|
6460
|
+
});
|
|
6461
|
+
verifyLeaf(pairs.map((p) => p[0]), surfaced, `${context} between[0]`);
|
|
6462
|
+
verifyLeaf(pairs.map((p) => p[1]), surfaced, `${context} between[1]`);
|
|
6463
|
+
} else if (op === "in") {
|
|
6464
|
+
const arrays = opVals.map((v) => {
|
|
6465
|
+
if (!Array.isArray(v) || v.length === 0) {
|
|
6466
|
+
throw new Error(
|
|
6467
|
+
`defineTransaction: ${context} \`in\` takes a non-empty array of operands.`
|
|
6468
|
+
);
|
|
6469
|
+
}
|
|
6470
|
+
return v;
|
|
6471
|
+
});
|
|
6472
|
+
const len = arrays[0].length;
|
|
6473
|
+
if (arrays.some((a) => a.length !== len)) {
|
|
6474
|
+
throw new Error(
|
|
6475
|
+
`defineTransaction: ${context} \`in\` length diverged between evaluation passes; the callback must be declarative.`
|
|
6476
|
+
);
|
|
6477
|
+
}
|
|
6478
|
+
for (let i = 0; i < len; i++) {
|
|
6479
|
+
verifyLeaf(arrays.map((a) => a[i]), surfaced, `${context} in[${i}]`);
|
|
6480
|
+
}
|
|
6481
|
+
} else if (op === "attributeExists" || op === "attributeType") {
|
|
6482
|
+
if (opVals.some((v) => isTransactionRef(v))) {
|
|
6483
|
+
throw new Error(
|
|
6484
|
+
`defineTransaction: ${context} \`${op}\` was given a field reference. \`${op}\` takes a ${op === "attributeExists" ? "concrete boolean" : "concrete DynamoDB type-code string"} (a structural operand, not a value-bound param); use a literal.`
|
|
6485
|
+
);
|
|
6486
|
+
}
|
|
6487
|
+
} else {
|
|
6488
|
+
verifyLeaf(opVals, surfaced, `${context} ${op}`);
|
|
6489
|
+
}
|
|
6490
|
+
}
|
|
6491
|
+
}
|
|
6082
6492
|
function verifyWhen(whens, surfaced, context) {
|
|
6083
6493
|
const present = whens.filter((w) => w !== void 0);
|
|
6084
6494
|
if (present.length === 0) return;
|
|
@@ -6191,10 +6601,19 @@ function conditionTreeLeaf(value, name, context) {
|
|
|
6191
6601
|
`defineTransaction: ${context} condition operand must be a field reference (from \`p\` or a forEach element) or a concrete literal; got ${typeof value}.`
|
|
6192
6602
|
);
|
|
6193
6603
|
}
|
|
6194
|
-
function
|
|
6604
|
+
function valueLeaf(value, context) {
|
|
6605
|
+
if (isTransactionRef(value)) return value.token;
|
|
6606
|
+
if (value instanceof Date) return value.toISOString();
|
|
6607
|
+
if (typeof value === "number" || typeof value === "boolean") return value;
|
|
6608
|
+
if (typeof value === "string") return value;
|
|
6609
|
+
throw new Error(
|
|
6610
|
+
`defineTransaction: ${context} must be a field reference (from \`p\` or a forEach element) or a concrete literal; got ${typeof value}.`
|
|
6611
|
+
);
|
|
6612
|
+
}
|
|
6613
|
+
function valueRecord(structure, context) {
|
|
6195
6614
|
const out = {};
|
|
6196
6615
|
for (const field of Object.keys(structure).sort()) {
|
|
6197
|
-
out[field] =
|
|
6616
|
+
out[field] = valueLeaf(structure[field], `${context} field '${field}'`);
|
|
6198
6617
|
}
|
|
6199
6618
|
return out;
|
|
6200
6619
|
}
|
|
@@ -6267,7 +6686,7 @@ function buildWriteItem(instr, forEachSource, txName) {
|
|
|
6267
6686
|
if (instr.operation === "put") {
|
|
6268
6687
|
return {
|
|
6269
6688
|
...base,
|
|
6270
|
-
item:
|
|
6689
|
+
item: valueRecord(instr.item ?? {}, `${txName} put`)
|
|
6271
6690
|
};
|
|
6272
6691
|
}
|
|
6273
6692
|
if (instr.operation === "delete") {
|
|
@@ -6291,7 +6710,7 @@ function buildWriteItem(instr, forEachSource, txName) {
|
|
|
6291
6710
|
)
|
|
6292
6711
|
};
|
|
6293
6712
|
}
|
|
6294
|
-
const changes =
|
|
6713
|
+
const changes = valueRecord(instr.changes ?? {}, `${txName} update changes`);
|
|
6295
6714
|
injectTransactionGsiRederive(
|
|
6296
6715
|
metadata,
|
|
6297
6716
|
instr.key ?? {},
|
|
@@ -6480,8 +6899,8 @@ function recoverKind(metadata, fieldName) {
|
|
|
6480
6899
|
function opToDefinition(contractName, methodName, op) {
|
|
6481
6900
|
const metadata = MetadataRegistry.get(op.entity.modelClass);
|
|
6482
6901
|
const params = {};
|
|
6483
|
-
const place = (paramName, bindField) => {
|
|
6484
|
-
const recovered = recoverKind(metadata, bindField);
|
|
6902
|
+
const place = (paramName, bindField, kindOverride) => {
|
|
6903
|
+
const recovered = kindOverride !== void 0 ? { kind: kindOverride } : recoverKind(metadata, bindField);
|
|
6485
6904
|
params[paramName] = recovered.literals === void 0 ? { kind: recovered.kind, required: true } : { kind: recovered.kind, literals: recovered.literals, required: true };
|
|
6486
6905
|
return recovered;
|
|
6487
6906
|
};
|
|
@@ -6555,13 +6974,13 @@ function convertStructure(structure, place, context) {
|
|
|
6555
6974
|
}
|
|
6556
6975
|
return out;
|
|
6557
6976
|
}
|
|
6558
|
-
function convertLeaf(leaf, bindField, place, context) {
|
|
6977
|
+
function convertLeaf(leaf, bindField, place, context, kindOverride) {
|
|
6559
6978
|
if (isContractParamRef(leaf)) {
|
|
6560
|
-
place(leaf.field, bindField);
|
|
6979
|
+
place(leaf.field, bindField, kindOverride);
|
|
6561
6980
|
return leaf;
|
|
6562
6981
|
}
|
|
6563
6982
|
if (isContractKeyFieldRef(leaf)) {
|
|
6564
|
-
place(leaf.field, bindField);
|
|
6983
|
+
place(leaf.field, bindField, kindOverride);
|
|
6565
6984
|
return leaf;
|
|
6566
6985
|
}
|
|
6567
6986
|
if (isConcreteLiteral3(leaf)) return leaf;
|
|
@@ -6569,24 +6988,118 @@ function convertLeaf(leaf, bindField, place, context) {
|
|
|
6569
6988
|
`${context}: value is neither a param/key reference nor a concrete literal \u2014 it cannot be serialized as a declarative contract operation.`
|
|
6570
6989
|
);
|
|
6571
6990
|
}
|
|
6991
|
+
function nearestColumnFields(condition) {
|
|
6992
|
+
const parts = condition.parts;
|
|
6993
|
+
const columnAt = (i) => isColumn(parts[i]) ? parts[i].name : void 0;
|
|
6994
|
+
const out = [];
|
|
6995
|
+
for (let i = 0; i < parts.length; i++) {
|
|
6996
|
+
const part = parts[i];
|
|
6997
|
+
if (!isContractParamRef(part) && !isContractKeyFieldRef(part)) continue;
|
|
6998
|
+
let found;
|
|
6999
|
+
for (let d = 1; d < parts.length && found === void 0; d++) {
|
|
7000
|
+
found = columnAt(i - d) ?? columnAt(i + d);
|
|
7001
|
+
}
|
|
7002
|
+
out.push(found);
|
|
7003
|
+
}
|
|
7004
|
+
return out;
|
|
7005
|
+
}
|
|
6572
7006
|
function convertCondition(condition, place, context) {
|
|
6573
|
-
if (isRawCondition(condition))
|
|
7007
|
+
if (isRawCondition(condition)) {
|
|
7008
|
+
const compareFieldForSlot = nearestColumnFields(condition);
|
|
7009
|
+
let slotIndex = 0;
|
|
7010
|
+
for (const part of condition.parts) {
|
|
7011
|
+
if (isContractParamRef(part) || isContractKeyFieldRef(part)) {
|
|
7012
|
+
const compareField = compareFieldForSlot[slotIndex];
|
|
7013
|
+
if (compareField === void 0) {
|
|
7014
|
+
throw new Error(
|
|
7015
|
+
`${context}: a raw \`cond\` value slot ('${part.field}') has no adjacent column, so its param type cannot be inferred. A \`cond\` param slot must be compared against a \`Model.col.<field>\` reference (e.g. \`cond\`\${Model.col.x} < \${param.number()}\`\`) so the parameter inherits that column's type; refusing to emit an un-typed (silently 'string') param.`
|
|
7016
|
+
);
|
|
7017
|
+
}
|
|
7018
|
+
place(part.field, compareField);
|
|
7019
|
+
slotIndex++;
|
|
7020
|
+
}
|
|
7021
|
+
}
|
|
7022
|
+
return condition;
|
|
7023
|
+
}
|
|
6574
7024
|
if (typeof condition === "object" && condition !== null) {
|
|
6575
|
-
const
|
|
6576
|
-
if (
|
|
6577
|
-
if (typeof
|
|
6578
|
-
return { attributeExists:
|
|
7025
|
+
const obj2 = condition;
|
|
7026
|
+
if (obj2.notExists === true) return { notExists: true };
|
|
7027
|
+
if (typeof obj2.attributeExists === "string") {
|
|
7028
|
+
return { attributeExists: obj2.attributeExists };
|
|
6579
7029
|
}
|
|
6580
|
-
if (typeof
|
|
6581
|
-
return { attributeNotExists:
|
|
7030
|
+
if (typeof obj2.attributeNotExists === "string") {
|
|
7031
|
+
return { attributeNotExists: obj2.attributeNotExists };
|
|
6582
7032
|
}
|
|
6583
7033
|
}
|
|
7034
|
+
const obj = condition;
|
|
7035
|
+
if (usesConditionTree(obj)) {
|
|
7036
|
+
return convertConditionTree(obj, place, context);
|
|
7037
|
+
}
|
|
6584
7038
|
const out = {};
|
|
6585
|
-
for (const [field, leaf] of Object.entries(
|
|
7039
|
+
for (const [field, leaf] of Object.entries(obj)) {
|
|
6586
7040
|
out[field] = convertLeaf(leaf, field, place, `${context} field '${field}'`);
|
|
6587
7041
|
}
|
|
6588
7042
|
return out;
|
|
6589
7043
|
}
|
|
7044
|
+
var CONDITION_LOGICAL_KEYS = /* @__PURE__ */ new Set(["and", "or", "not"]);
|
|
7045
|
+
function isConditionOperatorObject6(value) {
|
|
7046
|
+
if (typeof value !== "object" || value === null || Array.isArray(value) || value instanceof Date || isContractParamRef(value) || isContractKeyFieldRef(value)) {
|
|
7047
|
+
return false;
|
|
7048
|
+
}
|
|
7049
|
+
const keys = Object.keys(value);
|
|
7050
|
+
if (keys.length === 0) return false;
|
|
7051
|
+
return keys.every((k) => CONDITION_OPERATOR_KEYS.has(k));
|
|
7052
|
+
}
|
|
7053
|
+
function usesConditionTree(obj) {
|
|
7054
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
7055
|
+
if (CONDITION_LOGICAL_KEYS.has(key)) return true;
|
|
7056
|
+
if (isConditionOperatorObject6(value)) return true;
|
|
7057
|
+
}
|
|
7058
|
+
return false;
|
|
7059
|
+
}
|
|
7060
|
+
function convertConditionTree(obj, place, context) {
|
|
7061
|
+
const out = {};
|
|
7062
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
7063
|
+
if (value === void 0) continue;
|
|
7064
|
+
if (key === "and" || key === "or") {
|
|
7065
|
+
out[key] = value.map(
|
|
7066
|
+
(sub) => convertConditionTree(sub, place, `${context} ${key}`)
|
|
7067
|
+
);
|
|
7068
|
+
continue;
|
|
7069
|
+
}
|
|
7070
|
+
if (key === "not") {
|
|
7071
|
+
out[key] = convertConditionTree(value, place, `${context} not`);
|
|
7072
|
+
continue;
|
|
7073
|
+
}
|
|
7074
|
+
if (!isConditionOperatorObject6(value)) {
|
|
7075
|
+
out[key] = convertLeaf(value, key, place, `${context} field '${key}'`);
|
|
7076
|
+
continue;
|
|
7077
|
+
}
|
|
7078
|
+
const ops = value;
|
|
7079
|
+
const rendered = {};
|
|
7080
|
+
for (const [op, opVal] of Object.entries(ops)) {
|
|
7081
|
+
if (op === "between") {
|
|
7082
|
+
const [lo, hi] = opVal;
|
|
7083
|
+
rendered[op] = [
|
|
7084
|
+
convertLeaf(lo, key, place, `${context} field '${key}' between[0]`),
|
|
7085
|
+
convertLeaf(hi, key, place, `${context} field '${key}' between[1]`)
|
|
7086
|
+
];
|
|
7087
|
+
} else if (op === "in") {
|
|
7088
|
+
rendered[op] = opVal.map(
|
|
7089
|
+
(v, i) => convertLeaf(v, key, place, `${context} field '${key}' in[${i}]`)
|
|
7090
|
+
);
|
|
7091
|
+
} else if (op === "attributeExists" || op === "attributeType") {
|
|
7092
|
+
rendered[op] = opVal;
|
|
7093
|
+
} else if (op === "size") {
|
|
7094
|
+
rendered[op] = convertLeaf(opVal, key, place, `${context} field '${key}' ${op}`, "number");
|
|
7095
|
+
} else {
|
|
7096
|
+
rendered[op] = convertLeaf(opVal, key, place, `${context} field '${key}' ${op}`);
|
|
7097
|
+
}
|
|
7098
|
+
}
|
|
7099
|
+
out[key] = rendered;
|
|
7100
|
+
}
|
|
7101
|
+
return out;
|
|
7102
|
+
}
|
|
6590
7103
|
function assertBooleanProjection(select, context) {
|
|
6591
7104
|
const out = {};
|
|
6592
7105
|
for (const [field, leaf] of Object.entries(select)) {
|
|
@@ -6897,6 +7410,7 @@ var TEMPLATE_TOKEN_RE = /\{([^{}]+)\}/g;
|
|
|
6897
7410
|
function collectTemplateParams(record, metadata, out) {
|
|
6898
7411
|
if (record === void 0) return;
|
|
6899
7412
|
for (const template of Object.values(record)) {
|
|
7413
|
+
if (typeof template !== "string") continue;
|
|
6900
7414
|
for (const match of template.matchAll(TEMPLATE_TOKEN_RE)) {
|
|
6901
7415
|
const token = match[1];
|
|
6902
7416
|
const field = token.startsWith("old.") ? token.slice("old.".length) : token;
|
|
@@ -7775,7 +8289,7 @@ function buildCommandSpec(def) {
|
|
|
7775
8289
|
const condition = extractCondition(def);
|
|
7776
8290
|
const description = def.description !== void 0 ? { description: def.description } : {};
|
|
7777
8291
|
if (def.operation === "put") {
|
|
7778
|
-
const item =
|
|
8292
|
+
const item = templateRecord2(def.key);
|
|
7779
8293
|
return {
|
|
7780
8294
|
type: "PutItem",
|
|
7781
8295
|
tableName,
|
|
@@ -7803,7 +8317,7 @@ function buildCommandSpec(def) {
|
|
|
7803
8317
|
entity,
|
|
7804
8318
|
params,
|
|
7805
8319
|
keyCondition: writeKeyCondition(metadata, def.key, entity),
|
|
7806
|
-
changes:
|
|
8320
|
+
changes: templateRecord2(def.changes),
|
|
7807
8321
|
...condition ? { condition } : {},
|
|
7808
8322
|
...description
|
|
7809
8323
|
};
|
|
@@ -7827,7 +8341,7 @@ function writeKeyCondition(metadata, key, entity) {
|
|
|
7827
8341
|
if (sk !== void 0) out.SK = sk;
|
|
7828
8342
|
return out;
|
|
7829
8343
|
}
|
|
7830
|
-
function
|
|
8344
|
+
function templateRecord2(structure) {
|
|
7831
8345
|
const out = {};
|
|
7832
8346
|
for (const key of Object.keys(structure).sort()) {
|
|
7833
8347
|
out[key] = templateLeaf(key, structure[key]);
|
|
@@ -8158,6 +8672,10 @@ export {
|
|
|
8158
8672
|
MARKER_ROW_ENTITY,
|
|
8159
8673
|
buildManifestEntity,
|
|
8160
8674
|
buildManifest,
|
|
8675
|
+
conditionParamName,
|
|
8676
|
+
assertSupportedCondition,
|
|
8677
|
+
assertJsonSerializable,
|
|
8678
|
+
assertBundleSerializable,
|
|
8161
8679
|
buildProjection,
|
|
8162
8680
|
compileFilterExpression,
|
|
8163
8681
|
evaluateFilter,
|
|
@@ -8225,10 +8743,6 @@ export {
|
|
|
8225
8743
|
publicQueryModel,
|
|
8226
8744
|
publicCommandModel,
|
|
8227
8745
|
isPlannedCommandMethod,
|
|
8228
|
-
conditionParamName,
|
|
8229
|
-
assertSupportedCondition,
|
|
8230
|
-
assertJsonSerializable,
|
|
8231
|
-
assertBundleSerializable,
|
|
8232
8746
|
isTransactionRef,
|
|
8233
8747
|
when,
|
|
8234
8748
|
defineTransaction,
|