graphddb 0.7.7 → 0.7.9

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,10 +1,12 @@
1
1
  import {
2
2
  BATCH_GET_MAX_KEYS,
3
+ CONDITION_OPERATOR_KEYS,
3
4
  ChangeCaptureRegistry,
4
5
  ClientManager,
5
6
  MAX_TRANSACT_ITEMS,
6
7
  MetadataRegistry,
7
8
  NO_MIDDLEWARE,
9
+ SELECT_SPEC,
8
10
  attachHiddenKey,
9
11
  attachModelClass,
10
12
  buildConditionExpression,
@@ -21,6 +23,7 @@ import {
21
23
  chunkArray,
22
24
  commitTransaction,
23
25
  compileRawCondition,
26
+ condParamName,
24
27
  ctxModelFor,
25
28
  detectRelationFields,
26
29
  execItemKeySignature,
@@ -49,7 +52,7 @@ import {
49
52
  serializeRawCondition,
50
53
  skTemplate,
51
54
  validateInlineSnapshotSelect
52
- } from "./chunk-F2DI3GTI.js";
55
+ } from "./chunk-PFFPLD4B.js";
53
56
  import {
54
57
  TableMapping,
55
58
  createColumnMap,
@@ -201,6 +204,246 @@ function buildManifest(registry = MetadataRegistry) {
201
204
  };
202
205
  }
203
206
 
207
+ // src/spec/guard.ts
208
+ function conditionInputToSpec(condition, context, renderLeaf2, renderTreeLeaf, renderRawSlot) {
209
+ if (condition === void 0 || condition === null) return void 0;
210
+ if (isRawCondition(condition)) {
211
+ const { expression, names, values } = serializeRawCondition(
212
+ condition,
213
+ renderRawSlot
214
+ );
215
+ return { kind: "raw", expression, names, values };
216
+ }
217
+ if (typeof condition !== "object") {
218
+ throw new Error(
219
+ `${context}: write condition must be an object (\`{ notExists: true }\`, \`{ attributeExists: '<field>' }\`, \`{ attributeNotExists: '<field>' }\`, or a declarative field condition).`
220
+ );
221
+ }
222
+ const obj = condition;
223
+ if (obj.notExists === true) return { kind: "notExists" };
224
+ if ("attributeExists" in obj && typeof obj.attributeExists === "string") {
225
+ if (obj.attributeExists.length === 0) {
226
+ throw new Error(
227
+ `${context}: \`attributeExists\` must name a non-empty attribute field.`
228
+ );
229
+ }
230
+ return { kind: "attributeExists", field: obj.attributeExists };
231
+ }
232
+ if ("attributeNotExists" in obj) {
233
+ if (typeof obj.attributeNotExists !== "string" || obj.attributeNotExists.length === 0) {
234
+ throw new Error(
235
+ `${context}: \`attributeNotExists\` must name a non-empty attribute field.`
236
+ );
237
+ }
238
+ return { kind: "attributeNotExists", field: obj.attributeNotExists };
239
+ }
240
+ if (usesOperatorTree(obj)) {
241
+ const leaf = renderTreeLeaf ?? ((value, name) => defaultTreeLeaf(value, name, context));
242
+ return { kind: "expr", declarative: buildTree(obj, context, leaf) };
243
+ }
244
+ const fields = {};
245
+ for (const key of Object.keys(obj).sort()) {
246
+ fields[key] = renderLeaf2(key, obj[key]);
247
+ }
248
+ return { kind: "equals", fields };
249
+ }
250
+ function assertNotNestedRaw(sub, context) {
251
+ if (isRawCondition(sub)) {
252
+ throw new Error(
253
+ `${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.`
254
+ );
255
+ }
256
+ }
257
+ var LOGICAL_KEYS = /* @__PURE__ */ new Set(["and", "or", "not"]);
258
+ var OPERATOR_KEYS = /* @__PURE__ */ new Set([
259
+ "eq",
260
+ "ne",
261
+ "gt",
262
+ "ge",
263
+ "lt",
264
+ "le",
265
+ "between",
266
+ "in",
267
+ "beginsWith",
268
+ "contains",
269
+ "notContains",
270
+ "attributeExists",
271
+ "attributeType",
272
+ "size"
273
+ ]);
274
+ function isOperatorObject(value) {
275
+ if (typeof value !== "object" || value === null || Array.isArray(value) || value instanceof Date) {
276
+ return false;
277
+ }
278
+ const keys = Object.keys(value);
279
+ if (keys.length === 0) return false;
280
+ return keys.every((k) => OPERATOR_KEYS.has(k));
281
+ }
282
+ function usesOperatorTree(obj) {
283
+ for (const [key, value] of Object.entries(obj)) {
284
+ if (LOGICAL_KEYS.has(key)) return true;
285
+ if (isOperatorObject(value)) return true;
286
+ }
287
+ return false;
288
+ }
289
+ function defaultTreeLeaf(value, name, context) {
290
+ void name;
291
+ if (value instanceof Date) return value.toISOString();
292
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
293
+ return value;
294
+ }
295
+ throw new Error(
296
+ `${context}: condition operand must be a concrete literal or a param; got ${typeof value}.`
297
+ );
298
+ }
299
+ function conditionParamName(field, op, index) {
300
+ if (op === "between") return `${field}_${index === 0 ? "lo" : "hi"}`;
301
+ if (op === "in") return `${field}_${index ?? 0}`;
302
+ if (op === "size") return `${field}_size`;
303
+ return field;
304
+ }
305
+ function buildTree(obj, context, renderTreeLeaf) {
306
+ const out = {};
307
+ for (const key of Object.keys(obj).sort()) {
308
+ const value = obj[key];
309
+ if (value === void 0) continue;
310
+ if (key === "and" || key === "or") {
311
+ out[key] = value.map((sub) => {
312
+ assertNotNestedRaw(sub, context);
313
+ return buildTree(sub, context, renderTreeLeaf);
314
+ });
315
+ continue;
316
+ }
317
+ if (key === "not") {
318
+ assertNotNestedRaw(value, context);
319
+ out[key] = buildTree(
320
+ value,
321
+ context,
322
+ renderTreeLeaf
323
+ );
324
+ continue;
325
+ }
326
+ if (!isOperatorObject(value)) {
327
+ out[key] = { eq: renderTreeLeaf(value, conditionParamName(key, "eq")) };
328
+ continue;
329
+ }
330
+ const ops = value;
331
+ const rendered = {};
332
+ for (const op of Object.keys(ops).sort()) {
333
+ const opVal = ops[op];
334
+ if (op === "between") {
335
+ const [lo, hi] = opVal;
336
+ rendered[op] = [
337
+ renderTreeLeaf(lo, conditionParamName(key, op, 0)),
338
+ renderTreeLeaf(hi, conditionParamName(key, op, 1))
339
+ ];
340
+ } else if (op === "in") {
341
+ rendered[op] = opVal.map(
342
+ (v, i) => renderTreeLeaf(v, conditionParamName(key, op, i))
343
+ );
344
+ } else if (op === "attributeExists") {
345
+ rendered[op] = opVal;
346
+ } else if (op === "attributeType") {
347
+ rendered[op] = String(opVal);
348
+ } else {
349
+ rendered[op] = renderTreeLeaf(opVal, conditionParamName(key, op));
350
+ }
351
+ }
352
+ out[key] = rendered;
353
+ }
354
+ return out;
355
+ }
356
+ function assertSupportedCondition(commandName, condition) {
357
+ if (condition.kind === "notExists") return;
358
+ if (condition.kind === "attributeExists" || condition.kind === "attributeNotExists") {
359
+ if (typeof condition.field !== "string" || condition.field.length === 0) {
360
+ throw new Error(
361
+ `Command '${commandName}' has an ${condition.kind} write condition with no attribute field. The Python bridge requires a non-empty field name.`
362
+ );
363
+ }
364
+ return;
365
+ }
366
+ if (condition.kind === "equals") {
367
+ const entries = Object.entries(condition.fields);
368
+ if (entries.length === 0) {
369
+ throw new Error(
370
+ `Command '${commandName}' has an empty equality write condition. The Python bridge supports '{ notExists }' or non-empty field equality conditions only.`
371
+ );
372
+ }
373
+ for (const [field, value] of entries) {
374
+ if (typeof value !== "string") {
375
+ throw new Error(
376
+ `Command '${commandName}' write condition on '${field}' must be a template string value; the Python bridge supports equality on param/literal values only.`
377
+ );
378
+ }
379
+ }
380
+ return;
381
+ }
382
+ if (condition.kind === "raw") {
383
+ if (typeof condition.expression !== "string" || condition.expression.length === 0) {
384
+ throw new Error(
385
+ `Command '${commandName}' has an empty raw \`cond\` write condition.`
386
+ );
387
+ }
388
+ return;
389
+ }
390
+ if (condition.kind === "expr") {
391
+ const tree = condition.declarative;
392
+ if (!tree || Object.keys(tree).length === 0) {
393
+ throw new Error(
394
+ `Command '${commandName}' has an empty declarative write condition.`
395
+ );
396
+ }
397
+ return;
398
+ }
399
+ throw new Error(
400
+ `Command '${commandName}' has an unsupported write condition.`
401
+ );
402
+ }
403
+ function assertJsonSerializable(value, path = "$") {
404
+ if (value === null) return;
405
+ switch (typeof value) {
406
+ case "string":
407
+ case "boolean":
408
+ return;
409
+ case "number":
410
+ if (!Number.isFinite(value)) {
411
+ throw new Error(
412
+ `Bridge spec is not JSON-serializable: non-finite number at ${path}.`
413
+ );
414
+ }
415
+ return;
416
+ case "object":
417
+ break;
418
+ default:
419
+ throw new Error(
420
+ `Bridge spec is not JSON-serializable: '${typeof value}' at ${path}.`
421
+ );
422
+ }
423
+ if (Array.isArray(value)) {
424
+ value.forEach((v, i) => assertJsonSerializable(v, `${path}[${i}]`));
425
+ return;
426
+ }
427
+ const proto = Object.getPrototypeOf(value);
428
+ if (proto !== Object.prototype && proto !== null) {
429
+ throw new Error(
430
+ `Bridge spec is not JSON-serializable: non-plain object (${value.constructor?.name ?? "unknown"}) at ${path}.`
431
+ );
432
+ }
433
+ for (const [k, v] of Object.entries(value)) {
434
+ if (v === void 0) {
435
+ throw new Error(
436
+ `Bridge spec is not JSON-serializable: 'undefined' value at ${path}.${k}.`
437
+ );
438
+ }
439
+ assertJsonSerializable(v, `${path}.${k}`);
440
+ }
441
+ }
442
+ function assertBundleSerializable(bundle) {
443
+ assertJsonSerializable(bundle.manifest, "$.manifest");
444
+ assertJsonSerializable(bundle.operations, "$.operations");
445
+ }
446
+
204
447
  // src/planner/projection.ts
205
448
  function buildProjection(select, additionalFields) {
206
449
  const paths = [];
@@ -244,8 +487,8 @@ function buildProjection(select, additionalFields) {
244
487
  }
245
488
 
246
489
  // src/expression/filter-expression.ts
247
- var LOGICAL_KEYS = /* @__PURE__ */ new Set(["and", "or", "not"]);
248
- var OPERATOR_KEYS = /* @__PURE__ */ new Set([
490
+ var LOGICAL_KEYS2 = /* @__PURE__ */ new Set(["and", "or", "not"]);
491
+ var OPERATOR_KEYS2 = /* @__PURE__ */ new Set([
249
492
  "eq",
250
493
  "ne",
251
494
  "gt",
@@ -277,7 +520,7 @@ function valueAlias(ctx, field, raw) {
277
520
  }
278
521
  function compileField(ctx, field, condition) {
279
522
  const nAlias = nameAlias(ctx, field);
280
- if (!isOperatorObject(condition)) {
523
+ if (!isOperatorObject2(condition)) {
281
524
  const vAlias = valueAlias(ctx, field, condition);
282
525
  return `${nAlias} = ${vAlias}`;
283
526
  }
@@ -346,13 +589,13 @@ function compileField(ctx, field, condition) {
346
589
  }
347
590
  return joinAnd(clauses);
348
591
  }
349
- function isOperatorObject(value) {
592
+ function isOperatorObject2(value) {
350
593
  if (typeof value !== "object" || value === null || Array.isArray(value) || value instanceof Date || value instanceof Uint8Array || value instanceof Set) {
351
594
  return false;
352
595
  }
353
596
  const keys = Object.keys(value);
354
597
  if (keys.length === 0) return false;
355
- return keys.every((k) => OPERATOR_KEYS.has(k));
598
+ return keys.every((k) => OPERATOR_KEYS2.has(k));
356
599
  }
357
600
  function joinAnd(clauses) {
358
601
  if (clauses.length === 1) return clauses[0];
@@ -395,7 +638,7 @@ function compileNode(ctx, node) {
395
638
  const clauses = [];
396
639
  for (const [key, value] of Object.entries(node)) {
397
640
  if (value === void 0) continue;
398
- if (LOGICAL_KEYS.has(key)) {
641
+ if (LOGICAL_KEYS2.has(key)) {
399
642
  if (key === "and" || key === "or") {
400
643
  const parts = value.map((sub) => compileNode(ctx, sub)).filter((s) => s.length > 0);
401
644
  if (parts.length === 0) continue;
@@ -476,7 +719,7 @@ function eq(a, b) {
476
719
  return a === b;
477
720
  }
478
721
  function evaluateFieldCondition(actual, item, field, condition) {
479
- if (!isOperatorObject(condition)) {
722
+ if (!isOperatorObject2(condition)) {
480
723
  return eq(actual, condition);
481
724
  }
482
725
  const ops = condition;
@@ -1512,17 +1755,165 @@ async function executeQueryInner(modelClass, key, selectSpec, options, mw) {
1512
1755
  return hydratedItem;
1513
1756
  }
1514
1757
 
1515
- // src/operations/explain.ts
1516
- function executeExplain(modelClass, key, options) {
1517
- const metadata = MetadataRegistry.get(modelClass);
1518
- const exclusiveStartKey = options?.after ? decodeCursor(options.after) : void 0;
1519
- const normalized = normalizeTopLevelSelect(options?.select);
1520
- const select = normalized.select ?? {};
1521
- const filter = options?.filter ?? normalized.filter;
1522
- const mainPlan = plan(metadata, {
1523
- key,
1524
- select,
1525
- limit: options?.limit,
1758
+ // src/runtime/read-plan.ts
1759
+ function bindSlots(slots, params) {
1760
+ const out = {};
1761
+ for (const [field, slot] of Object.entries(slots)) {
1762
+ out[field] = slot.kind === "param" ? params[slot.name] : slot.value;
1763
+ }
1764
+ return out;
1765
+ }
1766
+ function bindOptionalSlot(slot, params) {
1767
+ if (slot === void 0) return void 0;
1768
+ return slot.kind === "param" ? params[slot.name] : slot.value;
1769
+ }
1770
+ function executeCompiledReadRoute(route, params, options) {
1771
+ const key = bindSlots(route.keySlots, params);
1772
+ const opts = { ...options ?? {} };
1773
+ if (route.kind === "query") {
1774
+ if (route.consistentReadSlot !== void 0) {
1775
+ opts.consistentRead = bindOptionalSlot(route.consistentReadSlot, params);
1776
+ }
1777
+ if (route.maxDepth !== void 0) opts.maxDepth = route.maxDepth;
1778
+ return executeQuery(route.modelClass, key, route.select, opts);
1779
+ }
1780
+ if (route.limitSlot !== void 0) {
1781
+ opts.limit = bindOptionalSlot(route.limitSlot, params);
1782
+ }
1783
+ if (route.afterSlot !== void 0) {
1784
+ opts.after = bindOptionalSlot(route.afterSlot, params);
1785
+ }
1786
+ if (route.order !== void 0) opts.order = route.order;
1787
+ if (route.filter !== void 0) opts.filter = route.filter;
1788
+ return executeList(route.modelClass, key, route.select, opts);
1789
+ }
1790
+ var MODEL_CLASS_IDS = /* @__PURE__ */ new WeakMap();
1791
+ var nextModelClassId = 1;
1792
+ function modelClassIdentity(cls) {
1793
+ let id = MODEL_CLASS_IDS.get(cls);
1794
+ if (id === void 0) {
1795
+ id = nextModelClassId++;
1796
+ MODEL_CLASS_IDS.set(cls, id);
1797
+ }
1798
+ return `model#${id}`;
1799
+ }
1800
+ var BoundedLru = class {
1801
+ constructor(max) {
1802
+ this.max = max;
1803
+ }
1804
+ max;
1805
+ map = /* @__PURE__ */ new Map();
1806
+ get(key) {
1807
+ const v = this.map.get(key);
1808
+ if (v !== void 0) {
1809
+ this.map.delete(key);
1810
+ this.map.set(key, v);
1811
+ }
1812
+ return v;
1813
+ }
1814
+ set(key, value) {
1815
+ if (this.map.has(key)) this.map.delete(key);
1816
+ this.map.set(key, value);
1817
+ if (this.map.size > this.max) {
1818
+ const oldest = this.map.keys().next().value;
1819
+ if (oldest !== void 0) this.map.delete(oldest);
1820
+ }
1821
+ }
1822
+ get size() {
1823
+ return this.map.size;
1824
+ }
1825
+ clear() {
1826
+ this.map.clear();
1827
+ }
1828
+ };
1829
+ var ADHOC_READ_PLAN_CACHE_MAX = 256;
1830
+ var ADHOC_READ_PLAN_CACHE = new BoundedLru(ADHOC_READ_PLAN_CACHE_MAX);
1831
+ var adHocCompileCount = 0;
1832
+ function shapeToken(value) {
1833
+ if (value === null) return "null";
1834
+ const t = typeof value;
1835
+ if (t === "string") return JSON.stringify(value);
1836
+ if (t === "number" || t === "boolean") return String(value);
1837
+ if (t === "undefined") return "u";
1838
+ if (t === "bigint") return `n${String(value)}`;
1839
+ if (t === "function" || t === "symbol") return null;
1840
+ if (value instanceof Date) return `D${value.getTime()}`;
1841
+ if (Array.isArray(value)) {
1842
+ const parts2 = [];
1843
+ for (const v of value) {
1844
+ const p = shapeToken(v);
1845
+ if (p === null) return null;
1846
+ parts2.push(p);
1847
+ }
1848
+ return `[${parts2.join(",")}]`;
1849
+ }
1850
+ if (isSelectBuilder(value)) {
1851
+ const spec = value[SELECT_SPEC];
1852
+ const inner = shapeToken({
1853
+ select: spec.select,
1854
+ filter: spec.filter,
1855
+ limit: spec.limit,
1856
+ after: spec.after,
1857
+ order: spec.order
1858
+ });
1859
+ return inner === null ? null : `B${inner}`;
1860
+ }
1861
+ const proto = Object.getPrototypeOf(value);
1862
+ if (proto !== Object.prototype && proto !== null) return null;
1863
+ const rec = value;
1864
+ const parts = [];
1865
+ for (const k of Object.keys(rec).sort()) {
1866
+ const p = shapeToken(rec[k]);
1867
+ if (p === null) return null;
1868
+ parts.push(`${JSON.stringify(k)}:${p}`);
1869
+ }
1870
+ return `{${parts.join(",")}}`;
1871
+ }
1872
+ function adHocStructureKey(kind, modelClass, key, select) {
1873
+ const selectToken = shapeToken(select);
1874
+ if (selectToken === null) return null;
1875
+ const fields = Object.keys(key).map((f) => JSON.stringify(f)).join(",");
1876
+ return `${kind}|${modelClassIdentity(modelClass)}|${fields}|${selectToken}`;
1877
+ }
1878
+ function lowerAdHocReadRoute(kind, modelClass, key, select) {
1879
+ const cacheKey = adHocStructureKey(kind, modelClass, key, select);
1880
+ if (cacheKey !== null) {
1881
+ const cached = ADHOC_READ_PLAN_CACHE.get(cacheKey);
1882
+ if (cached !== void 0) return cached;
1883
+ }
1884
+ adHocCompileCount++;
1885
+ const keySlots = {};
1886
+ for (const field of Object.keys(key)) {
1887
+ keySlots[field] = { kind: "param", name: field };
1888
+ }
1889
+ const route = { alias: "$adhoc", kind, modelClass, select, keySlots };
1890
+ if (cacheKey !== null) ADHOC_READ_PLAN_CACHE.set(cacheKey, route);
1891
+ return route;
1892
+ }
1893
+ function runAdHoc(route, select, key, options) {
1894
+ const effective = route.select === select ? route : { ...route, select };
1895
+ return executeCompiledReadRoute(effective, key, options);
1896
+ }
1897
+ function executeAdHocQuery(modelClass, key, selectSpec, options) {
1898
+ const route = lowerAdHocReadRoute("query", modelClass, key, selectSpec);
1899
+ return runAdHoc(route, selectSpec, key, options);
1900
+ }
1901
+ function executeAdHocList(modelClass, key, selectSpec, options) {
1902
+ const route = lowerAdHocReadRoute("list", modelClass, key, selectSpec);
1903
+ return runAdHoc(route, selectSpec, key, options);
1904
+ }
1905
+
1906
+ // src/operations/explain.ts
1907
+ function executeExplain(modelClass, key, options) {
1908
+ const metadata = MetadataRegistry.get(modelClass);
1909
+ const exclusiveStartKey = options?.after ? decodeCursor(options.after) : void 0;
1910
+ const normalized = normalizeTopLevelSelect(options?.select);
1911
+ const select = normalized.select ?? {};
1912
+ const filter = options?.filter ?? normalized.filter;
1913
+ const mainPlan = plan(metadata, {
1914
+ key,
1915
+ select,
1916
+ limit: options?.limit,
1526
1917
  after: exclusiveStartKey,
1527
1918
  order: options?.order,
1528
1919
  consistentRead: options?.consistentRead,
@@ -2597,6 +2988,53 @@ function renderInputLeaf(leaf, isKeyFieldInPut, consumerIndex, consumerField, re
2597
2988
  }
2598
2989
  return leaf;
2599
2990
  }
2991
+ function isConditionOperatorObject(value) {
2992
+ if (typeof value !== "object" || value === null || Array.isArray(value) || value instanceof Date || isMutationInputRef(value)) {
2993
+ return false;
2994
+ }
2995
+ const keys = Object.keys(value);
2996
+ if (keys.length === 0) return false;
2997
+ return keys.every((k) => CONDITION_OPERATOR_KEYS.has(k));
2998
+ }
2999
+ function lowerConditionLeaf(leaf) {
3000
+ if (isMutationInputRef(leaf)) return mintContractParamRef(leaf.field);
3001
+ return leaf;
3002
+ }
3003
+ function lowerFragmentCondition(condition) {
3004
+ const obj = condition;
3005
+ const out = {};
3006
+ for (const [key, value] of Object.entries(obj)) {
3007
+ if (value === void 0) continue;
3008
+ if (key === "and" || key === "or") {
3009
+ out[key] = value.map((sub) => lowerFragmentCondition(sub));
3010
+ continue;
3011
+ }
3012
+ if (key === "not") {
3013
+ out[key] = lowerFragmentCondition(value);
3014
+ continue;
3015
+ }
3016
+ if (!isConditionOperatorObject(value)) {
3017
+ out[key] = lowerConditionLeaf(value);
3018
+ continue;
3019
+ }
3020
+ const ops = value;
3021
+ const rendered = {};
3022
+ for (const [op, opVal] of Object.entries(ops)) {
3023
+ if (op === "between") {
3024
+ const [lo, hi] = opVal;
3025
+ rendered[op] = [lowerConditionLeaf(lo), lowerConditionLeaf(hi)];
3026
+ } else if (op === "in") {
3027
+ rendered[op] = opVal.map((v) => lowerConditionLeaf(v));
3028
+ } else if (op === "attributeExists" || op === "attributeType") {
3029
+ rendered[op] = opVal;
3030
+ } else {
3031
+ rendered[op] = lowerConditionLeaf(opVal);
3032
+ }
3033
+ }
3034
+ out[key] = rendered;
3035
+ }
3036
+ return out;
3037
+ }
2600
3038
  function compileFragment(fragment, index = 0, resolveEntityRef = null, maintenanceGraph) {
2601
3039
  const keyFields = primaryKeyFields(fragment);
2602
3040
  const keyFieldSet = new Set(keyFields);
@@ -2609,6 +3047,7 @@ function compileFragment(fragment, index = 0, resolveEntityRef = null, maintenan
2609
3047
  );
2610
3048
  }
2611
3049
  }
3050
+ const userCondition = fragment.condition !== void 0 ? lowerFragmentCondition(fragment.condition) : void 0;
2612
3051
  let op;
2613
3052
  if (fragment.intent === "create") {
2614
3053
  const item = {};
@@ -2621,6 +3060,7 @@ function compileFragment(fragment, index = 0, resolveEntityRef = null, maintenan
2621
3060
  resolveEntityRef
2622
3061
  );
2623
3062
  }
3063
+ const condition = userCondition !== void 0 ? { and: [{ PK: { attributeExists: false } }, userCondition] } : { notExists: true };
2624
3064
  op = {
2625
3065
  __isContractMethodOp: true,
2626
3066
  entity: fragment.entity,
@@ -2628,9 +3068,7 @@ function compileFragment(fragment, index = 0, resolveEntityRef = null, maintenan
2628
3068
  keys: wholeKeysSentinel(),
2629
3069
  // a `put` derives identity from the item.
2630
3070
  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 }
3071
+ condition
2634
3072
  };
2635
3073
  } else {
2636
3074
  const changes = {};
@@ -2644,7 +3082,11 @@ function compileFragment(fragment, index = 0, resolveEntityRef = null, maintenan
2644
3082
  operation,
2645
3083
  keys: wholeKeysSentinel(),
2646
3084
  keyFields,
2647
- ...fragment.intent === "update" ? { changes } : {}
3085
+ ...fragment.intent === "update" ? { changes } : {},
3086
+ // #242: a conditioned update / remove — the write is a CAS-style gate (the
3087
+ // row must satisfy the condition or the write is rejected, not silently
3088
+ // committed). Absent → an unconditional update / remove (unchanged).
3089
+ ...userCondition !== void 0 ? { condition: userCondition } : {}
2648
3090
  };
2649
3091
  }
2650
3092
  const conditionChecks = lifecycle?.effects.requires !== void 0 && lifecycle.effects.requires.length > 0 ? deriveConditionChecks(fragment, lifecycle.effects.requires) : void 0;
@@ -2832,13 +3274,62 @@ function renderCondition(condition, key, params, context) {
2832
3274
  return { attributeNotExists: obj.attributeNotExists };
2833
3275
  }
2834
3276
  }
2835
- return renderRecord(
3277
+ return renderConditionTree(
2836
3278
  condition,
2837
3279
  key,
2838
3280
  params,
2839
3281
  `${context} condition`
2840
3282
  );
2841
3283
  }
3284
+ function isConditionOperatorObject2(value) {
3285
+ if (typeof value !== "object" || value === null || Array.isArray(value) || value instanceof Date || isContractParamRef(value) || isContractKeyFieldRef(value)) {
3286
+ return false;
3287
+ }
3288
+ const keys = Object.keys(value);
3289
+ if (keys.length === 0) return false;
3290
+ return keys.every((k) => CONDITION_OPERATOR_KEYS.has(k));
3291
+ }
3292
+ function renderConditionTree(node, key, params, context) {
3293
+ const out = {};
3294
+ for (const [k, value] of Object.entries(node)) {
3295
+ if (value === void 0) continue;
3296
+ if (k === "and" || k === "or") {
3297
+ out[k] = value.map(
3298
+ (sub) => renderConditionTree(sub, key, params, `${context} ${k}`)
3299
+ );
3300
+ continue;
3301
+ }
3302
+ if (k === "not") {
3303
+ out[k] = renderConditionTree(value, key, params, `${context} not`);
3304
+ continue;
3305
+ }
3306
+ if (!isConditionOperatorObject2(value)) {
3307
+ out[k] = renderLeaf(value, key, params, `${context} field '${k}'`);
3308
+ continue;
3309
+ }
3310
+ const ops = value;
3311
+ const rendered = {};
3312
+ for (const [op, opVal] of Object.entries(ops)) {
3313
+ if (op === "between") {
3314
+ const [lo, hi] = opVal;
3315
+ rendered[op] = [
3316
+ renderLeaf(lo, key, params, `${context} field '${k}' between[0]`),
3317
+ renderLeaf(hi, key, params, `${context} field '${k}' between[1]`)
3318
+ ];
3319
+ } else if (op === "in") {
3320
+ rendered[op] = opVal.map(
3321
+ (v, i) => renderLeaf(v, key, params, `${context} field '${k}' in[${i}]`)
3322
+ );
3323
+ } else if (op === "attributeExists" || op === "attributeType") {
3324
+ rendered[op] = opVal;
3325
+ } else {
3326
+ rendered[op] = renderLeaf(opVal, key, params, `${context} field '${k}' ${op}`);
3327
+ }
3328
+ }
3329
+ out[k] = rendered;
3330
+ }
3331
+ return out;
3332
+ }
2842
3333
  function renderWrite(op, key, params, contextLabel) {
2843
3334
  if (!isContractKeyRef(op.keys) && op.operation !== "put") {
2844
3335
  throw new Error(
@@ -2924,6 +3415,7 @@ function renderConditionCheck(check, key, params, contextLabel) {
2924
3415
  }
2925
3416
  var RUNTIME_TOKEN_RE = /\{([^{}]+)\}/g;
2926
3417
  function renderTemplate(template, key, params, contextLabel) {
3418
+ if (typeof template !== "string") return template;
2927
3419
  const resolveToken = (token) => {
2928
3420
  if (token in params) return params[token];
2929
3421
  if (token in key) return key[token];
@@ -3930,13 +4422,13 @@ async function executeReadRoute(alias, route) {
3930
4422
  );
3931
4423
  const opt = route.options ?? {};
3932
4424
  if (isQuery) {
3933
- return executeQuery(modelClass, route.key, route.select, {
4425
+ return executeAdHocQuery(modelClass, route.key, route.select, {
3934
4426
  consistentRead: opt.consistentRead,
3935
4427
  maxDepth: opt.maxDepth,
3936
4428
  context: opt.context
3937
4429
  });
3938
4430
  }
3939
- return executeList(modelClass, route.key, route.select, {
4431
+ return executeAdHocList(modelClass, route.key, route.select, {
3940
4432
  limit: opt.limit,
3941
4433
  after: opt.after,
3942
4434
  order: opt.order,
@@ -4171,17 +4663,6 @@ function recordSlots(binding, where) {
4171
4663
  }
4172
4664
  return { fields, slots };
4173
4665
  }
4174
- function bindSlots(slots, params) {
4175
- const out = {};
4176
- for (const [field, slot] of Object.entries(slots)) {
4177
- out[field] = slot.kind === "param" ? params[slot.name] : slot.value;
4178
- }
4179
- return out;
4180
- }
4181
- function bindOptionalSlot(slot, params) {
4182
- if (slot === void 0) return void 0;
4183
- return slot.kind === "param" ? params[slot.name] : slot.value;
4184
- }
4185
4666
  function recordOptionSlot(value, where) {
4186
4667
  if (value === void 0) return void 0;
4187
4668
  if (isPreparedParamRef(value)) return { kind: "param", name: value.name };
@@ -4241,23 +4722,7 @@ var PreparedReadStatement = class {
4241
4722
  return out;
4242
4723
  }
4243
4724
  runRoute(route, params, options) {
4244
- const key = bindSlots(route.keySlots, params);
4245
- const consistentRead = bindOptionalSlot(route.consistentReadSlot, params);
4246
- if (route.kind === "query") {
4247
- return executeQuery(route.modelClass, key, route.select, {
4248
- consistentRead,
4249
- maxDepth: route.maxDepth,
4250
- ...options.retry !== void 0 ? { retry: options.retry } : {},
4251
- ...options.context !== void 0 ? { context: options.context } : {}
4252
- });
4253
- }
4254
- const limit = bindOptionalSlot(route.limitSlot, params);
4255
- const after = bindOptionalSlot(route.afterSlot, params);
4256
- return executeList(route.modelClass, key, route.select, {
4257
- limit,
4258
- after,
4259
- order: route.order,
4260
- filter: route.filter,
4725
+ return executeCompiledReadRoute(route, params, {
4261
4726
  ...options.retry !== void 0 ? { retry: options.retry } : {},
4262
4727
  ...options.context !== void 0 ? { context: options.context } : {}
4263
4728
  });
@@ -4421,35 +4886,6 @@ function assertStaticTemplate(value, where) {
4421
4886
  }
4422
4887
  }
4423
4888
  var PREPARE_CACHE_MAX = 256;
4424
- var BoundedLru = class {
4425
- constructor(max) {
4426
- this.max = max;
4427
- }
4428
- max;
4429
- map = /* @__PURE__ */ new Map();
4430
- get(key) {
4431
- const v = this.map.get(key);
4432
- if (v !== void 0) {
4433
- this.map.delete(key);
4434
- this.map.set(key, v);
4435
- }
4436
- return v;
4437
- }
4438
- set(key, value) {
4439
- if (this.map.has(key)) this.map.delete(key);
4440
- this.map.set(key, value);
4441
- if (this.map.size > this.max) {
4442
- const oldest = this.map.keys().next().value;
4443
- if (oldest !== void 0) this.map.delete(oldest);
4444
- }
4445
- }
4446
- get size() {
4447
- return this.map.size;
4448
- }
4449
- clear() {
4450
- this.map.clear();
4451
- }
4452
- };
4453
4889
  var PREPARE_CACHE = new BoundedLru(PREPARE_CACHE_MAX);
4454
4890
  function structureKey(routes) {
4455
4891
  const parts = routes.map(({ alias, route }) => {
@@ -4465,18 +4901,11 @@ function structureKey(routes) {
4465
4901
  });
4466
4902
  return parts.join("||");
4467
4903
  }
4468
- var MODEL_IDENTITY_IDS = /* @__PURE__ */ new WeakMap();
4469
- var nextModelIdentityId = 1;
4470
4904
  function modelIdentity(ref, alias) {
4471
4905
  try {
4472
4906
  const model = resolveModelStatic(ref, alias);
4473
4907
  const cls = resolveModelClass(model);
4474
- let id = MODEL_IDENTITY_IDS.get(cls);
4475
- if (id === void 0) {
4476
- id = nextModelIdentityId++;
4477
- MODEL_IDENTITY_IDS.set(cls, id);
4478
- }
4479
- return `model#${id}`;
4908
+ return modelClassIdentity(cls);
4480
4909
  } catch {
4481
4910
  return "unresolved";
4482
4911
  }
@@ -4776,10 +5205,10 @@ var DDBModel = class {
4776
5205
  // method type — the public overloads (the only caller-visible surface)
4777
5206
  // are untouched.
4778
5207
  query: ((key, select, options) => {
4779
- return executeQuery(modelClass, key, select, options);
5208
+ return executeAdHocQuery(modelClass, key, select, options);
4780
5209
  }),
4781
5210
  list: ((key, select, options) => {
4782
- return executeList(modelClass, key, select, options);
5211
+ return executeAdHocList(modelClass, key, select, options);
4783
5212
  }),
4784
5213
  explain(key, options) {
4785
5214
  return executeExplain(
@@ -4945,23 +5374,107 @@ function assertFaithfulInput(input, intent, entityName) {
4945
5374
  }
4946
5375
  return input;
4947
5376
  }
5377
+ function isConditionOperatorObject3(value) {
5378
+ if (typeof value !== "object" || value === null || Array.isArray(value) || value instanceof Date || isMutationInputRef(value)) {
5379
+ return false;
5380
+ }
5381
+ const keys = Object.keys(value);
5382
+ if (keys.length === 0) return false;
5383
+ return keys.every((k) => CONDITION_OPERATOR_KEYS.has(k));
5384
+ }
5385
+ function assertFaithfulConditionLeaf(leaf, intent, entityName, where) {
5386
+ if (isMutationInputRef(leaf) || isConcreteLiteral(leaf)) return;
5387
+ if (isAliasFieldRef(leaf)) {
5388
+ throw new Error(
5389
+ `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.`
5390
+ );
5391
+ }
5392
+ throw new Error(
5393
+ `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.`
5394
+ );
5395
+ }
5396
+ function assertFaithfulCondition(condition, intent, entityName) {
5397
+ if (condition === null || typeof condition !== "object" || Array.isArray(condition)) {
5398
+ throw new Error(
5399
+ `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 ] }\`).`
5400
+ );
5401
+ }
5402
+ const obj = condition;
5403
+ if (Object.keys(obj).length === 0) {
5404
+ throw new Error(
5405
+ `mutation: the '${intent}' fragment on '${entityName}' declared an EMPTY \`condition\` object. Omit \`condition\` entirely for an unconditional write, or declare a real gate.`
5406
+ );
5407
+ }
5408
+ for (const [key, value] of Object.entries(obj)) {
5409
+ if (value === void 0) continue;
5410
+ if (key === "and" || key === "or") {
5411
+ if (!Array.isArray(value) || value.length === 0) {
5412
+ throw new Error(
5413
+ `mutation: the '${intent}' fragment on '${entityName}' \`condition\` \`${key}\` must be a non-empty array of sub-conditions.`
5414
+ );
5415
+ }
5416
+ for (const sub of value) assertFaithfulCondition(sub, intent, entityName);
5417
+ continue;
5418
+ }
5419
+ if (key === "not") {
5420
+ assertFaithfulCondition(value, intent, entityName);
5421
+ continue;
5422
+ }
5423
+ if (!isConditionOperatorObject3(value)) {
5424
+ assertFaithfulConditionLeaf(value, intent, entityName, `field '${key}'`);
5425
+ continue;
5426
+ }
5427
+ const ops = value;
5428
+ for (const [op, opVal] of Object.entries(ops)) {
5429
+ if (op === "between") {
5430
+ if (!Array.isArray(opVal) || opVal.length !== 2) {
5431
+ throw new Error(
5432
+ `mutation: the '${intent}' fragment on '${entityName}' \`condition\` field '${key}' \`between\` takes a \`[lo, hi]\` pair.`
5433
+ );
5434
+ }
5435
+ assertFaithfulConditionLeaf(opVal[0], intent, entityName, `field '${key}' between[0]`);
5436
+ assertFaithfulConditionLeaf(opVal[1], intent, entityName, `field '${key}' between[1]`);
5437
+ } else if (op === "in") {
5438
+ if (!Array.isArray(opVal) || opVal.length === 0) {
5439
+ throw new Error(
5440
+ `mutation: the '${intent}' fragment on '${entityName}' \`condition\` field '${key}' \`in\` takes a non-empty array of operands.`
5441
+ );
5442
+ }
5443
+ opVal.forEach(
5444
+ (v, i) => assertFaithfulConditionLeaf(v, intent, entityName, `field '${key}' in[${i}]`)
5445
+ );
5446
+ } else if (op === "attributeExists" || op === "attributeType") {
5447
+ if (isMutationInputRef(opVal)) {
5448
+ throw new Error(
5449
+ `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.`
5450
+ );
5451
+ }
5452
+ } else {
5453
+ assertFaithfulConditionLeaf(opVal, intent, entityName, `field '${key}' ${op}`);
5454
+ }
5455
+ }
5456
+ }
5457
+ return condition;
5458
+ }
4948
5459
  function isConcreteLiteral(value) {
4949
5460
  const t = typeof value;
4950
5461
  return value === null || value instanceof Date || t === "string" || t === "number" || t === "boolean";
4951
5462
  }
4952
- function makeFragment(intent, entity, input, use) {
5463
+ function makeFragment(intent, entity, input, use, condition) {
4953
5464
  const checked = assertFaithfulInput(input ?? {}, intent, entity.name);
4954
5465
  if (use !== void 0 && !isEntityWritesDefinition(use)) {
4955
5466
  throw new Error(
4956
5467
  `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
5468
  );
4958
5469
  }
5470
+ const checkedCondition = condition !== void 0 ? assertFaithfulCondition(condition, intent, entity.name) : void 0;
4959
5471
  return {
4960
5472
  [FRAGMENT_BRAND]: true,
4961
5473
  intent,
4962
5474
  entity,
4963
5475
  input: checked,
4964
- ...use !== void 0 ? { use } : {}
5476
+ ...use !== void 0 ? { use } : {},
5477
+ ...checkedCondition !== void 0 ? { condition: checkedCondition } : {}
4965
5478
  };
4966
5479
  }
4967
5480
  var COMMAND_PLAN_BRAND = /* @__PURE__ */ Symbol("graphddb:commandPlan");
@@ -5055,7 +5568,7 @@ function mutation(nameOrBody, maybeBody) {
5055
5568
  const { intent, entity } = resolveDescriptorTarget(descriptor, alias);
5056
5569
  const merged = mergeBindings(descriptor, intent, alias);
5057
5570
  const resolved = resolveAliasRefs(merged, aliasToIndex, alias);
5058
- return makeFragment(intent, entity, resolved, descriptor.use);
5571
+ return makeFragment(intent, entity, resolved, descriptor.use, descriptor.condition);
5059
5572
  });
5060
5573
  return { [COMMAND_PLAN_BRAND]: true, name, fragments };
5061
5574
  }
@@ -5375,320 +5888,148 @@ function descriptorKeyRecord(record, name, what) {
5375
5888
  }
5376
5889
  return out;
5377
5890
  }
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) {
5891
+ function isConditionOperatorObject4(value) {
5892
+ if (typeof value !== "object" || value === null || Array.isArray(value) || value instanceof Date || isParam(value) || isContractParamRef(value) || isContractKeyFieldRef(value)) {
5523
5893
  return false;
5524
5894
  }
5525
5895
  const keys = Object.keys(value);
5526
5896
  if (keys.length === 0) return false;
5527
- return keys.every((k) => OPERATOR_KEYS2.has(k));
5897
+ return keys.every((k) => CONDITION_OPERATOR_KEYS.has(k));
5528
5898
  }
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;
5899
+ function mintConditionOperand(leaf, field, op, index) {
5900
+ if (isParam(leaf)) return mintContractParamRef(conditionParamName(field, op, index));
5901
+ return leaf;
5535
5902
  }
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
- );
5903
+ function mintRawConditionParams(raw) {
5904
+ let paramIndex = 0;
5905
+ const parts = raw.parts.map((part) => {
5906
+ if (isColumn(part)) return part;
5907
+ if (isParam(part)) return mintContractParamRef(condParamName(paramIndex++));
5908
+ return part;
5909
+ });
5910
+ return { ...raw, parts };
5545
5911
  }
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;
5912
+ function descriptorConditionRecord(record, name) {
5913
+ if (isRawCondition(record)) {
5914
+ return mintRawConditionParams(record);
5915
+ }
5916
+ const top = record;
5917
+ if (top.notExists === true || typeof top.attributeExists === "string" || typeof top.attributeNotExists === "string") {
5918
+ return { ...top };
5919
+ }
5920
+ return descriptorConditionTree(top, name, `publicCommandModel: method '${name}' condition`);
5551
5921
  }
5552
- function buildTree(obj, context, renderTreeLeaf) {
5922
+ function descriptorConditionTree(obj, name, context) {
5553
5923
  const out = {};
5554
- for (const key of Object.keys(obj).sort()) {
5555
- const value = obj[key];
5924
+ for (const [key, value] of Object.entries(obj)) {
5556
5925
  if (value === void 0) continue;
5557
5926
  if (key === "and" || key === "or") {
5558
5927
  out[key] = value.map((sub) => {
5559
- assertNotNestedRaw(sub, context);
5560
- return buildTree(sub, context, renderTreeLeaf);
5928
+ assertNoNestedRawCond(sub, name);
5929
+ return descriptorConditionTree(sub, name, `${context} ${key}`);
5561
5930
  });
5562
5931
  continue;
5563
5932
  }
5564
5933
  if (key === "not") {
5565
- assertNotNestedRaw(value, context);
5566
- out[key] = buildTree(
5567
- value,
5568
- context,
5569
- renderTreeLeaf
5570
- );
5934
+ assertNoNestedRawCond(value, name);
5935
+ out[key] = descriptorConditionTree(value, name, `${context} not`);
5571
5936
  continue;
5572
5937
  }
5573
- if (!isOperatorObject2(value)) {
5574
- out[key] = { eq: renderTreeLeaf(value, conditionParamName(key, "eq")) };
5938
+ if (!isConditionOperatorObject4(value)) {
5939
+ out[key] = mintConditionOperand(value, key, "eq");
5575
5940
  continue;
5576
5941
  }
5577
5942
  const ops = value;
5578
5943
  const rendered = {};
5579
- for (const op of Object.keys(ops).sort()) {
5580
- const opVal = ops[op];
5944
+ for (const [op, opVal] of Object.entries(ops)) {
5581
5945
  if (op === "between") {
5582
5946
  const [lo, hi] = opVal;
5583
5947
  rendered[op] = [
5584
- renderTreeLeaf(lo, conditionParamName(key, op, 0)),
5585
- renderTreeLeaf(hi, conditionParamName(key, op, 1))
5948
+ mintConditionOperand(lo, key, op, 0),
5949
+ mintConditionOperand(hi, key, op, 1)
5586
5950
  ];
5587
5951
  } else if (op === "in") {
5588
- rendered[op] = opVal.map(
5589
- (v, i) => renderTreeLeaf(v, conditionParamName(key, op, i))
5590
- );
5591
- } else if (op === "attributeExists") {
5952
+ rendered[op] = opVal.map((v, i) => mintConditionOperand(v, key, op, i));
5953
+ } else if (op === "attributeExists" || op === "attributeType") {
5592
5954
  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
- );
5955
+ } else {
5956
+ rendered[op] = mintConditionOperand(opVal, key, op);
5625
5957
  }
5626
5958
  }
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;
5959
+ out[key] = rendered;
5636
5960
  }
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;
5961
+ return out;
5962
+ }
5963
+ function assertNoNestedRawCond(sub, name) {
5964
+ if (isRawCondition(sub)) {
5965
+ throw new Error(
5966
+ `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.`
5967
+ );
5645
5968
  }
5646
- throw new Error(
5647
- `Command '${commandName}' has an unsupported write condition.`
5648
- );
5649
5969
  }
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
- );
5970
+ function readDescriptorToOp(name, d) {
5971
+ const isQuery = d.query !== void 0;
5972
+ const isList = d.list !== void 0;
5973
+ if (isQuery === isList) {
5974
+ throw new Error(
5975
+ `publicQueryModel: method '${name}' must carry exactly one of \`query\` or \`list\` (the target model); it declared ${isQuery && isList ? "both" : "neither"}.`
5976
+ );
5669
5977
  }
5670
- if (Array.isArray(value)) {
5671
- value.forEach((v, i) => assertJsonSerializable(v, `${path}[${i}]`));
5672
- return;
5978
+ if (d.key === void 0 || d.select === void 0) {
5979
+ throw new Error(
5980
+ `publicQueryModel: the '${isQuery ? "query" : "list"}' descriptor for method '${name}' must declare both \`key\` and \`select\`.`
5981
+ );
5673
5982
  }
5674
- const proto = Object.getPrototypeOf(value);
5675
- if (proto !== Object.prototype && proto !== null) {
5983
+ const model = resolveDescriptorModel(isQuery ? d.query : d.list, name);
5984
+ const entity = entityRef(model);
5985
+ const operation = isQuery ? "query" : "list";
5986
+ const keyFieldNames2 = Object.keys(d.key);
5987
+ const keyRecord = descriptorKeyRecord(d.key, name, `${operation} key`);
5988
+ const { select, compose } = extractCompose({ ...d.select }, operation, entity.name);
5989
+ const keys = isQuery ? wholeKeysSentinel() : keyRecord;
5990
+ return {
5991
+ __isContractMethodOp: true,
5992
+ entity,
5993
+ operation,
5994
+ keys,
5995
+ ...isQuery ? { keyFields: keyFieldNames2 } : {},
5996
+ ...select !== void 0 ? { select } : {},
5997
+ ...compose.length > 0 ? { compose } : {},
5998
+ ...d.description !== void 0 ? { description: d.description } : {}
5999
+ };
6000
+ }
6001
+ function writeDescriptorToPlan(name, d) {
6002
+ const present = WRITE_INTENT_KEYS.filter((k) => d[k] !== void 0);
6003
+ if (present.length !== 1) {
5676
6004
  throw new Error(
5677
- `Bridge spec is not JSON-serializable: non-plain object (${value.constructor?.name ?? "unknown"}) at ${path}.`
6005
+ `publicCommandModel: method '${name}' must carry exactly one intent key (\`create\` / \`update\` / \`remove\`), but declared ${present.length === 0 ? "none" : present.join(", ")}.`
5678
6006
  );
5679
6007
  }
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}`);
6008
+ const intent = present[0];
6009
+ if (d.key === void 0 || d.key === null || typeof d.key !== "object") {
6010
+ throw new Error(
6011
+ `publicCommandModel: the '${intent}' descriptor for method '${name}' must declare a \`key\` object binding the target's primary-key fields.`
6012
+ );
5687
6013
  }
5688
- }
5689
- function assertBundleSerializable(bundle) {
5690
- assertJsonSerializable(bundle.manifest, "$.manifest");
5691
- assertJsonSerializable(bundle.operations, "$.operations");
6014
+ const model = resolveDescriptorModel(d[intent], name);
6015
+ const keyFieldNames2 = Object.keys(d.key);
6016
+ const inputFieldNames = d.input !== void 0 ? Object.keys(d.input) : [];
6017
+ const plan2 = mutation(name, ($) => {
6018
+ const refMap = (fields) => {
6019
+ const out = {};
6020
+ for (const f of fields) out[f] = $[f];
6021
+ return out;
6022
+ };
6023
+ const descriptor = {
6024
+ [intent]: (() => model),
6025
+ key: refMap(keyFieldNames2),
6026
+ ...inputFieldNames.length > 0 ? { input: refMap(inputFieldNames) } : {}
6027
+ };
6028
+ return { [name]: descriptor };
6029
+ });
6030
+ const select = d.result !== void 0 && d.result.select !== void 0 ? d.result.select : void 0;
6031
+ const condition = d.condition !== void 0 ? descriptorConditionRecord(d.condition, name) : void 0;
6032
+ return { plan: plan2, select, condition, mode: d.mode ?? "transaction", description: d.description };
5692
6033
  }
5693
6034
 
5694
6035
  // src/define/transaction.ts
@@ -6060,7 +6401,7 @@ function verifyWrite(instrs, surfaced) {
6060
6401
  verifyRecord(instrs.map((x) => x.item), surfaced, `${op} item`);
6061
6402
  verifyRecord(instrs.map((x) => x.key), surfaced, `${op} key`);
6062
6403
  verifyRecord(instrs.map((x) => x.changes), surfaced, `${op} changes`);
6063
- verifyRecord(
6404
+ verifyCondition(
6064
6405
  instrs.map((x) => x.condition),
6065
6406
  surfaced,
6066
6407
  `${op} condition`
@@ -6092,6 +6433,148 @@ function verifyRecord(records, surfaced, context) {
6092
6433
  );
6093
6434
  }
6094
6435
  }
6436
+ function isConditionOperatorObject5(value) {
6437
+ if (typeof value !== "object" || value === null || Array.isArray(value) || value instanceof Date || isTransactionRef(value)) {
6438
+ return false;
6439
+ }
6440
+ const keys = Object.keys(value);
6441
+ if (keys.length === 0) return false;
6442
+ return keys.every((k) => CONDITION_OPERATOR_KEYS.has(k));
6443
+ }
6444
+ function verifyCondition(conditions, surfaced, context) {
6445
+ const present = conditions.filter(
6446
+ (c) => c !== void 0 && c !== null
6447
+ );
6448
+ if (present.length === 0) return;
6449
+ if (present.length !== conditions.length) {
6450
+ throw new Error(
6451
+ `defineTransaction: ${context} present in some evaluation passes but not others; the callback must be a pure, declarative description.`
6452
+ );
6453
+ }
6454
+ if (present.some((c) => isRawCondition(c))) return;
6455
+ if (present.some((c) => typeof c !== "object" || Array.isArray(c))) {
6456
+ throw new Error(
6457
+ `defineTransaction: ${context} must be a declarative condition object (field clause / operator object / \`and\` / \`or\` / \`not\` group).`
6458
+ );
6459
+ }
6460
+ verifyConditionTree(
6461
+ present.map((c) => c),
6462
+ surfaced,
6463
+ context
6464
+ );
6465
+ }
6466
+ function verifyConditionTree(nodes, surfaced, context) {
6467
+ const keys = Object.keys(nodes[0]).sort();
6468
+ for (const n of nodes) {
6469
+ const k = Object.keys(n).sort();
6470
+ if (k.length !== keys.length || k.some((x, i) => x !== keys[i])) {
6471
+ throw new Error(
6472
+ `defineTransaction: ${context} key set diverged between evaluation passes; the callback must be a pure, declarative description.`
6473
+ );
6474
+ }
6475
+ }
6476
+ for (const key of keys) {
6477
+ const values = nodes.map((n) => n[key]);
6478
+ if (key === "and" || key === "or") {
6479
+ const arrays = values.map((v) => {
6480
+ if (!Array.isArray(v) || v.length === 0) {
6481
+ throw new Error(
6482
+ `defineTransaction: ${context} \`${key}\` must be a non-empty array of sub-conditions.`
6483
+ );
6484
+ }
6485
+ return v;
6486
+ });
6487
+ const len = arrays[0].length;
6488
+ if (arrays.some((a) => a.length !== len)) {
6489
+ throw new Error(
6490
+ `defineTransaction: ${context} \`${key}\` length diverged between evaluation passes; the callback must be declarative.`
6491
+ );
6492
+ }
6493
+ for (let i = 0; i < len; i++) {
6494
+ verifyCondition(
6495
+ arrays.map((a) => a[i]),
6496
+ surfaced,
6497
+ `${context} ${key}[${i}]`
6498
+ );
6499
+ }
6500
+ continue;
6501
+ }
6502
+ if (key === "not") {
6503
+ verifyCondition(
6504
+ values,
6505
+ surfaced,
6506
+ `${context} not`
6507
+ );
6508
+ continue;
6509
+ }
6510
+ if (!values.every((v) => isConditionOperatorObject5(v))) {
6511
+ if (values.some((v) => isConditionOperatorObject5(v))) {
6512
+ throw new Error(
6513
+ `defineTransaction: ${context} field '${key}' is an operator object in some evaluation passes but a bare value in others; the callback must be declarative.`
6514
+ );
6515
+ }
6516
+ verifyLeaf(values, surfaced, `${context} field '${key}'`);
6517
+ continue;
6518
+ }
6519
+ verifyOperatorObject(
6520
+ values.map((v) => v),
6521
+ surfaced,
6522
+ `${context} field '${key}'`
6523
+ );
6524
+ }
6525
+ }
6526
+ function verifyOperatorObject(objs, surfaced, context) {
6527
+ const ops = Object.keys(objs[0]).sort();
6528
+ for (const o of objs) {
6529
+ const k = Object.keys(o).sort();
6530
+ if (k.length !== ops.length || k.some((x, i) => x !== ops[i])) {
6531
+ throw new Error(
6532
+ `defineTransaction: ${context} operator set diverged between evaluation passes; the callback must be a pure, declarative description.`
6533
+ );
6534
+ }
6535
+ }
6536
+ for (const op of ops) {
6537
+ const opVals = objs.map((o) => o[op]);
6538
+ if (op === "between") {
6539
+ const pairs = opVals.map((v) => {
6540
+ if (!Array.isArray(v) || v.length !== 2) {
6541
+ throw new Error(
6542
+ `defineTransaction: ${context} \`between\` takes a \`[lo, hi]\` pair.`
6543
+ );
6544
+ }
6545
+ return v;
6546
+ });
6547
+ verifyLeaf(pairs.map((p) => p[0]), surfaced, `${context} between[0]`);
6548
+ verifyLeaf(pairs.map((p) => p[1]), surfaced, `${context} between[1]`);
6549
+ } else if (op === "in") {
6550
+ const arrays = opVals.map((v) => {
6551
+ if (!Array.isArray(v) || v.length === 0) {
6552
+ throw new Error(
6553
+ `defineTransaction: ${context} \`in\` takes a non-empty array of operands.`
6554
+ );
6555
+ }
6556
+ return v;
6557
+ });
6558
+ const len = arrays[0].length;
6559
+ if (arrays.some((a) => a.length !== len)) {
6560
+ throw new Error(
6561
+ `defineTransaction: ${context} \`in\` length diverged between evaluation passes; the callback must be declarative.`
6562
+ );
6563
+ }
6564
+ for (let i = 0; i < len; i++) {
6565
+ verifyLeaf(arrays.map((a) => a[i]), surfaced, `${context} in[${i}]`);
6566
+ }
6567
+ } else if (op === "attributeExists" || op === "attributeType") {
6568
+ if (opVals.some((v) => isTransactionRef(v))) {
6569
+ throw new Error(
6570
+ `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.`
6571
+ );
6572
+ }
6573
+ } else {
6574
+ verifyLeaf(opVals, surfaced, `${context} ${op}`);
6575
+ }
6576
+ }
6577
+ }
6095
6578
  function verifyWhen(whens, surfaced, context) {
6096
6579
  const present = whens.filter((w) => w !== void 0);
6097
6580
  if (present.length === 0) return;
@@ -6204,10 +6687,19 @@ function conditionTreeLeaf(value, name, context) {
6204
6687
  `defineTransaction: ${context} condition operand must be a field reference (from \`p\` or a forEach element) or a concrete literal; got ${typeof value}.`
6205
6688
  );
6206
6689
  }
6207
- function templateRecord2(structure, context) {
6690
+ function valueLeaf(value, context) {
6691
+ if (isTransactionRef(value)) return value.token;
6692
+ if (value instanceof Date) return value.toISOString();
6693
+ if (typeof value === "number" || typeof value === "boolean") return value;
6694
+ if (typeof value === "string") return value;
6695
+ throw new Error(
6696
+ `defineTransaction: ${context} must be a field reference (from \`p\` or a forEach element) or a concrete literal; got ${typeof value}.`
6697
+ );
6698
+ }
6699
+ function valueRecord(structure, context) {
6208
6700
  const out = {};
6209
6701
  for (const field of Object.keys(structure).sort()) {
6210
- out[field] = leafTemplate(structure[field], `${context} field '${field}'`);
6702
+ out[field] = valueLeaf(structure[field], `${context} field '${field}'`);
6211
6703
  }
6212
6704
  return out;
6213
6705
  }
@@ -6280,7 +6772,7 @@ function buildWriteItem(instr, forEachSource, txName) {
6280
6772
  if (instr.operation === "put") {
6281
6773
  return {
6282
6774
  ...base,
6283
- item: templateRecord2(instr.item ?? {}, `${txName} put`)
6775
+ item: valueRecord(instr.item ?? {}, `${txName} put`)
6284
6776
  };
6285
6777
  }
6286
6778
  if (instr.operation === "delete") {
@@ -6304,7 +6796,7 @@ function buildWriteItem(instr, forEachSource, txName) {
6304
6796
  )
6305
6797
  };
6306
6798
  }
6307
- const changes = templateRecord2(instr.changes ?? {}, `${txName} update changes`);
6799
+ const changes = valueRecord(instr.changes ?? {}, `${txName} update changes`);
6308
6800
  injectTransactionGsiRederive(
6309
6801
  metadata,
6310
6802
  instr.key ?? {},
@@ -6493,8 +6985,8 @@ function recoverKind(metadata, fieldName) {
6493
6985
  function opToDefinition(contractName, methodName, op) {
6494
6986
  const metadata = MetadataRegistry.get(op.entity.modelClass);
6495
6987
  const params = {};
6496
- const place = (paramName, bindField) => {
6497
- const recovered = recoverKind(metadata, bindField);
6988
+ const place = (paramName, bindField, kindOverride) => {
6989
+ const recovered = kindOverride !== void 0 ? { kind: kindOverride } : recoverKind(metadata, bindField);
6498
6990
  params[paramName] = recovered.literals === void 0 ? { kind: recovered.kind, required: true } : { kind: recovered.kind, literals: recovered.literals, required: true };
6499
6991
  return recovered;
6500
6992
  };
@@ -6568,13 +7060,13 @@ function convertStructure(structure, place, context) {
6568
7060
  }
6569
7061
  return out;
6570
7062
  }
6571
- function convertLeaf(leaf, bindField, place, context) {
7063
+ function convertLeaf(leaf, bindField, place, context, kindOverride) {
6572
7064
  if (isContractParamRef(leaf)) {
6573
- place(leaf.field, bindField);
7065
+ place(leaf.field, bindField, kindOverride);
6574
7066
  return leaf;
6575
7067
  }
6576
7068
  if (isContractKeyFieldRef(leaf)) {
6577
- place(leaf.field, bindField);
7069
+ place(leaf.field, bindField, kindOverride);
6578
7070
  return leaf;
6579
7071
  }
6580
7072
  if (isConcreteLiteral3(leaf)) return leaf;
@@ -6582,24 +7074,118 @@ function convertLeaf(leaf, bindField, place, context) {
6582
7074
  `${context}: value is neither a param/key reference nor a concrete literal \u2014 it cannot be serialized as a declarative contract operation.`
6583
7075
  );
6584
7076
  }
7077
+ function nearestColumnFields(condition) {
7078
+ const parts = condition.parts;
7079
+ const columnAt = (i) => isColumn(parts[i]) ? parts[i].name : void 0;
7080
+ const out = [];
7081
+ for (let i = 0; i < parts.length; i++) {
7082
+ const part = parts[i];
7083
+ if (!isContractParamRef(part) && !isContractKeyFieldRef(part)) continue;
7084
+ let found;
7085
+ for (let d = 1; d < parts.length && found === void 0; d++) {
7086
+ found = columnAt(i - d) ?? columnAt(i + d);
7087
+ }
7088
+ out.push(found);
7089
+ }
7090
+ return out;
7091
+ }
6585
7092
  function convertCondition(condition, place, context) {
6586
- if (isRawCondition(condition)) return condition;
7093
+ if (isRawCondition(condition)) {
7094
+ const compareFieldForSlot = nearestColumnFields(condition);
7095
+ let slotIndex = 0;
7096
+ for (const part of condition.parts) {
7097
+ if (isContractParamRef(part) || isContractKeyFieldRef(part)) {
7098
+ const compareField = compareFieldForSlot[slotIndex];
7099
+ if (compareField === void 0) {
7100
+ throw new Error(
7101
+ `${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.`
7102
+ );
7103
+ }
7104
+ place(part.field, compareField);
7105
+ slotIndex++;
7106
+ }
7107
+ }
7108
+ return condition;
7109
+ }
6587
7110
  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 };
7111
+ const obj2 = condition;
7112
+ if (obj2.notExists === true) return { notExists: true };
7113
+ if (typeof obj2.attributeExists === "string") {
7114
+ return { attributeExists: obj2.attributeExists };
6592
7115
  }
6593
- if (typeof obj.attributeNotExists === "string") {
6594
- return { attributeNotExists: obj.attributeNotExists };
7116
+ if (typeof obj2.attributeNotExists === "string") {
7117
+ return { attributeNotExists: obj2.attributeNotExists };
6595
7118
  }
6596
7119
  }
7120
+ const obj = condition;
7121
+ if (usesConditionTree(obj)) {
7122
+ return convertConditionTree(obj, place, context);
7123
+ }
6597
7124
  const out = {};
6598
- for (const [field, leaf] of Object.entries(condition)) {
7125
+ for (const [field, leaf] of Object.entries(obj)) {
6599
7126
  out[field] = convertLeaf(leaf, field, place, `${context} field '${field}'`);
6600
7127
  }
6601
7128
  return out;
6602
7129
  }
7130
+ var CONDITION_LOGICAL_KEYS = /* @__PURE__ */ new Set(["and", "or", "not"]);
7131
+ function isConditionOperatorObject6(value) {
7132
+ if (typeof value !== "object" || value === null || Array.isArray(value) || value instanceof Date || isContractParamRef(value) || isContractKeyFieldRef(value)) {
7133
+ return false;
7134
+ }
7135
+ const keys = Object.keys(value);
7136
+ if (keys.length === 0) return false;
7137
+ return keys.every((k) => CONDITION_OPERATOR_KEYS.has(k));
7138
+ }
7139
+ function usesConditionTree(obj) {
7140
+ for (const [key, value] of Object.entries(obj)) {
7141
+ if (CONDITION_LOGICAL_KEYS.has(key)) return true;
7142
+ if (isConditionOperatorObject6(value)) return true;
7143
+ }
7144
+ return false;
7145
+ }
7146
+ function convertConditionTree(obj, place, context) {
7147
+ const out = {};
7148
+ for (const [key, value] of Object.entries(obj)) {
7149
+ if (value === void 0) continue;
7150
+ if (key === "and" || key === "or") {
7151
+ out[key] = value.map(
7152
+ (sub) => convertConditionTree(sub, place, `${context} ${key}`)
7153
+ );
7154
+ continue;
7155
+ }
7156
+ if (key === "not") {
7157
+ out[key] = convertConditionTree(value, place, `${context} not`);
7158
+ continue;
7159
+ }
7160
+ if (!isConditionOperatorObject6(value)) {
7161
+ out[key] = convertLeaf(value, key, place, `${context} field '${key}'`);
7162
+ continue;
7163
+ }
7164
+ const ops = value;
7165
+ const rendered = {};
7166
+ for (const [op, opVal] of Object.entries(ops)) {
7167
+ if (op === "between") {
7168
+ const [lo, hi] = opVal;
7169
+ rendered[op] = [
7170
+ convertLeaf(lo, key, place, `${context} field '${key}' between[0]`),
7171
+ convertLeaf(hi, key, place, `${context} field '${key}' between[1]`)
7172
+ ];
7173
+ } else if (op === "in") {
7174
+ rendered[op] = opVal.map(
7175
+ (v, i) => convertLeaf(v, key, place, `${context} field '${key}' in[${i}]`)
7176
+ );
7177
+ } else if (op === "attributeExists" || op === "attributeType") {
7178
+ rendered[op] = opVal;
7179
+ } else if (op === "size") {
7180
+ rendered[op] = convertLeaf(opVal, key, place, `${context} field '${key}' ${op}`, "number");
7181
+ } else {
7182
+ rendered[op] = convertLeaf(opVal, key, place, `${context} field '${key}' ${op}`);
7183
+ }
7184
+ }
7185
+ out[key] = rendered;
7186
+ }
7187
+ return out;
7188
+ }
6603
7189
  function assertBooleanProjection(select, context) {
6604
7190
  const out = {};
6605
7191
  for (const [field, leaf] of Object.entries(select)) {
@@ -6910,6 +7496,7 @@ var TEMPLATE_TOKEN_RE = /\{([^{}]+)\}/g;
6910
7496
  function collectTemplateParams(record, metadata, out) {
6911
7497
  if (record === void 0) return;
6912
7498
  for (const template of Object.values(record)) {
7499
+ if (typeof template !== "string") continue;
6913
7500
  for (const match of template.matchAll(TEMPLATE_TOKEN_RE)) {
6914
7501
  const token = match[1];
6915
7502
  const field = token.startsWith("old.") ? token.slice("old.".length) : token;
@@ -7788,7 +8375,7 @@ function buildCommandSpec(def) {
7788
8375
  const condition = extractCondition(def);
7789
8376
  const description = def.description !== void 0 ? { description: def.description } : {};
7790
8377
  if (def.operation === "put") {
7791
- const item = templateRecord3(def.key);
8378
+ const item = templateRecord2(def.key);
7792
8379
  return {
7793
8380
  type: "PutItem",
7794
8381
  tableName,
@@ -7816,7 +8403,7 @@ function buildCommandSpec(def) {
7816
8403
  entity,
7817
8404
  params,
7818
8405
  keyCondition: writeKeyCondition(metadata, def.key, entity),
7819
- changes: templateRecord3(def.changes),
8406
+ changes: templateRecord2(def.changes),
7820
8407
  ...condition ? { condition } : {},
7821
8408
  ...description
7822
8409
  };
@@ -7840,7 +8427,7 @@ function writeKeyCondition(metadata, key, entity) {
7840
8427
  if (sk !== void 0) out.SK = sk;
7841
8428
  return out;
7842
8429
  }
7843
- function templateRecord3(structure) {
8430
+ function templateRecord2(structure) {
7844
8431
  const out = {};
7845
8432
  for (const key of Object.keys(structure).sort()) {
7846
8433
  out[key] = templateLeaf(key, structure[key]);
@@ -8171,6 +8758,10 @@ export {
8171
8758
  MARKER_ROW_ENTITY,
8172
8759
  buildManifestEntity,
8173
8760
  buildManifest,
8761
+ conditionParamName,
8762
+ assertSupportedCondition,
8763
+ assertJsonSerializable,
8764
+ assertBundleSerializable,
8174
8765
  buildProjection,
8175
8766
  compileFilterExpression,
8176
8767
  evaluateFilter,
@@ -8238,10 +8829,6 @@ export {
8238
8829
  publicQueryModel,
8239
8830
  publicCommandModel,
8240
8831
  isPlannedCommandMethod,
8241
- conditionParamName,
8242
- assertSupportedCondition,
8243
- assertJsonSerializable,
8244
- assertBundleSerializable,
8245
8832
  isTransactionRef,
8246
8833
  when,
8247
8834
  defineTransaction,