graphddb 0.7.7 → 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-WPABX7WG.js → chunk-NZRCBEWS.js} +828 -327
- package/dist/cli.js +2 -2
- package/dist/{from-change-D2zFApzb.d.ts → from-change-w2Ih8fkm.d.ts} +1 -1
- package/dist/{index-BnTZQ_IG.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-C1HaBX4y.d.ts → maintenance-view-adapter-BCbgKG5d.d.ts} +196 -41
- package/dist/{registry-DWhq5wiA.d.ts → registry-pAnFcc62.d.ts} +1 -1
- package/dist/{relation-depth-BnqfKkZB.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];
|
|
@@ -4945,23 +5288,107 @@ function assertFaithfulInput(input, intent, entityName) {
|
|
|
4945
5288
|
}
|
|
4946
5289
|
return input;
|
|
4947
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
|
+
}
|
|
4948
5373
|
function isConcreteLiteral(value) {
|
|
4949
5374
|
const t = typeof value;
|
|
4950
5375
|
return value === null || value instanceof Date || t === "string" || t === "number" || t === "boolean";
|
|
4951
5376
|
}
|
|
4952
|
-
function makeFragment(intent, entity, input, use) {
|
|
5377
|
+
function makeFragment(intent, entity, input, use, condition) {
|
|
4953
5378
|
const checked = assertFaithfulInput(input ?? {}, intent, entity.name);
|
|
4954
5379
|
if (use !== void 0 && !isEntityWritesDefinition(use)) {
|
|
4955
5380
|
throw new Error(
|
|
4956
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.`
|
|
4957
5382
|
);
|
|
4958
5383
|
}
|
|
5384
|
+
const checkedCondition = condition !== void 0 ? assertFaithfulCondition(condition, intent, entity.name) : void 0;
|
|
4959
5385
|
return {
|
|
4960
5386
|
[FRAGMENT_BRAND]: true,
|
|
4961
5387
|
intent,
|
|
4962
5388
|
entity,
|
|
4963
5389
|
input: checked,
|
|
4964
|
-
...use !== void 0 ? { use } : {}
|
|
5390
|
+
...use !== void 0 ? { use } : {},
|
|
5391
|
+
...checkedCondition !== void 0 ? { condition: checkedCondition } : {}
|
|
4965
5392
|
};
|
|
4966
5393
|
}
|
|
4967
5394
|
var COMMAND_PLAN_BRAND = /* @__PURE__ */ Symbol("graphddb:commandPlan");
|
|
@@ -5055,7 +5482,7 @@ function mutation(nameOrBody, maybeBody) {
|
|
|
5055
5482
|
const { intent, entity } = resolveDescriptorTarget(descriptor, alias);
|
|
5056
5483
|
const merged = mergeBindings(descriptor, intent, alias);
|
|
5057
5484
|
const resolved = resolveAliasRefs(merged, aliasToIndex, alias);
|
|
5058
|
-
return makeFragment(intent, entity, resolved, descriptor.use);
|
|
5485
|
+
return makeFragment(intent, entity, resolved, descriptor.use, descriptor.condition);
|
|
5059
5486
|
});
|
|
5060
5487
|
return { [COMMAND_PLAN_BRAND]: true, name, fragments };
|
|
5061
5488
|
}
|
|
@@ -5375,320 +5802,148 @@ function descriptorKeyRecord(record, name, what) {
|
|
|
5375
5802
|
}
|
|
5376
5803
|
return out;
|
|
5377
5804
|
}
|
|
5378
|
-
function
|
|
5379
|
-
|
|
5380
|
-
for (const [field, leaf] of Object.entries(record)) {
|
|
5381
|
-
if (isParam(leaf)) {
|
|
5382
|
-
out[field] = mintContractParamRef(field);
|
|
5383
|
-
} else {
|
|
5384
|
-
out[field] = leaf;
|
|
5385
|
-
}
|
|
5386
|
-
}
|
|
5387
|
-
return out;
|
|
5388
|
-
}
|
|
5389
|
-
function readDescriptorToOp(name, d) {
|
|
5390
|
-
const isQuery = d.query !== void 0;
|
|
5391
|
-
const isList = d.list !== void 0;
|
|
5392
|
-
if (isQuery === isList) {
|
|
5393
|
-
throw new Error(
|
|
5394
|
-
`publicQueryModel: method '${name}' must carry exactly one of \`query\` or \`list\` (the target model); it declared ${isQuery && isList ? "both" : "neither"}.`
|
|
5395
|
-
);
|
|
5396
|
-
}
|
|
5397
|
-
if (d.key === void 0 || d.select === void 0) {
|
|
5398
|
-
throw new Error(
|
|
5399
|
-
`publicQueryModel: the '${isQuery ? "query" : "list"}' descriptor for method '${name}' must declare both \`key\` and \`select\`.`
|
|
5400
|
-
);
|
|
5401
|
-
}
|
|
5402
|
-
const model = resolveDescriptorModel(isQuery ? d.query : d.list, name);
|
|
5403
|
-
const entity = entityRef(model);
|
|
5404
|
-
const operation = isQuery ? "query" : "list";
|
|
5405
|
-
const keyFieldNames2 = Object.keys(d.key);
|
|
5406
|
-
const keyRecord = descriptorKeyRecord(d.key, name, `${operation} key`);
|
|
5407
|
-
const { select, compose } = extractCompose({ ...d.select }, operation, entity.name);
|
|
5408
|
-
const keys = isQuery ? wholeKeysSentinel() : keyRecord;
|
|
5409
|
-
return {
|
|
5410
|
-
__isContractMethodOp: true,
|
|
5411
|
-
entity,
|
|
5412
|
-
operation,
|
|
5413
|
-
keys,
|
|
5414
|
-
...isQuery ? { keyFields: keyFieldNames2 } : {},
|
|
5415
|
-
...select !== void 0 ? { select } : {},
|
|
5416
|
-
...compose.length > 0 ? { compose } : {},
|
|
5417
|
-
...d.description !== void 0 ? { description: d.description } : {}
|
|
5418
|
-
};
|
|
5419
|
-
}
|
|
5420
|
-
function writeDescriptorToPlan(name, d) {
|
|
5421
|
-
const present = WRITE_INTENT_KEYS.filter((k) => d[k] !== void 0);
|
|
5422
|
-
if (present.length !== 1) {
|
|
5423
|
-
throw new Error(
|
|
5424
|
-
`publicCommandModel: method '${name}' must carry exactly one intent key (\`create\` / \`update\` / \`remove\`), but declared ${present.length === 0 ? "none" : present.join(", ")}.`
|
|
5425
|
-
);
|
|
5426
|
-
}
|
|
5427
|
-
const intent = present[0];
|
|
5428
|
-
if (d.key === void 0 || d.key === null || typeof d.key !== "object") {
|
|
5429
|
-
throw new Error(
|
|
5430
|
-
`publicCommandModel: the '${intent}' descriptor for method '${name}' must declare a \`key\` object binding the target's primary-key fields.`
|
|
5431
|
-
);
|
|
5432
|
-
}
|
|
5433
|
-
const model = resolveDescriptorModel(d[intent], name);
|
|
5434
|
-
const keyFieldNames2 = Object.keys(d.key);
|
|
5435
|
-
const inputFieldNames = d.input !== void 0 ? Object.keys(d.input) : [];
|
|
5436
|
-
const plan2 = mutation(name, ($) => {
|
|
5437
|
-
const refMap = (fields) => {
|
|
5438
|
-
const out = {};
|
|
5439
|
-
for (const f of fields) out[f] = $[f];
|
|
5440
|
-
return out;
|
|
5441
|
-
};
|
|
5442
|
-
const descriptor = {
|
|
5443
|
-
[intent]: (() => model),
|
|
5444
|
-
key: refMap(keyFieldNames2),
|
|
5445
|
-
...inputFieldNames.length > 0 ? { input: refMap(inputFieldNames) } : {}
|
|
5446
|
-
};
|
|
5447
|
-
return { [name]: descriptor };
|
|
5448
|
-
});
|
|
5449
|
-
const select = d.result !== void 0 && d.result.select !== void 0 ? d.result.select : void 0;
|
|
5450
|
-
const condition = d.condition !== void 0 ? descriptorConditionRecord(d.condition, name) : void 0;
|
|
5451
|
-
return { plan: plan2, select, condition, mode: d.mode ?? "transaction", description: d.description };
|
|
5452
|
-
}
|
|
5453
|
-
|
|
5454
|
-
// src/spec/guard.ts
|
|
5455
|
-
function conditionInputToSpec(condition, context, renderLeaf2, renderTreeLeaf, renderRawSlot) {
|
|
5456
|
-
if (condition === void 0 || condition === null) return void 0;
|
|
5457
|
-
if (isRawCondition(condition)) {
|
|
5458
|
-
const { expression, names, values } = serializeRawCondition(
|
|
5459
|
-
condition,
|
|
5460
|
-
renderRawSlot
|
|
5461
|
-
);
|
|
5462
|
-
return { kind: "raw", expression, names, values };
|
|
5463
|
-
}
|
|
5464
|
-
if (typeof condition !== "object") {
|
|
5465
|
-
throw new Error(
|
|
5466
|
-
`${context}: write condition must be an object (\`{ notExists: true }\`, \`{ attributeExists: '<field>' }\`, \`{ attributeNotExists: '<field>' }\`, or a declarative field condition).`
|
|
5467
|
-
);
|
|
5468
|
-
}
|
|
5469
|
-
const obj = condition;
|
|
5470
|
-
if (obj.notExists === true) return { kind: "notExists" };
|
|
5471
|
-
if ("attributeExists" in obj && typeof obj.attributeExists === "string") {
|
|
5472
|
-
if (obj.attributeExists.length === 0) {
|
|
5473
|
-
throw new Error(
|
|
5474
|
-
`${context}: \`attributeExists\` must name a non-empty attribute field.`
|
|
5475
|
-
);
|
|
5476
|
-
}
|
|
5477
|
-
return { kind: "attributeExists", field: obj.attributeExists };
|
|
5478
|
-
}
|
|
5479
|
-
if ("attributeNotExists" in obj) {
|
|
5480
|
-
if (typeof obj.attributeNotExists !== "string" || obj.attributeNotExists.length === 0) {
|
|
5481
|
-
throw new Error(
|
|
5482
|
-
`${context}: \`attributeNotExists\` must name a non-empty attribute field.`
|
|
5483
|
-
);
|
|
5484
|
-
}
|
|
5485
|
-
return { kind: "attributeNotExists", field: obj.attributeNotExists };
|
|
5486
|
-
}
|
|
5487
|
-
if (usesOperatorTree(obj)) {
|
|
5488
|
-
const leaf = renderTreeLeaf ?? ((value, name) => defaultTreeLeaf(value, name, context));
|
|
5489
|
-
return { kind: "expr", declarative: buildTree(obj, context, leaf) };
|
|
5490
|
-
}
|
|
5491
|
-
const fields = {};
|
|
5492
|
-
for (const key of Object.keys(obj).sort()) {
|
|
5493
|
-
fields[key] = renderLeaf2(key, obj[key]);
|
|
5494
|
-
}
|
|
5495
|
-
return { kind: "equals", fields };
|
|
5496
|
-
}
|
|
5497
|
-
function assertNotNestedRaw(sub, context) {
|
|
5498
|
-
if (isRawCondition(sub)) {
|
|
5499
|
-
throw new Error(
|
|
5500
|
-
`${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.`
|
|
5501
|
-
);
|
|
5502
|
-
}
|
|
5503
|
-
}
|
|
5504
|
-
var LOGICAL_KEYS2 = /* @__PURE__ */ new Set(["and", "or", "not"]);
|
|
5505
|
-
var OPERATOR_KEYS2 = /* @__PURE__ */ new Set([
|
|
5506
|
-
"eq",
|
|
5507
|
-
"ne",
|
|
5508
|
-
"gt",
|
|
5509
|
-
"ge",
|
|
5510
|
-
"lt",
|
|
5511
|
-
"le",
|
|
5512
|
-
"between",
|
|
5513
|
-
"in",
|
|
5514
|
-
"beginsWith",
|
|
5515
|
-
"contains",
|
|
5516
|
-
"notContains",
|
|
5517
|
-
"attributeExists",
|
|
5518
|
-
"attributeType",
|
|
5519
|
-
"size"
|
|
5520
|
-
]);
|
|
5521
|
-
function isOperatorObject2(value) {
|
|
5522
|
-
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)) {
|
|
5523
5807
|
return false;
|
|
5524
5808
|
}
|
|
5525
5809
|
const keys = Object.keys(value);
|
|
5526
5810
|
if (keys.length === 0) return false;
|
|
5527
|
-
return keys.every((k) =>
|
|
5811
|
+
return keys.every((k) => CONDITION_OPERATOR_KEYS.has(k));
|
|
5528
5812
|
}
|
|
5529
|
-
function
|
|
5530
|
-
|
|
5531
|
-
|
|
5532
|
-
if (isOperatorObject2(value)) return true;
|
|
5533
|
-
}
|
|
5534
|
-
return false;
|
|
5813
|
+
function mintConditionOperand(leaf, field, op, index) {
|
|
5814
|
+
if (isParam(leaf)) return mintContractParamRef(conditionParamName(field, op, index));
|
|
5815
|
+
return leaf;
|
|
5535
5816
|
}
|
|
5536
|
-
function
|
|
5537
|
-
|
|
5538
|
-
|
|
5539
|
-
|
|
5540
|
-
return
|
|
5541
|
-
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
);
|
|
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 };
|
|
5545
5825
|
}
|
|
5546
|
-
function
|
|
5547
|
-
if (
|
|
5548
|
-
|
|
5549
|
-
|
|
5550
|
-
|
|
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`);
|
|
5551
5835
|
}
|
|
5552
|
-
function
|
|
5836
|
+
function descriptorConditionTree(obj, name, context) {
|
|
5553
5837
|
const out = {};
|
|
5554
|
-
for (const key of Object.
|
|
5555
|
-
const value = obj[key];
|
|
5838
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
5556
5839
|
if (value === void 0) continue;
|
|
5557
5840
|
if (key === "and" || key === "or") {
|
|
5558
5841
|
out[key] = value.map((sub) => {
|
|
5559
|
-
|
|
5560
|
-
return
|
|
5842
|
+
assertNoNestedRawCond(sub, name);
|
|
5843
|
+
return descriptorConditionTree(sub, name, `${context} ${key}`);
|
|
5561
5844
|
});
|
|
5562
5845
|
continue;
|
|
5563
5846
|
}
|
|
5564
5847
|
if (key === "not") {
|
|
5565
|
-
|
|
5566
|
-
out[key] =
|
|
5567
|
-
value,
|
|
5568
|
-
context,
|
|
5569
|
-
renderTreeLeaf
|
|
5570
|
-
);
|
|
5848
|
+
assertNoNestedRawCond(value, name);
|
|
5849
|
+
out[key] = descriptorConditionTree(value, name, `${context} not`);
|
|
5571
5850
|
continue;
|
|
5572
5851
|
}
|
|
5573
|
-
if (!
|
|
5574
|
-
out[key] =
|
|
5852
|
+
if (!isConditionOperatorObject4(value)) {
|
|
5853
|
+
out[key] = mintConditionOperand(value, key, "eq");
|
|
5575
5854
|
continue;
|
|
5576
5855
|
}
|
|
5577
5856
|
const ops = value;
|
|
5578
5857
|
const rendered = {};
|
|
5579
|
-
for (const op of Object.
|
|
5580
|
-
const opVal = ops[op];
|
|
5858
|
+
for (const [op, opVal] of Object.entries(ops)) {
|
|
5581
5859
|
if (op === "between") {
|
|
5582
5860
|
const [lo, hi] = opVal;
|
|
5583
5861
|
rendered[op] = [
|
|
5584
|
-
|
|
5585
|
-
|
|
5862
|
+
mintConditionOperand(lo, key, op, 0),
|
|
5863
|
+
mintConditionOperand(hi, key, op, 1)
|
|
5586
5864
|
];
|
|
5587
5865
|
} else if (op === "in") {
|
|
5588
|
-
rendered[op] = opVal.map(
|
|
5589
|
-
|
|
5590
|
-
);
|
|
5591
|
-
} else if (op === "attributeExists") {
|
|
5866
|
+
rendered[op] = opVal.map((v, i) => mintConditionOperand(v, key, op, i));
|
|
5867
|
+
} else if (op === "attributeExists" || op === "attributeType") {
|
|
5592
5868
|
rendered[op] = opVal;
|
|
5593
|
-
} else
|
|
5594
|
-
rendered[op] =
|
|
5595
|
-
} else {
|
|
5596
|
-
rendered[op] = renderTreeLeaf(opVal, conditionParamName(key, op));
|
|
5597
|
-
}
|
|
5598
|
-
}
|
|
5599
|
-
out[key] = rendered;
|
|
5600
|
-
}
|
|
5601
|
-
return out;
|
|
5602
|
-
}
|
|
5603
|
-
function assertSupportedCondition(commandName, condition) {
|
|
5604
|
-
if (condition.kind === "notExists") return;
|
|
5605
|
-
if (condition.kind === "attributeExists" || condition.kind === "attributeNotExists") {
|
|
5606
|
-
if (typeof condition.field !== "string" || condition.field.length === 0) {
|
|
5607
|
-
throw new Error(
|
|
5608
|
-
`Command '${commandName}' has an ${condition.kind} write condition with no attribute field. The Python bridge requires a non-empty field name.`
|
|
5609
|
-
);
|
|
5610
|
-
}
|
|
5611
|
-
return;
|
|
5612
|
-
}
|
|
5613
|
-
if (condition.kind === "equals") {
|
|
5614
|
-
const entries = Object.entries(condition.fields);
|
|
5615
|
-
if (entries.length === 0) {
|
|
5616
|
-
throw new Error(
|
|
5617
|
-
`Command '${commandName}' has an empty equality write condition. The Python bridge supports '{ notExists }' or non-empty field equality conditions only.`
|
|
5618
|
-
);
|
|
5619
|
-
}
|
|
5620
|
-
for (const [field, value] of entries) {
|
|
5621
|
-
if (typeof value !== "string") {
|
|
5622
|
-
throw new Error(
|
|
5623
|
-
`Command '${commandName}' write condition on '${field}' must be a template string value; the Python bridge supports equality on param/literal values only.`
|
|
5624
|
-
);
|
|
5869
|
+
} else {
|
|
5870
|
+
rendered[op] = mintConditionOperand(opVal, key, op);
|
|
5625
5871
|
}
|
|
5626
5872
|
}
|
|
5627
|
-
|
|
5628
|
-
}
|
|
5629
|
-
if (condition.kind === "raw") {
|
|
5630
|
-
if (typeof condition.expression !== "string" || condition.expression.length === 0) {
|
|
5631
|
-
throw new Error(
|
|
5632
|
-
`Command '${commandName}' has an empty raw \`cond\` write condition.`
|
|
5633
|
-
);
|
|
5634
|
-
}
|
|
5635
|
-
return;
|
|
5873
|
+
out[key] = rendered;
|
|
5636
5874
|
}
|
|
5637
|
-
|
|
5638
|
-
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
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
|
+
);
|
|
5645
5882
|
}
|
|
5646
|
-
throw new Error(
|
|
5647
|
-
`Command '${commandName}' has an unsupported write condition.`
|
|
5648
|
-
);
|
|
5649
5883
|
}
|
|
5650
|
-
function
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
if (!Number.isFinite(value)) {
|
|
5658
|
-
throw new Error(
|
|
5659
|
-
`Bridge spec is not JSON-serializable: non-finite number at ${path}.`
|
|
5660
|
-
);
|
|
5661
|
-
}
|
|
5662
|
-
return;
|
|
5663
|
-
case "object":
|
|
5664
|
-
break;
|
|
5665
|
-
default:
|
|
5666
|
-
throw new Error(
|
|
5667
|
-
`Bridge spec is not JSON-serializable: '${typeof value}' at ${path}.`
|
|
5668
|
-
);
|
|
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
|
+
);
|
|
5669
5891
|
}
|
|
5670
|
-
if (
|
|
5671
|
-
|
|
5672
|
-
|
|
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
|
+
);
|
|
5673
5896
|
}
|
|
5674
|
-
const
|
|
5675
|
-
|
|
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) {
|
|
5676
5918
|
throw new Error(
|
|
5677
|
-
`
|
|
5919
|
+
`publicCommandModel: method '${name}' must carry exactly one intent key (\`create\` / \`update\` / \`remove\`), but declared ${present.length === 0 ? "none" : present.join(", ")}.`
|
|
5678
5920
|
);
|
|
5679
5921
|
}
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
}
|
|
5686
|
-
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
|
+
);
|
|
5687
5927
|
}
|
|
5688
|
-
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
|
|
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 };
|
|
5692
5947
|
}
|
|
5693
5948
|
|
|
5694
5949
|
// src/define/transaction.ts
|
|
@@ -6060,7 +6315,7 @@ function verifyWrite(instrs, surfaced) {
|
|
|
6060
6315
|
verifyRecord(instrs.map((x) => x.item), surfaced, `${op} item`);
|
|
6061
6316
|
verifyRecord(instrs.map((x) => x.key), surfaced, `${op} key`);
|
|
6062
6317
|
verifyRecord(instrs.map((x) => x.changes), surfaced, `${op} changes`);
|
|
6063
|
-
|
|
6318
|
+
verifyCondition(
|
|
6064
6319
|
instrs.map((x) => x.condition),
|
|
6065
6320
|
surfaced,
|
|
6066
6321
|
`${op} condition`
|
|
@@ -6092,6 +6347,148 @@ function verifyRecord(records, surfaced, context) {
|
|
|
6092
6347
|
);
|
|
6093
6348
|
}
|
|
6094
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
|
+
}
|
|
6095
6492
|
function verifyWhen(whens, surfaced, context) {
|
|
6096
6493
|
const present = whens.filter((w) => w !== void 0);
|
|
6097
6494
|
if (present.length === 0) return;
|
|
@@ -6204,10 +6601,19 @@ function conditionTreeLeaf(value, name, context) {
|
|
|
6204
6601
|
`defineTransaction: ${context} condition operand must be a field reference (from \`p\` or a forEach element) or a concrete literal; got ${typeof value}.`
|
|
6205
6602
|
);
|
|
6206
6603
|
}
|
|
6207
|
-
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) {
|
|
6208
6614
|
const out = {};
|
|
6209
6615
|
for (const field of Object.keys(structure).sort()) {
|
|
6210
|
-
out[field] =
|
|
6616
|
+
out[field] = valueLeaf(structure[field], `${context} field '${field}'`);
|
|
6211
6617
|
}
|
|
6212
6618
|
return out;
|
|
6213
6619
|
}
|
|
@@ -6280,7 +6686,7 @@ function buildWriteItem(instr, forEachSource, txName) {
|
|
|
6280
6686
|
if (instr.operation === "put") {
|
|
6281
6687
|
return {
|
|
6282
6688
|
...base,
|
|
6283
|
-
item:
|
|
6689
|
+
item: valueRecord(instr.item ?? {}, `${txName} put`)
|
|
6284
6690
|
};
|
|
6285
6691
|
}
|
|
6286
6692
|
if (instr.operation === "delete") {
|
|
@@ -6304,7 +6710,7 @@ function buildWriteItem(instr, forEachSource, txName) {
|
|
|
6304
6710
|
)
|
|
6305
6711
|
};
|
|
6306
6712
|
}
|
|
6307
|
-
const changes =
|
|
6713
|
+
const changes = valueRecord(instr.changes ?? {}, `${txName} update changes`);
|
|
6308
6714
|
injectTransactionGsiRederive(
|
|
6309
6715
|
metadata,
|
|
6310
6716
|
instr.key ?? {},
|
|
@@ -6493,8 +6899,8 @@ function recoverKind(metadata, fieldName) {
|
|
|
6493
6899
|
function opToDefinition(contractName, methodName, op) {
|
|
6494
6900
|
const metadata = MetadataRegistry.get(op.entity.modelClass);
|
|
6495
6901
|
const params = {};
|
|
6496
|
-
const place = (paramName, bindField) => {
|
|
6497
|
-
const recovered = recoverKind(metadata, bindField);
|
|
6902
|
+
const place = (paramName, bindField, kindOverride) => {
|
|
6903
|
+
const recovered = kindOverride !== void 0 ? { kind: kindOverride } : recoverKind(metadata, bindField);
|
|
6498
6904
|
params[paramName] = recovered.literals === void 0 ? { kind: recovered.kind, required: true } : { kind: recovered.kind, literals: recovered.literals, required: true };
|
|
6499
6905
|
return recovered;
|
|
6500
6906
|
};
|
|
@@ -6568,13 +6974,13 @@ function convertStructure(structure, place, context) {
|
|
|
6568
6974
|
}
|
|
6569
6975
|
return out;
|
|
6570
6976
|
}
|
|
6571
|
-
function convertLeaf(leaf, bindField, place, context) {
|
|
6977
|
+
function convertLeaf(leaf, bindField, place, context, kindOverride) {
|
|
6572
6978
|
if (isContractParamRef(leaf)) {
|
|
6573
|
-
place(leaf.field, bindField);
|
|
6979
|
+
place(leaf.field, bindField, kindOverride);
|
|
6574
6980
|
return leaf;
|
|
6575
6981
|
}
|
|
6576
6982
|
if (isContractKeyFieldRef(leaf)) {
|
|
6577
|
-
place(leaf.field, bindField);
|
|
6983
|
+
place(leaf.field, bindField, kindOverride);
|
|
6578
6984
|
return leaf;
|
|
6579
6985
|
}
|
|
6580
6986
|
if (isConcreteLiteral3(leaf)) return leaf;
|
|
@@ -6582,24 +6988,118 @@ function convertLeaf(leaf, bindField, place, context) {
|
|
|
6582
6988
|
`${context}: value is neither a param/key reference nor a concrete literal \u2014 it cannot be serialized as a declarative contract operation.`
|
|
6583
6989
|
);
|
|
6584
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
|
+
}
|
|
6585
7006
|
function convertCondition(condition, place, context) {
|
|
6586
|
-
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
|
+
}
|
|
6587
7024
|
if (typeof condition === "object" && condition !== null) {
|
|
6588
|
-
const
|
|
6589
|
-
if (
|
|
6590
|
-
if (typeof
|
|
6591
|
-
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 };
|
|
6592
7029
|
}
|
|
6593
|
-
if (typeof
|
|
6594
|
-
return { attributeNotExists:
|
|
7030
|
+
if (typeof obj2.attributeNotExists === "string") {
|
|
7031
|
+
return { attributeNotExists: obj2.attributeNotExists };
|
|
6595
7032
|
}
|
|
6596
7033
|
}
|
|
7034
|
+
const obj = condition;
|
|
7035
|
+
if (usesConditionTree(obj)) {
|
|
7036
|
+
return convertConditionTree(obj, place, context);
|
|
7037
|
+
}
|
|
6597
7038
|
const out = {};
|
|
6598
|
-
for (const [field, leaf] of Object.entries(
|
|
7039
|
+
for (const [field, leaf] of Object.entries(obj)) {
|
|
6599
7040
|
out[field] = convertLeaf(leaf, field, place, `${context} field '${field}'`);
|
|
6600
7041
|
}
|
|
6601
7042
|
return out;
|
|
6602
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
|
+
}
|
|
6603
7103
|
function assertBooleanProjection(select, context) {
|
|
6604
7104
|
const out = {};
|
|
6605
7105
|
for (const [field, leaf] of Object.entries(select)) {
|
|
@@ -6910,6 +7410,7 @@ var TEMPLATE_TOKEN_RE = /\{([^{}]+)\}/g;
|
|
|
6910
7410
|
function collectTemplateParams(record, metadata, out) {
|
|
6911
7411
|
if (record === void 0) return;
|
|
6912
7412
|
for (const template of Object.values(record)) {
|
|
7413
|
+
if (typeof template !== "string") continue;
|
|
6913
7414
|
for (const match of template.matchAll(TEMPLATE_TOKEN_RE)) {
|
|
6914
7415
|
const token = match[1];
|
|
6915
7416
|
const field = token.startsWith("old.") ? token.slice("old.".length) : token;
|
|
@@ -7788,7 +8289,7 @@ function buildCommandSpec(def) {
|
|
|
7788
8289
|
const condition = extractCondition(def);
|
|
7789
8290
|
const description = def.description !== void 0 ? { description: def.description } : {};
|
|
7790
8291
|
if (def.operation === "put") {
|
|
7791
|
-
const item =
|
|
8292
|
+
const item = templateRecord2(def.key);
|
|
7792
8293
|
return {
|
|
7793
8294
|
type: "PutItem",
|
|
7794
8295
|
tableName,
|
|
@@ -7816,7 +8317,7 @@ function buildCommandSpec(def) {
|
|
|
7816
8317
|
entity,
|
|
7817
8318
|
params,
|
|
7818
8319
|
keyCondition: writeKeyCondition(metadata, def.key, entity),
|
|
7819
|
-
changes:
|
|
8320
|
+
changes: templateRecord2(def.changes),
|
|
7820
8321
|
...condition ? { condition } : {},
|
|
7821
8322
|
...description
|
|
7822
8323
|
};
|
|
@@ -7840,7 +8341,7 @@ function writeKeyCondition(metadata, key, entity) {
|
|
|
7840
8341
|
if (sk !== void 0) out.SK = sk;
|
|
7841
8342
|
return out;
|
|
7842
8343
|
}
|
|
7843
|
-
function
|
|
8344
|
+
function templateRecord2(structure) {
|
|
7844
8345
|
const out = {};
|
|
7845
8346
|
for (const key of Object.keys(structure).sort()) {
|
|
7846
8347
|
out[key] = templateLeaf(key, structure[key]);
|
|
@@ -8171,6 +8672,10 @@ export {
|
|
|
8171
8672
|
MARKER_ROW_ENTITY,
|
|
8172
8673
|
buildManifestEntity,
|
|
8173
8674
|
buildManifest,
|
|
8675
|
+
conditionParamName,
|
|
8676
|
+
assertSupportedCondition,
|
|
8677
|
+
assertJsonSerializable,
|
|
8678
|
+
assertBundleSerializable,
|
|
8174
8679
|
buildProjection,
|
|
8175
8680
|
compileFilterExpression,
|
|
8176
8681
|
evaluateFilter,
|
|
@@ -8238,10 +8743,6 @@ export {
|
|
|
8238
8743
|
publicQueryModel,
|
|
8239
8744
|
publicCommandModel,
|
|
8240
8745
|
isPlannedCommandMethod,
|
|
8241
|
-
conditionParamName,
|
|
8242
|
-
assertSupportedCondition,
|
|
8243
|
-
assertJsonSerializable,
|
|
8244
|
-
assertBundleSerializable,
|
|
8245
8746
|
isTransactionRef,
|
|
8246
8747
|
when,
|
|
8247
8748
|
defineTransaction,
|