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.
@@ -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-F2DI3GTI.js";
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 LOGICAL_KEYS = /* @__PURE__ */ new Set(["and", "or", "not"]);
248
- var OPERATOR_KEYS = /* @__PURE__ */ new Set([
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 (!isOperatorObject(condition)) {
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 isOperatorObject(value) {
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) => OPERATOR_KEYS.has(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 (LOGICAL_KEYS.has(key)) {
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 (!isOperatorObject(condition)) {
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 compileFragment(fragment, index = 0, resolveEntityRef = null, maintenanceGraph) {
2601
- const keyFields = primaryKeyFields(fragment);
2602
- const keyFieldSet = new Set(keyFields);
2603
- const operation = INTENT_OPERATION[fragment.intent];
2604
- const lifecycle = resolveLifecycle(fragment);
2605
- for (const field of keyFields) {
2606
- if (!(field in fragment.input)) {
2607
- throw new Error(
2608
- `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 }\`.`
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
- // The base `create` op guards against clobbering an existing row, exactly as
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 renderRecord(
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 descriptorConditionRecord(record, name) {
5379
- const out = {};
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) => OPERATOR_KEYS2.has(k));
5811
+ return keys.every((k) => CONDITION_OPERATOR_KEYS.has(k));
5528
5812
  }
5529
- function usesOperatorTree(obj) {
5530
- for (const [key, value] of Object.entries(obj)) {
5531
- if (LOGICAL_KEYS2.has(key)) return true;
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 defaultTreeLeaf(value, name, context) {
5537
- void name;
5538
- if (value instanceof Date) return value.toISOString();
5539
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
5540
- return value;
5541
- }
5542
- throw new Error(
5543
- `${context}: condition operand must be a concrete literal or a param; got ${typeof value}.`
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 conditionParamName(field, op, index) {
5547
- if (op === "between") return `${field}_${index === 0 ? "lo" : "hi"}`;
5548
- if (op === "in") return `${field}_${index ?? 0}`;
5549
- if (op === "size") return `${field}_size`;
5550
- return field;
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 buildTree(obj, context, renderTreeLeaf) {
5836
+ function descriptorConditionTree(obj, name, context) {
5553
5837
  const out = {};
5554
- for (const key of Object.keys(obj).sort()) {
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
- assertNotNestedRaw(sub, context);
5560
- return buildTree(sub, context, renderTreeLeaf);
5842
+ assertNoNestedRawCond(sub, name);
5843
+ return descriptorConditionTree(sub, name, `${context} ${key}`);
5561
5844
  });
5562
5845
  continue;
5563
5846
  }
5564
5847
  if (key === "not") {
5565
- assertNotNestedRaw(value, context);
5566
- out[key] = buildTree(
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 (!isOperatorObject2(value)) {
5574
- out[key] = { eq: renderTreeLeaf(value, conditionParamName(key, "eq")) };
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.keys(ops).sort()) {
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
- renderTreeLeaf(lo, conditionParamName(key, op, 0)),
5585
- renderTreeLeaf(hi, conditionParamName(key, op, 1))
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
- (v, i) => renderTreeLeaf(v, conditionParamName(key, op, i))
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 if (op === "attributeType") {
5594
- rendered[op] = String(opVal);
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
- return;
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
- if (condition.kind === "expr") {
5638
- const tree = condition.declarative;
5639
- if (!tree || Object.keys(tree).length === 0) {
5640
- throw new Error(
5641
- `Command '${commandName}' has an empty declarative write condition.`
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 assertJsonSerializable(value, path = "$") {
5651
- if (value === null) return;
5652
- switch (typeof value) {
5653
- case "string":
5654
- case "boolean":
5655
- return;
5656
- case "number":
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 (Array.isArray(value)) {
5671
- value.forEach((v, i) => assertJsonSerializable(v, `${path}[${i}]`));
5672
- return;
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 proto = Object.getPrototypeOf(value);
5675
- if (proto !== Object.prototype && proto !== null) {
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
- `Bridge spec is not JSON-serializable: non-plain object (${value.constructor?.name ?? "unknown"}) at ${path}.`
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
- for (const [k, v] of Object.entries(value)) {
5681
- if (v === void 0) {
5682
- throw new Error(
5683
- `Bridge spec is not JSON-serializable: 'undefined' value at ${path}.${k}.`
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
- function assertBundleSerializable(bundle) {
5690
- assertJsonSerializable(bundle.manifest, "$.manifest");
5691
- assertJsonSerializable(bundle.operations, "$.operations");
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
- verifyRecord(
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 templateRecord2(structure, context) {
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] = leafTemplate(structure[field], `${context} field '${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: templateRecord2(instr.item ?? {}, `${txName} put`)
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 = templateRecord2(instr.changes ?? {}, `${txName} update 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)) return 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 obj = condition;
6589
- if (obj.notExists === true) return { notExists: true };
6590
- if (typeof obj.attributeExists === "string") {
6591
- return { attributeExists: obj.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 obj.attributeNotExists === "string") {
6594
- return { attributeNotExists: obj.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(condition)) {
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 = templateRecord3(def.key);
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: templateRecord3(def.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 templateRecord3(structure) {
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,