graphddb 0.7.8 → 0.7.10

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,3 +1,32 @@
1
+ import {
2
+ SELECT_SPEC,
3
+ buildMaintenanceGraph,
4
+ buildProject,
5
+ buildRelation,
6
+ buildSubscribeHandler,
7
+ detectRelationFields,
8
+ getEntityWrites,
9
+ getImplicitKeyFields,
10
+ hydrate,
11
+ isEntityWritesDefinition,
12
+ isInlineSnapshotSpec,
13
+ isLifecycleContract,
14
+ isSelectBuilder,
15
+ lifecyclePhaseForIntent,
16
+ normalizeSelectSpec,
17
+ normalizeTopLevelSelect,
18
+ parseChange,
19
+ validateInlineSnapshotSelect
20
+ } from "./chunk-DFUKGU2Q.js";
21
+ import {
22
+ EXPR_VERSION,
23
+ MARKER_ROW_ENTITY,
24
+ SPEC_VERSION,
25
+ canonicalizeExpressionSpec,
26
+ isScpExprConditionInput,
27
+ isTransactionRef,
28
+ operationsSpecVersion
29
+ } from "./chunk-3UD3XIF2.js";
1
30
  import {
2
31
  BATCH_GET_MAX_KEYS,
3
32
  CONDITION_OPERATOR_KEYS,
@@ -10,12 +39,8 @@ import {
10
39
  attachModelClass,
11
40
  buildConditionExpression,
12
41
  buildDeleteInput,
13
- buildMaintenanceGraph,
14
- buildProject,
15
42
  buildPutInput,
16
43
  buildReadRuntime,
17
- buildRelation,
18
- buildSubscribeHandler,
19
44
  buildUpdateInput,
20
45
  buildWriteRuntime,
21
46
  captureWrite,
@@ -24,34 +49,21 @@ import {
24
49
  compileRawCondition,
25
50
  condParamName,
26
51
  ctxModelFor,
27
- detectRelationFields,
28
52
  execItemKeySignature,
29
53
  executeDelete,
30
54
  executePut,
31
55
  executeTransaction,
32
56
  executeUpdate,
33
- getEntityWrites,
34
- getImplicitKeyFields,
35
- hydrate,
36
- isEntityWritesDefinition,
37
- isInlineSnapshotSpec,
38
- isLifecycleContract,
39
57
  isParam,
40
58
  isRawCondition,
41
- isSelectBuilder,
42
- lifecyclePhaseForIntent,
43
- normalizeSelectSpec,
44
- normalizeTopLevelSelect,
45
59
  param,
46
- parseChange,
47
60
  pkTemplate,
48
61
  resolveModelClass,
49
62
  resolveSegmentedKey,
50
63
  serializeFieldValue,
51
64
  serializeRawCondition,
52
- skTemplate,
53
- validateInlineSnapshotSelect
54
- } from "./chunk-HFFIB77D.js";
65
+ skTemplate
66
+ } from "./chunk-3ZU2VW3L.js";
55
67
  import {
56
68
  TableMapping,
57
69
  createColumnMap,
@@ -68,10 +80,6 @@ function evaluateKey(segmented, scope, presentFields, nameOf = (f) => f, partial
68
80
  return { pk, sk: sk?.template };
69
81
  }
70
82
 
71
- // src/spec/types.ts
72
- var SPEC_VERSION = "1.1";
73
- var MARKER_ROW_ENTITY = "__marker__";
74
-
75
83
  // src/spec/manifest.ts
76
84
  var DYNAMO_TYPE_TO_FIELD_TYPE = {
77
85
  S: "string",
@@ -219,6 +227,18 @@ function conditionInputToSpec(condition, context, renderLeaf2, renderTreeLeaf, r
219
227
  );
220
228
  }
221
229
  const obj = condition;
230
+ if (isScpExprConditionInput(obj)) {
231
+ const siblings = Object.keys(obj).filter((k) => k !== "scpExpr");
232
+ if (siblings.length > 0) {
233
+ throw new Error(
234
+ `${context}: a \`scpExpr\` write condition must be the WHOLE condition object; it cannot be mixed with sibling clauses [${siblings.join(", ")}] (they would be silently dropped). Fold the other clauses into the expression itself (e.g. \`{and: [...]}\`), or keep the condition fully declarative.`
235
+ );
236
+ }
237
+ return {
238
+ kind: "scpExpr",
239
+ expression: canonicalizeExpressionSpec(obj.scpExpr, `${context} (scpExpr)`)
240
+ };
241
+ }
222
242
  if (obj.notExists === true) return { kind: "notExists" };
223
243
  if ("attributeExists" in obj && typeof obj.attributeExists === "string") {
224
244
  if (obj.attributeExists.length === 0) {
@@ -253,6 +273,13 @@ function assertNotNestedRaw(sub, context) {
253
273
  );
254
274
  }
255
275
  }
276
+ function assertNotNestedScpExpr(sub, context) {
277
+ if (sub !== null && typeof sub === "object" && !Array.isArray(sub) && "scpExpr" in sub) {
278
+ throw new Error(
279
+ `${context}: a \`scpExpr\` write condition may be used as a whole condition, but not nested inside an \`and\` / \`or\` / \`not\` group of a declarative condition tree. Lift the expression to the top level and express the group inside the expression (e.g. \`{and: [...]}\`), or keep the group fully declarative.`
280
+ );
281
+ }
282
+ }
256
283
  var LOGICAL_KEYS = /* @__PURE__ */ new Set(["and", "or", "not"]);
257
284
  var OPERATOR_KEYS = /* @__PURE__ */ new Set([
258
285
  "eq",
@@ -309,12 +336,14 @@ function buildTree(obj, context, renderTreeLeaf) {
309
336
  if (key === "and" || key === "or") {
310
337
  out[key] = value.map((sub) => {
311
338
  assertNotNestedRaw(sub, context);
339
+ assertNotNestedScpExpr(sub, context);
312
340
  return buildTree(sub, context, renderTreeLeaf);
313
341
  });
314
342
  continue;
315
343
  }
316
344
  if (key === "not") {
317
345
  assertNotNestedRaw(value, context);
346
+ assertNotNestedScpExpr(value, context);
318
347
  out[key] = buildTree(
319
348
  value,
320
349
  context,
@@ -395,6 +424,15 @@ function assertSupportedCondition(commandName, condition) {
395
424
  }
396
425
  return;
397
426
  }
427
+ if (condition.kind === "scpExpr") {
428
+ const expression = condition.expression;
429
+ if (!expression || expression.exprVersion !== EXPR_VERSION || expression.expr === void 0) {
430
+ throw new Error(
431
+ `Command '${commandName}' has a malformed scpExpr write condition \u2014 expected \`{ exprVersion: ${EXPR_VERSION}, expr: <node> }\`.`
432
+ );
433
+ }
434
+ return;
435
+ }
398
436
  throw new Error(
399
437
  `Command '${commandName}' has an unsupported write condition.`
400
438
  );
@@ -1754,6 +1792,154 @@ async function executeQueryInner(modelClass, key, selectSpec, options, mw) {
1754
1792
  return hydratedItem;
1755
1793
  }
1756
1794
 
1795
+ // src/runtime/read-plan.ts
1796
+ function bindSlots(slots, params) {
1797
+ const out = {};
1798
+ for (const [field, slot] of Object.entries(slots)) {
1799
+ out[field] = slot.kind === "param" ? params[slot.name] : slot.value;
1800
+ }
1801
+ return out;
1802
+ }
1803
+ function bindOptionalSlot(slot, params) {
1804
+ if (slot === void 0) return void 0;
1805
+ return slot.kind === "param" ? params[slot.name] : slot.value;
1806
+ }
1807
+ function executeCompiledReadRoute(route, params, options) {
1808
+ const key = bindSlots(route.keySlots, params);
1809
+ const opts = { ...options ?? {} };
1810
+ if (route.kind === "query") {
1811
+ if (route.consistentReadSlot !== void 0) {
1812
+ opts.consistentRead = bindOptionalSlot(route.consistentReadSlot, params);
1813
+ }
1814
+ if (route.maxDepth !== void 0) opts.maxDepth = route.maxDepth;
1815
+ return executeQuery(route.modelClass, key, route.select, opts);
1816
+ }
1817
+ if (route.limitSlot !== void 0) {
1818
+ opts.limit = bindOptionalSlot(route.limitSlot, params);
1819
+ }
1820
+ if (route.afterSlot !== void 0) {
1821
+ opts.after = bindOptionalSlot(route.afterSlot, params);
1822
+ }
1823
+ if (route.order !== void 0) opts.order = route.order;
1824
+ if (route.filter !== void 0) opts.filter = route.filter;
1825
+ return executeList(route.modelClass, key, route.select, opts);
1826
+ }
1827
+ var MODEL_CLASS_IDS = /* @__PURE__ */ new WeakMap();
1828
+ var nextModelClassId = 1;
1829
+ function modelClassIdentity(cls) {
1830
+ let id = MODEL_CLASS_IDS.get(cls);
1831
+ if (id === void 0) {
1832
+ id = nextModelClassId++;
1833
+ MODEL_CLASS_IDS.set(cls, id);
1834
+ }
1835
+ return `model#${id}`;
1836
+ }
1837
+ var BoundedLru = class {
1838
+ constructor(max) {
1839
+ this.max = max;
1840
+ }
1841
+ max;
1842
+ map = /* @__PURE__ */ new Map();
1843
+ get(key) {
1844
+ const v = this.map.get(key);
1845
+ if (v !== void 0) {
1846
+ this.map.delete(key);
1847
+ this.map.set(key, v);
1848
+ }
1849
+ return v;
1850
+ }
1851
+ set(key, value) {
1852
+ if (this.map.has(key)) this.map.delete(key);
1853
+ this.map.set(key, value);
1854
+ if (this.map.size > this.max) {
1855
+ const oldest = this.map.keys().next().value;
1856
+ if (oldest !== void 0) this.map.delete(oldest);
1857
+ }
1858
+ }
1859
+ get size() {
1860
+ return this.map.size;
1861
+ }
1862
+ clear() {
1863
+ this.map.clear();
1864
+ }
1865
+ };
1866
+ var ADHOC_READ_PLAN_CACHE_MAX = 256;
1867
+ var ADHOC_READ_PLAN_CACHE = new BoundedLru(ADHOC_READ_PLAN_CACHE_MAX);
1868
+ var adHocCompileCount = 0;
1869
+ function shapeToken(value) {
1870
+ if (value === null) return "null";
1871
+ const t = typeof value;
1872
+ if (t === "string") return JSON.stringify(value);
1873
+ if (t === "number" || t === "boolean") return String(value);
1874
+ if (t === "undefined") return "u";
1875
+ if (t === "bigint") return `n${String(value)}`;
1876
+ if (t === "function" || t === "symbol") return null;
1877
+ if (value instanceof Date) return `D${value.getTime()}`;
1878
+ if (Array.isArray(value)) {
1879
+ const parts2 = [];
1880
+ for (const v of value) {
1881
+ const p = shapeToken(v);
1882
+ if (p === null) return null;
1883
+ parts2.push(p);
1884
+ }
1885
+ return `[${parts2.join(",")}]`;
1886
+ }
1887
+ if (isSelectBuilder(value)) {
1888
+ const spec = value[SELECT_SPEC];
1889
+ const inner = shapeToken({
1890
+ select: spec.select,
1891
+ filter: spec.filter,
1892
+ limit: spec.limit,
1893
+ after: spec.after,
1894
+ order: spec.order
1895
+ });
1896
+ return inner === null ? null : `B${inner}`;
1897
+ }
1898
+ const proto = Object.getPrototypeOf(value);
1899
+ if (proto !== Object.prototype && proto !== null) return null;
1900
+ const rec = value;
1901
+ const parts = [];
1902
+ for (const k of Object.keys(rec).sort()) {
1903
+ const p = shapeToken(rec[k]);
1904
+ if (p === null) return null;
1905
+ parts.push(`${JSON.stringify(k)}:${p}`);
1906
+ }
1907
+ return `{${parts.join(",")}}`;
1908
+ }
1909
+ function adHocStructureKey(kind, modelClass, key, select) {
1910
+ const selectToken = shapeToken(select);
1911
+ if (selectToken === null) return null;
1912
+ const fields = Object.keys(key).map((f) => JSON.stringify(f)).join(",");
1913
+ return `${kind}|${modelClassIdentity(modelClass)}|${fields}|${selectToken}`;
1914
+ }
1915
+ function lowerAdHocReadRoute(kind, modelClass, key, select) {
1916
+ const cacheKey = adHocStructureKey(kind, modelClass, key, select);
1917
+ if (cacheKey !== null) {
1918
+ const cached = ADHOC_READ_PLAN_CACHE.get(cacheKey);
1919
+ if (cached !== void 0) return cached;
1920
+ }
1921
+ adHocCompileCount++;
1922
+ const keySlots = {};
1923
+ for (const field of Object.keys(key)) {
1924
+ keySlots[field] = { kind: "param", name: field };
1925
+ }
1926
+ const route = { alias: "$adhoc", kind, modelClass, select, keySlots };
1927
+ if (cacheKey !== null) ADHOC_READ_PLAN_CACHE.set(cacheKey, route);
1928
+ return route;
1929
+ }
1930
+ function runAdHoc(route, select, key, options) {
1931
+ const effective = route.select === select ? route : { ...route, select };
1932
+ return executeCompiledReadRoute(effective, key, options);
1933
+ }
1934
+ function executeAdHocQuery(modelClass, key, selectSpec, options) {
1935
+ const route = lowerAdHocReadRoute("query", modelClass, key, selectSpec);
1936
+ return runAdHoc(route, selectSpec, key, options);
1937
+ }
1938
+ function executeAdHocList(modelClass, key, selectSpec, options) {
1939
+ const route = lowerAdHocReadRoute("list", modelClass, key, selectSpec);
1940
+ return runAdHoc(route, selectSpec, key, options);
1941
+ }
1942
+
1757
1943
  // src/operations/explain.ts
1758
1944
  function executeExplain(modelClass, key, options) {
1759
1945
  const metadata = MetadataRegistry.get(modelClass);
@@ -2828,14 +3014,14 @@ function renderInputLeaf(leaf, isKeyFieldInPut, consumerIndex, consumerField, re
2828
3014
  if (isMutationInputRef(leaf)) {
2829
3015
  return isKeyFieldInPut ? mintContractKeyFieldRef(leaf.field) : mintContractParamRef(leaf.field);
2830
3016
  }
2831
- const entityRef3 = parseEntityRef(leaf);
2832
- if (entityRef3 !== void 0) {
3017
+ const entityRef2 = parseEntityRef(leaf);
3018
+ if (entityRef2 !== void 0) {
2833
3019
  if (resolveEntityRef === null) {
2834
3020
  throw new Error(
2835
- `mutation: the cross-fragment reference '${entityRef3.path}' is only valid in a MULTI-fragment mutation \u2014 it names a value produced by an earlier fragment, but this mutation has a single fragment (there is no fragment #${entityRef3.fragmentIndex} to read from). Bind '${consumerField}' to a \`$.field\` input reference or a literal instead.`
3021
+ `mutation: the cross-fragment reference '${entityRef2.path}' is only valid in a MULTI-fragment mutation \u2014 it names a value produced by an earlier fragment, but this mutation has a single fragment (there is no fragment #${entityRef2.fragmentIndex} to read from). Bind '${consumerField}' to a \`$.field\` input reference or a literal instead.`
2836
3022
  );
2837
3023
  }
2838
- return resolveEntityRef(entityRef3, consumerIndex, consumerField);
3024
+ return resolveEntityRef(entityRef2, consumerIndex, consumerField);
2839
3025
  }
2840
3026
  return leaf;
2841
3027
  }
@@ -3117,6 +3303,11 @@ function renderCondition(condition, key, params, context) {
3117
3303
  }
3118
3304
  if (typeof condition === "object" && condition !== null) {
3119
3305
  const obj = condition;
3306
+ if ("scpExpr" in obj) {
3307
+ throw new Error(
3308
+ `Contract runtime: ${context} \u2014 this runtime does not evaluate scpExpr write conditions yet (spec 1.2 expression vocabulary, issue #261); evaluation lands with Phase 3 S5 (#265). Refusing to execute the write with its condition ignored.`
3309
+ );
3310
+ }
3120
3311
  if (obj.notExists === true) return { notExists: true };
3121
3312
  if (typeof obj.attributeExists === "string") {
3122
3313
  return { attributeExists: obj.attributeExists };
@@ -4273,13 +4464,13 @@ async function executeReadRoute(alias, route) {
4273
4464
  );
4274
4465
  const opt = route.options ?? {};
4275
4466
  if (isQuery) {
4276
- return executeQuery(modelClass, route.key, route.select, {
4467
+ return executeAdHocQuery(modelClass, route.key, route.select, {
4277
4468
  consistentRead: opt.consistentRead,
4278
4469
  maxDepth: opt.maxDepth,
4279
4470
  context: opt.context
4280
4471
  });
4281
4472
  }
4282
- return executeList(modelClass, route.key, route.select, {
4473
+ return executeAdHocList(modelClass, route.key, route.select, {
4283
4474
  limit: opt.limit,
4284
4475
  after: opt.after,
4285
4476
  order: opt.order,
@@ -4514,17 +4705,6 @@ function recordSlots(binding, where) {
4514
4705
  }
4515
4706
  return { fields, slots };
4516
4707
  }
4517
- function bindSlots(slots, params) {
4518
- const out = {};
4519
- for (const [field, slot] of Object.entries(slots)) {
4520
- out[field] = slot.kind === "param" ? params[slot.name] : slot.value;
4521
- }
4522
- return out;
4523
- }
4524
- function bindOptionalSlot(slot, params) {
4525
- if (slot === void 0) return void 0;
4526
- return slot.kind === "param" ? params[slot.name] : slot.value;
4527
- }
4528
4708
  function recordOptionSlot(value, where) {
4529
4709
  if (value === void 0) return void 0;
4530
4710
  if (isPreparedParamRef(value)) return { kind: "param", name: value.name };
@@ -4584,23 +4764,7 @@ var PreparedReadStatement = class {
4584
4764
  return out;
4585
4765
  }
4586
4766
  runRoute(route, params, options) {
4587
- const key = bindSlots(route.keySlots, params);
4588
- const consistentRead = bindOptionalSlot(route.consistentReadSlot, params);
4589
- if (route.kind === "query") {
4590
- return executeQuery(route.modelClass, key, route.select, {
4591
- consistentRead,
4592
- maxDepth: route.maxDepth,
4593
- ...options.retry !== void 0 ? { retry: options.retry } : {},
4594
- ...options.context !== void 0 ? { context: options.context } : {}
4595
- });
4596
- }
4597
- const limit = bindOptionalSlot(route.limitSlot, params);
4598
- const after = bindOptionalSlot(route.afterSlot, params);
4599
- return executeList(route.modelClass, key, route.select, {
4600
- limit,
4601
- after,
4602
- order: route.order,
4603
- filter: route.filter,
4767
+ return executeCompiledReadRoute(route, params, {
4604
4768
  ...options.retry !== void 0 ? { retry: options.retry } : {},
4605
4769
  ...options.context !== void 0 ? { context: options.context } : {}
4606
4770
  });
@@ -4764,35 +4928,6 @@ function assertStaticTemplate(value, where) {
4764
4928
  }
4765
4929
  }
4766
4930
  var PREPARE_CACHE_MAX = 256;
4767
- var BoundedLru = class {
4768
- constructor(max) {
4769
- this.max = max;
4770
- }
4771
- max;
4772
- map = /* @__PURE__ */ new Map();
4773
- get(key) {
4774
- const v = this.map.get(key);
4775
- if (v !== void 0) {
4776
- this.map.delete(key);
4777
- this.map.set(key, v);
4778
- }
4779
- return v;
4780
- }
4781
- set(key, value) {
4782
- if (this.map.has(key)) this.map.delete(key);
4783
- this.map.set(key, value);
4784
- if (this.map.size > this.max) {
4785
- const oldest = this.map.keys().next().value;
4786
- if (oldest !== void 0) this.map.delete(oldest);
4787
- }
4788
- }
4789
- get size() {
4790
- return this.map.size;
4791
- }
4792
- clear() {
4793
- this.map.clear();
4794
- }
4795
- };
4796
4931
  var PREPARE_CACHE = new BoundedLru(PREPARE_CACHE_MAX);
4797
4932
  function structureKey(routes) {
4798
4933
  const parts = routes.map(({ alias, route }) => {
@@ -4808,18 +4943,11 @@ function structureKey(routes) {
4808
4943
  });
4809
4944
  return parts.join("||");
4810
4945
  }
4811
- var MODEL_IDENTITY_IDS = /* @__PURE__ */ new WeakMap();
4812
- var nextModelIdentityId = 1;
4813
4946
  function modelIdentity(ref, alias) {
4814
4947
  try {
4815
4948
  const model = resolveModelStatic(ref, alias);
4816
4949
  const cls = resolveModelClass(model);
4817
- let id = MODEL_IDENTITY_IDS.get(cls);
4818
- if (id === void 0) {
4819
- id = nextModelIdentityId++;
4820
- MODEL_IDENTITY_IDS.set(cls, id);
4821
- }
4822
- return `model#${id}`;
4950
+ return modelClassIdentity(cls);
4823
4951
  } catch {
4824
4952
  return "unresolved";
4825
4953
  }
@@ -5119,10 +5247,10 @@ var DDBModel = class {
5119
5247
  // method type — the public overloads (the only caller-visible surface)
5120
5248
  // are untouched.
5121
5249
  query: ((key, select, options) => {
5122
- return executeQuery(modelClass, key, select, options);
5250
+ return executeAdHocQuery(modelClass, key, select, options);
5123
5251
  }),
5124
5252
  list: ((key, select, options) => {
5125
- return executeList(modelClass, key, select, options);
5253
+ return executeAdHocList(modelClass, key, select, options);
5126
5254
  }),
5127
5255
  explain(key, options) {
5128
5256
  return executeExplain(
@@ -5946,618 +6074,6 @@ function writeDescriptorToPlan(name, d) {
5946
6074
  return { plan: plan2, select, condition, mode: d.mode ?? "transaction", description: d.description };
5947
6075
  }
5948
6076
 
5949
- // src/define/transaction.ts
5950
- var TX_REF_BRAND = /* @__PURE__ */ Symbol("graphddb:txref");
5951
- function isTransactionRef(value) {
5952
- return typeof value === "object" && value !== null && value[TX_REF_BRAND] === true;
5953
- }
5954
- var MARKER_TAGS = [
5955
- // pass 0: short, begins with 'A'.
5956
- "Axqv",
5957
- // pass 1: long, begins with 'Z'. The two tags share NO common prefix and NO
5958
- // common substring of length >= 2 (their alphabets are disjoint), and differ
5959
- // in length, so any fragment-extracting / length-dependent transform
5960
- // (`slice(0,n)`, `[i]`, `charAt`, `padStart`, `padEnd`) yields *different*
5961
- // fragments across the two passes -- the differential check then rejects it.
5962
- // (Convergence-to-constant escapes that even disjoint markers cannot separate
5963
- // -- `slice(0,0)` -> '' in both passes, `includes('x')` -> a constant boolean
5964
- // -- are caught instead by the per-access coercion ledger; see
5965
- // {@link makeFieldRef} and {@link defineTransaction}.)
5966
- "Zmnoprstuwy0123456789WIDEMARKERBODYFILLERZ"
5967
- ];
5968
- var MARKER_PASS_COUNT = MARKER_TAGS.length;
5969
- function markerValue(pass, field) {
5970
- const tag = MARKER_TAGS[pass];
5971
- return `${tag}::${field}::${tag}`;
5972
- }
5973
- function makeValueSentinel(fields, tokenFor, originFor, markerFor, containerOrigin, onAccess, onCoerce) {
5974
- const cache = /* @__PURE__ */ new Map();
5975
- return new Proxy(/* @__PURE__ */ Object.create(null), {
5976
- get(_t, key) {
5977
- if (typeof key === "symbol") {
5978
- if (key === Symbol.iterator || key === Symbol.asyncIterator || key === Symbol.toStringTag || typeof Symbol.for === "function" && (key === /* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom") || key.description === "nodejs.util.inspect.custom")) {
5979
- return void 0;
5980
- }
5981
- throw new Error(
5982
- `defineTransaction: unsupported symbol access on ${containerOrigin}. Only declarative field references are allowed; any JS transform / branching / coercion on a parameter is not representable as a declarative transaction template.`
5983
- );
5984
- }
5985
- if (!fields.has(key)) {
5986
- throw new Error(
5987
- `defineTransaction: ${containerOrigin} has no field '${key}'. Reference only declared fields; arbitrary property access (or any JS transform / branching on a parameter) is not representable as a declarative transaction template.`
5988
- );
5989
- }
5990
- onAccess(key);
5991
- let ref = cache.get(key);
5992
- if (!ref) {
5993
- ref = makeFieldRef(
5994
- tokenFor(key),
5995
- originFor(key),
5996
- markerFor(key),
5997
- () => onCoerce(key)
5998
- );
5999
- cache.set(key, ref);
6000
- }
6001
- return ref;
6002
- },
6003
- set() {
6004
- throw new Error(
6005
- `defineTransaction: cannot assign to a field of ${containerOrigin}.`
6006
- );
6007
- },
6008
- defineProperty() {
6009
- throw new Error(
6010
- `defineTransaction: cannot define a property on ${containerOrigin}.`
6011
- );
6012
- },
6013
- deleteProperty() {
6014
- throw new Error(
6015
- `defineTransaction: cannot delete a field of ${containerOrigin}.`
6016
- );
6017
- }
6018
- });
6019
- }
6020
- function makeFieldRef(token, origin, marker, onCoerce) {
6021
- const target = /* @__PURE__ */ Object.create(null);
6022
- const proxy = new Proxy(target, {
6023
- get(_t, key) {
6024
- if (key === TX_REF_BRAND) return true;
6025
- if (key === "token") return token;
6026
- if (key === "origin") return origin;
6027
- if (key === Symbol.toPrimitive) {
6028
- return (hint) => {
6029
- onCoerce();
6030
- return hint === "number" ? NaN : marker;
6031
- };
6032
- }
6033
- if (key === "toString" || key === Symbol.toStringTag) {
6034
- return () => {
6035
- onCoerce();
6036
- return marker;
6037
- };
6038
- }
6039
- if (key === "valueOf") {
6040
- return () => {
6041
- onCoerce();
6042
- return NaN;
6043
- };
6044
- }
6045
- if (key === Symbol.iterator || key === Symbol.asyncIterator || key === "then" || key === "constructor" || key === "prototype" || typeof key === "symbol" && typeof Symbol.for === "function" && (key === /* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom") || key.description === "nodejs.util.inspect.custom")) {
6046
- return void 0;
6047
- }
6048
- const name = typeof key === "symbol" ? key.toString() : String(key);
6049
- throw new Error(
6050
- `defineTransaction: unsupported operation '${name}' on ${origin}. The static planner (Python bridge) only supports direct field references (\`p.field\` / \`el.field\`) or concrete literals at value positions; any string transform, property access (e.g. \`.length\`, \`[i]\`, \`.toUpperCase()\`), arithmetic, or value branching on a parameter is not representable as a declarative transaction template.`
6051
- );
6052
- },
6053
- set() {
6054
- throw new Error(`defineTransaction: cannot assign on ${origin}.`);
6055
- },
6056
- defineProperty() {
6057
- throw new Error(`defineTransaction: cannot define a property on ${origin}.`);
6058
- },
6059
- deleteProperty() {
6060
- throw new Error(`defineTransaction: cannot delete on ${origin}.`);
6061
- }
6062
- });
6063
- return proxy;
6064
- }
6065
- var when = {
6066
- eq(left, right) {
6067
- assertRef(left, "when.eq");
6068
- return { op: "eq", left, right };
6069
- },
6070
- ne(left, right) {
6071
- assertRef(left, "when.ne");
6072
- return { op: "ne", left, right };
6073
- }
6074
- };
6075
- function assertRef(value, context) {
6076
- if (!isTransactionRef(value)) {
6077
- throw new Error(
6078
- `defineTransaction: ${context} expects a field reference (e.g. \`p.status\` or an element field) on its left-hand side.`
6079
- );
6080
- }
6081
- }
6082
- function entityRef2(model) {
6083
- const modelClass = resolveModelClass(model);
6084
- return { name: modelClass.name, modelClass };
6085
- }
6086
- function descriptorOf(p) {
6087
- if (p.kind === "array") {
6088
- const element = {};
6089
- for (const [field, fieldParam] of Object.entries(p.element ?? {})) {
6090
- element[field] = descriptorOf(fieldParam);
6091
- }
6092
- return { kind: "array", required: true, element };
6093
- }
6094
- return p.literals === void 0 ? { kind: p.kind, required: true } : { kind: p.kind, literals: p.literals, required: true };
6095
- }
6096
- function runBuildPass(params, build, scalarFields, arrayParams, pass, accessed, coerced) {
6097
- const noteAccess = (token, origin) => {
6098
- if (!accessed.has(token)) accessed.set(token, { token, origin });
6099
- };
6100
- const noteCoerce = (token, origin) => {
6101
- if (!coerced.has(token)) coerced.set(token, { token, origin });
6102
- };
6103
- const scalarSentinel = makeValueSentinel(
6104
- scalarFields,
6105
- (f) => `{${f}}`,
6106
- (f) => `param '${f}'`,
6107
- (f) => markerValue(pass, f),
6108
- "params",
6109
- (f) => noteAccess(`{${f}}`, `param '${f}'`),
6110
- (f) => noteCoerce(`{${f}}`, `param '${f}'`)
6111
- );
6112
- const pProxy = new Proxy(/* @__PURE__ */ Object.create(null), {
6113
- get(_t, key) {
6114
- if (typeof key === "symbol") {
6115
- if (key === Symbol.iterator || key === Symbol.asyncIterator || key === Symbol.toStringTag || typeof Symbol.for === "function" && (key === /* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom") || key.description === "nodejs.util.inspect.custom")) {
6116
- return void 0;
6117
- }
6118
- throw new Error("defineTransaction: unsupported symbol access on params.");
6119
- }
6120
- if (arrayParams.has(key)) return params[key];
6121
- if (scalarFields.has(key)) return scalarSentinel[key];
6122
- throw new Error(
6123
- `defineTransaction: unknown parameter '${key}'. Declare it in the params map passed to defineTransaction.`
6124
- );
6125
- }
6126
- });
6127
- const instructions = [];
6128
- let currentBody = instructions;
6129
- let insideForEach = false;
6130
- const recordWrite = (instr) => {
6131
- currentBody.push(instr);
6132
- };
6133
- const recorder2 = {
6134
- put(model, item, options) {
6135
- recordWrite({
6136
- kind: "write",
6137
- operation: "put",
6138
- entity: entityRef2(model),
6139
- item,
6140
- ...options?.condition !== void 0 ? { condition: options.condition } : {},
6141
- ...options?.when !== void 0 ? { when: options.when } : {}
6142
- });
6143
- },
6144
- update(model, key, changes, options) {
6145
- recordWrite({
6146
- kind: "write",
6147
- operation: "update",
6148
- entity: entityRef2(model),
6149
- key,
6150
- changes,
6151
- ...options?.condition !== void 0 ? { condition: options.condition } : {},
6152
- ...options?.when !== void 0 ? { when: options.when } : {}
6153
- });
6154
- },
6155
- delete(model, key, options) {
6156
- recordWrite({
6157
- kind: "write",
6158
- operation: "delete",
6159
- entity: entityRef2(model),
6160
- key,
6161
- ...options?.condition !== void 0 ? { condition: options.condition } : {},
6162
- ...options?.when !== void 0 ? { when: options.when } : {}
6163
- });
6164
- },
6165
- conditionCheck(model, key, options) {
6166
- if (options?.condition === void 0 || options.condition === null) {
6167
- throw new Error(
6168
- "defineTransaction: tx.conditionCheck requires a `condition` (the read-only assertion it makes); e.g. `{ condition: { attributeExists: 'PK' } }`."
6169
- );
6170
- }
6171
- recordWrite({
6172
- kind: "write",
6173
- operation: "conditionCheck",
6174
- entity: entityRef2(model),
6175
- key,
6176
- condition: options.condition,
6177
- ...options.when !== void 0 ? { when: options.when } : {}
6178
- });
6179
- },
6180
- forEach(source, body, options) {
6181
- if (insideForEach) {
6182
- throw new Error(
6183
- "defineTransaction: nested forEach is not supported; flatten the loop."
6184
- );
6185
- }
6186
- if (!isParam(source) || source.kind !== "array") {
6187
- throw new Error(
6188
- "defineTransaction: forEach source must be a `param.array(...)` parameter accessed via `p.<name>`."
6189
- );
6190
- }
6191
- const arrayParam = source;
6192
- const sourceName = arrayParams.size ? Object.keys(params).find((k) => params[k] === arrayParam) : void 0;
6193
- if (sourceName === void 0) {
6194
- throw new Error(
6195
- "defineTransaction: forEach source is not a declared array param."
6196
- );
6197
- }
6198
- const elementFields = new Set(Object.keys(arrayParam.element ?? {}));
6199
- const elementProxy = makeValueSentinel(
6200
- elementFields,
6201
- (f) => `{item.${f}}`,
6202
- (f) => `element field '${f}' of '${sourceName}'`,
6203
- (f) => markerValue(pass, `item.${f}`),
6204
- `element of '${sourceName}'`,
6205
- (f) => noteAccess(`{item.${f}}`, `element field '${f}' of '${sourceName}'`),
6206
- (f) => noteCoerce(`{item.${f}}`, `element field '${f}' of '${sourceName}'`)
6207
- );
6208
- const block = {
6209
- kind: "forEach",
6210
- source: sourceName,
6211
- ...options?.when !== void 0 ? { when: options.when } : {},
6212
- body: []
6213
- };
6214
- const prevBody = currentBody;
6215
- currentBody = block.body;
6216
- insideForEach = true;
6217
- try {
6218
- body(elementProxy);
6219
- } finally {
6220
- insideForEach = false;
6221
- currentBody = prevBody;
6222
- }
6223
- currentBody.push(block);
6224
- }
6225
- };
6226
- build(recorder2, pProxy);
6227
- return instructions;
6228
- }
6229
- function defineTransaction(params, build) {
6230
- const descriptors = {};
6231
- for (const [name, value] of Object.entries(params)) {
6232
- if (!isParam(value)) {
6233
- throw new Error(
6234
- `defineTransaction: param '${name}' is not a param.* placeholder.`
6235
- );
6236
- }
6237
- descriptors[name] = descriptorOf(value);
6238
- }
6239
- const scalarFields = new Set(
6240
- Object.keys(params).filter((k) => params[k].kind !== "array")
6241
- );
6242
- const arrayParams = new Set(
6243
- Object.keys(params).filter((k) => params[k].kind === "array")
6244
- );
6245
- const accessed = /* @__PURE__ */ new Map();
6246
- const coerced = /* @__PURE__ */ new Map();
6247
- const passes = [];
6248
- for (let pass = 0; pass < MARKER_PASS_COUNT; pass++) {
6249
- passes.push(
6250
- runBuildPass(
6251
- params,
6252
- build,
6253
- scalarFields,
6254
- arrayParams,
6255
- pass,
6256
- accessed,
6257
- coerced
6258
- )
6259
- );
6260
- }
6261
- if (coerced.size > 0) {
6262
- const info = [...coerced.values()][0];
6263
- throw new Error(
6264
- `defineTransaction: ${info.origin} is coerced to a primitive (e.g. via \`String(${info.origin.includes("element") ? "el" : "p"}.x)\`, a template literal \`\${\u2026}\`, string concatenation, or a comparison) and then consumed by a transform or branch \u2014 for example \`String(p.x).slice(0, n)\`, \`String(p.x)[0]\`, \`String(p.x).includes('\u2026')\`, or \`\${p.x}-suffix\`. At a value position the static planner (Python bridge) only supports a *direct* field reference (stored without coercion) or a concrete literal; a coerced / transformed parameter is not representable as a declarative transaction template, and (because the transform may converge to a constant or a shared marker fragment) could otherwise be silently emitted as a wrong spec. Reference the field directly (\`p.x\` / \`el.x\`) or use a literal.`
6265
- );
6266
- }
6267
- const surfaced = /* @__PURE__ */ new Set();
6268
- verifyInstructions(passes, surfaced);
6269
- for (const [token, info] of accessed) {
6270
- if (!surfaced.has(token)) {
6271
- throw new Error(
6272
- `defineTransaction: ${info.origin} is accessed but never appears as a field reference in the recorded transaction. This happens when a parameter is consumed by a value branch (e.g. \`p.role === 'admin' ? 'A' : 'B'\`) or a string transform (e.g. \`String(p.role).toUpperCase()\`) instead of being referenced directly. The static planner (Python bridge) only supports direct field references or concrete literals at value positions; branching or transforming a parameter is not representable as a declarative transaction template.`
6273
- );
6274
- }
6275
- }
6276
- return {
6277
- __isTransactionDefinition: true,
6278
- params: descriptors,
6279
- instructions: passes[0]
6280
- };
6281
- }
6282
- function verifyInstructions(passes, surfaced) {
6283
- const first = passes[0];
6284
- for (let i = 0; i < first.length; i++) {
6285
- const instrs = passes.map((p) => p[i]);
6286
- const kind = first[i].kind;
6287
- if (instrs.some((x) => x.kind !== kind)) {
6288
- throw new Error(
6289
- "defineTransaction: build is not deterministic across evaluation (instruction shape diverged between passes). The callback must be a pure, declarative description of the writes."
6290
- );
6291
- }
6292
- if (kind === "write") {
6293
- verifyWrite(instrs, surfaced);
6294
- } else {
6295
- const blocks = instrs;
6296
- verifyWhen(
6297
- blocks.map((b) => b.when),
6298
- surfaced,
6299
- `forEach '${blocks[0].source}' when`
6300
- );
6301
- verifyInstructions(
6302
- blocks.map((b) => b.body),
6303
- surfaced
6304
- );
6305
- }
6306
- }
6307
- }
6308
- function verifyWrite(instrs, surfaced) {
6309
- const op = instrs[0].operation;
6310
- if (instrs.some((x) => x.operation !== op)) {
6311
- throw new Error(
6312
- "defineTransaction: write operation diverged between evaluation passes; the callback must be a pure, declarative description of the writes."
6313
- );
6314
- }
6315
- verifyRecord(instrs.map((x) => x.item), surfaced, `${op} item`);
6316
- verifyRecord(instrs.map((x) => x.key), surfaced, `${op} key`);
6317
- verifyRecord(instrs.map((x) => x.changes), surfaced, `${op} changes`);
6318
- verifyCondition(
6319
- instrs.map((x) => x.condition),
6320
- surfaced,
6321
- `${op} condition`
6322
- );
6323
- verifyWhen(instrs.map((x) => x.when), surfaced, `${op} when`);
6324
- }
6325
- function verifyRecord(records, surfaced, context) {
6326
- const present = records.filter((r) => r !== void 0);
6327
- if (present.length === 0) return;
6328
- if (present.length !== records.length) {
6329
- throw new Error(
6330
- `defineTransaction: ${context} present in some evaluation passes but not others; the callback must be a pure, declarative description.`
6331
- );
6332
- }
6333
- const keys = Object.keys(present[0]).sort();
6334
- for (const r of present) {
6335
- const k = Object.keys(r).sort();
6336
- if (k.length !== keys.length || k.some((x, i) => x !== keys[i])) {
6337
- throw new Error(
6338
- `defineTransaction: ${context} field set diverged between evaluation passes; the callback must be a pure, declarative description.`
6339
- );
6340
- }
6341
- }
6342
- for (const field of keys) {
6343
- verifyLeaf(
6344
- present.map((r) => r[field]),
6345
- surfaced,
6346
- `${context} field '${field}'`
6347
- );
6348
- }
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
- }
6492
- function verifyWhen(whens, surfaced, context) {
6493
- const present = whens.filter((w) => w !== void 0);
6494
- if (present.length === 0) return;
6495
- if (present.length !== whens.length) {
6496
- throw new Error(
6497
- `defineTransaction: ${context} present in some evaluation passes but not others; the callback must be a pure, declarative description.`
6498
- );
6499
- }
6500
- for (const w of present) {
6501
- if (isTransactionRef(w.left)) surfaced.add(w.left.token);
6502
- }
6503
- verifyLeaf(present.map((w) => w.right), surfaced, `${context} right-hand value`);
6504
- }
6505
- function verifyLeaf(values, surfaced, context) {
6506
- const refTokens = values.map((v) => isTransactionRef(v) ? v.token : void 0);
6507
- const anyRef = refTokens.some((t) => t !== void 0);
6508
- if (anyRef) {
6509
- if (refTokens.some((t) => t === void 0)) {
6510
- throw new Error(
6511
- `defineTransaction: ${context} is a field reference in some evaluation passes but a transformed value in others \u2014 a coercion/transform escape (e.g. \`String(p.x).toUpperCase()\`) was applied. Use a direct field reference or a concrete literal.`
6512
- );
6513
- }
6514
- const token = refTokens[0];
6515
- if (refTokens.some((t) => t !== token)) {
6516
- throw new Error(
6517
- `defineTransaction: ${context} resolves to different field references across evaluation passes \u2014 the value is not a stable, declarative reference.`
6518
- );
6519
- }
6520
- surfaced.add(token);
6521
- return;
6522
- }
6523
- const norm = values.map(normalizeLiteral);
6524
- for (let i = 1; i < norm.length; i++) {
6525
- if (norm[i] !== norm[0]) {
6526
- throw new Error(
6527
- `defineTransaction: ${context} is not a stable concrete literal \u2014 evaluating the callback twice produced different values ('${norm[0]}' vs '${norm[i]}'). This means a parameter was coerced or transformed into the value (e.g. \`String(p.x).toUpperCase()\`, \`\${p.x}-suffix\`, or arithmetic). The static planner (Python bridge) only supports a direct field reference or a concrete literal at a value position.`
6528
- );
6529
- }
6530
- }
6531
- }
6532
- function normalizeLiteral(value) {
6533
- if (value === null) return "t:null";
6534
- if (value instanceof Date) return `t:date:${value.toISOString()}`;
6535
- const t = typeof value;
6536
- if (t === "string") return `t:s:${value}`;
6537
- if (t === "boolean") return `t:b:${String(value)}`;
6538
- if (t === "number") {
6539
- if (Number.isNaN(value)) {
6540
- throw new Error(
6541
- "defineTransaction: a value leaf coerced to NaN, which means arithmetic was performed on a parameter. Only direct field references or concrete literals are allowed at value positions."
6542
- );
6543
- }
6544
- return `t:n:${String(value)}`;
6545
- }
6546
- throw new Error(
6547
- `defineTransaction: a value leaf has unsupported type '${t}'. Only a direct field reference (\`p.field\` / \`el.field\`) or a concrete literal (string / number / boolean / Date) is allowed at a value position.`
6548
- );
6549
- }
6550
- function defineTransactions(definitions) {
6551
- for (const [name, def] of Object.entries(definitions)) {
6552
- if (def === null || typeof def !== "object" || def.__isTransactionDefinition !== true) {
6553
- throw new Error(
6554
- `defineTransactions: '${name}' is not a transaction definition. Use defineTransaction(...) to build entries.`
6555
- );
6556
- }
6557
- }
6558
- return definitions;
6559
- }
6560
-
6561
6077
  // src/spec/transaction.ts
6562
6078
  function paramSpec(d) {
6563
6079
  if (d.kind === "array") {
@@ -6644,10 +6160,66 @@ function keyConditionTemplate2(metadata, keyStructure, context) {
6644
6160
  if (sk !== void 0) out.SK = sk;
6645
6161
  return out;
6646
6162
  }
6647
- function whenSpec(when2) {
6648
- const left = when2.left.token;
6649
- const right = leafTemplate(when2.right, "when right-hand value");
6650
- return { op: when2.op, left, right };
6163
+ function walkExpressionRefs(node, visit) {
6164
+ if (node === null || typeof node !== "object") return;
6165
+ if (Array.isArray(node)) {
6166
+ for (const el of node) walkExpressionRefs(el, visit);
6167
+ return;
6168
+ }
6169
+ const [key] = Object.keys(node);
6170
+ const arg = node[key];
6171
+ if (key === "ref" || key === "refOpt") {
6172
+ visit(arg);
6173
+ return;
6174
+ }
6175
+ if (key === "obj") {
6176
+ for (const v of Object.values(arg)) {
6177
+ walkExpressionRefs(v, visit);
6178
+ }
6179
+ return;
6180
+ }
6181
+ if (key === "int" || key === "float") return;
6182
+ walkExpressionRefs(arg, visit);
6183
+ }
6184
+ function assertGuardRefsBound(guard, params, forEachSource, context) {
6185
+ walkExpressionRefs(guard.expr, (path) => {
6186
+ const [scope, field] = path;
6187
+ const rendered = JSON.stringify(path);
6188
+ if (scope !== "input" && scope !== "item") {
6189
+ throw new Error(
6190
+ `${context}: guard reference ${rendered} uses unknown scope '${scope}' \u2014 expression references use the FIXED scope names 'input' (declared params) and 'item' (the forEach element), mirroring the {param} / {item.<field>} template scopes (issue #264).`
6191
+ );
6192
+ }
6193
+ if (field === void 0) {
6194
+ throw new Error(
6195
+ `${context}: guard reference ${rendered} names no field \u2014 a reference path is [scope, field, \u2026] (issue #264).`
6196
+ );
6197
+ }
6198
+ if (scope === "input") {
6199
+ if (params[field] === void 0) {
6200
+ throw new Error(
6201
+ `${context}: guard reference ${rendered} names undeclared input '${field}' \u2014 declared params: ${Object.keys(params).sort().map((p) => `'${p}'`).join(", ") || "(none)"}. Expression input refs and the ParamSpec names are one namespace (issue #264); an unbound reference is a build error, never a runtime leak (#243).`
6202
+ );
6203
+ }
6204
+ return;
6205
+ }
6206
+ if (forEachSource === void 0) {
6207
+ throw new Error(
6208
+ `${context}: guard reference ${rendered} uses the 'item' scope outside a forEach fan-out \u2014 an element reference only exists inside a forEach block (issue #264).`
6209
+ );
6210
+ }
6211
+ const element = params[forEachSource]?.element;
6212
+ if (element === void 0 || element[field] === void 0) {
6213
+ throw new Error(
6214
+ `${context}: guard reference ${rendered} names '${field}', which is not a declared element field of the forEach source '${forEachSource}' \u2014 declared element fields: ${Object.keys(element ?? {}).sort().map((f) => `'${f}'`).join(", ") || "(none)"} (issue #264 single namespace; #243: unbound references are build errors).`
6215
+ );
6216
+ }
6217
+ });
6218
+ }
6219
+ function whenSpec(when) {
6220
+ const left = when.left.token;
6221
+ const right = leafTemplate(when.right, "when right-hand value");
6222
+ return { op: when.op, left, right };
6651
6223
  }
6652
6224
  function conditionSpec(condition, commandName) {
6653
6225
  const spec = conditionInputToSpec(
@@ -6669,18 +6241,23 @@ var ITEM_TYPE = {
6669
6241
  delete: "Delete",
6670
6242
  conditionCheck: "ConditionCheck"
6671
6243
  };
6672
- function buildWriteItem(instr, forEachSource, txName) {
6244
+ function buildWriteItem(instr, forEachSource, txName, params) {
6673
6245
  const metadata = MetadataRegistry.get(instr.entity.modelClass);
6674
6246
  const tableName = TableMapping.resolve(metadata.tableName);
6675
6247
  const entity = instr.entity.name;
6676
6248
  const condition = conditionSpec(instr.condition, `${txName} (${entity})`);
6677
- const when2 = instr.when ? whenSpec(instr.when) : void 0;
6249
+ const when = instr.when ? whenSpec(instr.when) : void 0;
6250
+ const guard = instr.guard !== void 0 ? canonicalizeExpressionSpec(instr.guard, `${txName} (${entity}) guard`) : void 0;
6251
+ if (guard !== void 0) {
6252
+ assertGuardRefsBound(guard, params, forEachSource, `${txName} (${entity})`);
6253
+ }
6678
6254
  const base = {
6679
6255
  type: ITEM_TYPE[instr.operation],
6680
6256
  tableName,
6681
6257
  entity,
6682
6258
  ...condition ? { condition } : {},
6683
- ...when2 ? { when: when2 } : {},
6259
+ ...when ? { when } : {},
6260
+ ...guard ? { guard } : {},
6684
6261
  ...forEachSource ? { forEach: { source: forEachSource } } : {}
6685
6262
  };
6686
6263
  if (instr.operation === "put") {
@@ -6760,13 +6337,13 @@ function buildTransactionSpec(txName, def) {
6760
6337
  let hasForEach = false;
6761
6338
  for (const instr of def.instructions) {
6762
6339
  if (instr.kind === "write") {
6763
- items.push(buildWriteItem(instr, void 0, txName));
6340
+ items.push(buildWriteItem(instr, void 0, txName, def.params));
6764
6341
  } else {
6765
6342
  hasForEach = true;
6766
6343
  const block = instr;
6767
6344
  const blockWhen = block.when ? whenSpec(block.when) : void 0;
6768
6345
  for (const w of block.body) {
6769
- const item = buildWriteItem(w, block.source, txName);
6346
+ const item = buildWriteItem(w, block.source, txName, def.params);
6770
6347
  items.push(blockWhen && !item.when ? { ...item, when: blockWhen } : item);
6771
6348
  }
6772
6349
  }
@@ -7042,7 +6619,7 @@ function convertCondition(condition, place, context) {
7042
6619
  return out;
7043
6620
  }
7044
6621
  var CONDITION_LOGICAL_KEYS = /* @__PURE__ */ new Set(["and", "or", "not"]);
7045
- function isConditionOperatorObject6(value) {
6622
+ function isConditionOperatorObject5(value) {
7046
6623
  if (typeof value !== "object" || value === null || Array.isArray(value) || value instanceof Date || isContractParamRef(value) || isContractKeyFieldRef(value)) {
7047
6624
  return false;
7048
6625
  }
@@ -7053,7 +6630,7 @@ function isConditionOperatorObject6(value) {
7053
6630
  function usesConditionTree(obj) {
7054
6631
  for (const [key, value] of Object.entries(obj)) {
7055
6632
  if (CONDITION_LOGICAL_KEYS.has(key)) return true;
7056
- if (isConditionOperatorObject6(value)) return true;
6633
+ if (isConditionOperatorObject5(value)) return true;
7057
6634
  }
7058
6635
  return false;
7059
6636
  }
@@ -7071,7 +6648,7 @@ function convertConditionTree(obj, place, context) {
7071
6648
  out[key] = convertConditionTree(value, place, `${context} not`);
7072
6649
  continue;
7073
6650
  }
7074
- if (!isConditionOperatorObject6(value)) {
6651
+ if (!isConditionOperatorObject5(value)) {
7075
6652
  out[key] = convertLeaf(value, key, place, `${context} field '${key}'`);
7076
6653
  continue;
7077
6654
  }
@@ -8422,14 +7999,17 @@ function buildOperations(queries = {}, commands = {}, transactions = {}, contrac
8422
7999
  mergeSpecs(transactionSpecs, built.transactions, "transaction");
8423
8000
  const contractSpecs = built.contracts;
8424
8001
  const contextSpecs = buildContexts(contextInputMap);
8425
- return {
8426
- version: SPEC_VERSION,
8002
+ const content = {
8427
8003
  queries: querySpecs,
8428
8004
  commands: commandSpecs,
8429
8005
  ...Object.keys(transactionSpecs).length > 0 ? { transactions: transactionSpecs } : {},
8430
8006
  ...Object.keys(contractSpecs).length > 0 ? { contracts: contractSpecs } : {},
8431
8007
  ...Object.keys(contextSpecs).length > 0 ? { contexts: contextSpecs } : {}
8432
8008
  };
8009
+ return {
8010
+ version: operationsSpecVersion(content),
8011
+ ...content
8012
+ };
8433
8013
  }
8434
8014
 
8435
8015
  // src/spec/prepared-artifact.ts
@@ -8668,8 +8248,6 @@ function buildBridgeBundle(queries = {}, commands = {}, registry = MetadataRegis
8668
8248
  }
8669
8249
 
8670
8250
  export {
8671
- SPEC_VERSION,
8672
- MARKER_ROW_ENTITY,
8673
8251
  buildManifestEntity,
8674
8252
  buildManifest,
8675
8253
  conditionParamName,
@@ -8743,10 +8321,6 @@ export {
8743
8321
  publicQueryModel,
8744
8322
  publicCommandModel,
8745
8323
  isPlannedCommandMethod,
8746
- isTransactionRef,
8747
- when,
8748
- defineTransaction,
8749
- defineTransactions,
8750
8324
  buildTransactionSpec,
8751
8325
  buildTransactions,
8752
8326
  collectContractN1Violations,