graphddb 0.7.9 → 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,
@@ -6,17 +35,12 @@ import {
6
35
  MAX_TRANSACT_ITEMS,
7
36
  MetadataRegistry,
8
37
  NO_MIDDLEWARE,
9
- SELECT_SPEC,
10
38
  attachHiddenKey,
11
39
  attachModelClass,
12
40
  buildConditionExpression,
13
41
  buildDeleteInput,
14
- buildMaintenanceGraph,
15
- buildProject,
16
42
  buildPutInput,
17
43
  buildReadRuntime,
18
- buildRelation,
19
- buildSubscribeHandler,
20
44
  buildUpdateInput,
21
45
  buildWriteRuntime,
22
46
  captureWrite,
@@ -25,34 +49,21 @@ import {
25
49
  compileRawCondition,
26
50
  condParamName,
27
51
  ctxModelFor,
28
- detectRelationFields,
29
52
  execItemKeySignature,
30
53
  executeDelete,
31
54
  executePut,
32
55
  executeTransaction,
33
56
  executeUpdate,
34
- getEntityWrites,
35
- getImplicitKeyFields,
36
- hydrate,
37
- isEntityWritesDefinition,
38
- isInlineSnapshotSpec,
39
- isLifecycleContract,
40
57
  isParam,
41
58
  isRawCondition,
42
- isSelectBuilder,
43
- lifecyclePhaseForIntent,
44
- normalizeSelectSpec,
45
- normalizeTopLevelSelect,
46
59
  param,
47
- parseChange,
48
60
  pkTemplate,
49
61
  resolveModelClass,
50
62
  resolveSegmentedKey,
51
63
  serializeFieldValue,
52
64
  serializeRawCondition,
53
- skTemplate,
54
- validateInlineSnapshotSelect
55
- } from "./chunk-PFFPLD4B.js";
65
+ skTemplate
66
+ } from "./chunk-3ZU2VW3L.js";
56
67
  import {
57
68
  TableMapping,
58
69
  createColumnMap,
@@ -69,10 +80,6 @@ function evaluateKey(segmented, scope, presentFields, nameOf = (f) => f, partial
69
80
  return { pk, sk: sk?.template };
70
81
  }
71
82
 
72
- // src/spec/types.ts
73
- var SPEC_VERSION = "1.1";
74
- var MARKER_ROW_ENTITY = "__marker__";
75
-
76
83
  // src/spec/manifest.ts
77
84
  var DYNAMO_TYPE_TO_FIELD_TYPE = {
78
85
  S: "string",
@@ -220,6 +227,18 @@ function conditionInputToSpec(condition, context, renderLeaf2, renderTreeLeaf, r
220
227
  );
221
228
  }
222
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
+ }
223
242
  if (obj.notExists === true) return { kind: "notExists" };
224
243
  if ("attributeExists" in obj && typeof obj.attributeExists === "string") {
225
244
  if (obj.attributeExists.length === 0) {
@@ -254,6 +273,13 @@ function assertNotNestedRaw(sub, context) {
254
273
  );
255
274
  }
256
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
+ }
257
283
  var LOGICAL_KEYS = /* @__PURE__ */ new Set(["and", "or", "not"]);
258
284
  var OPERATOR_KEYS = /* @__PURE__ */ new Set([
259
285
  "eq",
@@ -310,12 +336,14 @@ function buildTree(obj, context, renderTreeLeaf) {
310
336
  if (key === "and" || key === "or") {
311
337
  out[key] = value.map((sub) => {
312
338
  assertNotNestedRaw(sub, context);
339
+ assertNotNestedScpExpr(sub, context);
313
340
  return buildTree(sub, context, renderTreeLeaf);
314
341
  });
315
342
  continue;
316
343
  }
317
344
  if (key === "not") {
318
345
  assertNotNestedRaw(value, context);
346
+ assertNotNestedScpExpr(value, context);
319
347
  out[key] = buildTree(
320
348
  value,
321
349
  context,
@@ -396,6 +424,15 @@ function assertSupportedCondition(commandName, condition) {
396
424
  }
397
425
  return;
398
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
+ }
399
436
  throw new Error(
400
437
  `Command '${commandName}' has an unsupported write condition.`
401
438
  );
@@ -2977,14 +3014,14 @@ function renderInputLeaf(leaf, isKeyFieldInPut, consumerIndex, consumerField, re
2977
3014
  if (isMutationInputRef(leaf)) {
2978
3015
  return isKeyFieldInPut ? mintContractKeyFieldRef(leaf.field) : mintContractParamRef(leaf.field);
2979
3016
  }
2980
- const entityRef3 = parseEntityRef(leaf);
2981
- if (entityRef3 !== void 0) {
3017
+ const entityRef2 = parseEntityRef(leaf);
3018
+ if (entityRef2 !== void 0) {
2982
3019
  if (resolveEntityRef === null) {
2983
3020
  throw new Error(
2984
- `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.`
2985
3022
  );
2986
3023
  }
2987
- return resolveEntityRef(entityRef3, consumerIndex, consumerField);
3024
+ return resolveEntityRef(entityRef2, consumerIndex, consumerField);
2988
3025
  }
2989
3026
  return leaf;
2990
3027
  }
@@ -3266,6 +3303,11 @@ function renderCondition(condition, key, params, context) {
3266
3303
  }
3267
3304
  if (typeof condition === "object" && condition !== null) {
3268
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
+ }
3269
3311
  if (obj.notExists === true) return { notExists: true };
3270
3312
  if (typeof obj.attributeExists === "string") {
3271
3313
  return { attributeExists: obj.attributeExists };
@@ -6032,618 +6074,6 @@ function writeDescriptorToPlan(name, d) {
6032
6074
  return { plan: plan2, select, condition, mode: d.mode ?? "transaction", description: d.description };
6033
6075
  }
6034
6076
 
6035
- // src/define/transaction.ts
6036
- var TX_REF_BRAND = /* @__PURE__ */ Symbol("graphddb:txref");
6037
- function isTransactionRef(value) {
6038
- return typeof value === "object" && value !== null && value[TX_REF_BRAND] === true;
6039
- }
6040
- var MARKER_TAGS = [
6041
- // pass 0: short, begins with 'A'.
6042
- "Axqv",
6043
- // pass 1: long, begins with 'Z'. The two tags share NO common prefix and NO
6044
- // common substring of length >= 2 (their alphabets are disjoint), and differ
6045
- // in length, so any fragment-extracting / length-dependent transform
6046
- // (`slice(0,n)`, `[i]`, `charAt`, `padStart`, `padEnd`) yields *different*
6047
- // fragments across the two passes -- the differential check then rejects it.
6048
- // (Convergence-to-constant escapes that even disjoint markers cannot separate
6049
- // -- `slice(0,0)` -> '' in both passes, `includes('x')` -> a constant boolean
6050
- // -- are caught instead by the per-access coercion ledger; see
6051
- // {@link makeFieldRef} and {@link defineTransaction}.)
6052
- "Zmnoprstuwy0123456789WIDEMARKERBODYFILLERZ"
6053
- ];
6054
- var MARKER_PASS_COUNT = MARKER_TAGS.length;
6055
- function markerValue(pass, field) {
6056
- const tag = MARKER_TAGS[pass];
6057
- return `${tag}::${field}::${tag}`;
6058
- }
6059
- function makeValueSentinel(fields, tokenFor, originFor, markerFor, containerOrigin, onAccess, onCoerce) {
6060
- const cache = /* @__PURE__ */ new Map();
6061
- return new Proxy(/* @__PURE__ */ Object.create(null), {
6062
- get(_t, key) {
6063
- if (typeof key === "symbol") {
6064
- 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")) {
6065
- return void 0;
6066
- }
6067
- throw new Error(
6068
- `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.`
6069
- );
6070
- }
6071
- if (!fields.has(key)) {
6072
- throw new Error(
6073
- `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.`
6074
- );
6075
- }
6076
- onAccess(key);
6077
- let ref = cache.get(key);
6078
- if (!ref) {
6079
- ref = makeFieldRef(
6080
- tokenFor(key),
6081
- originFor(key),
6082
- markerFor(key),
6083
- () => onCoerce(key)
6084
- );
6085
- cache.set(key, ref);
6086
- }
6087
- return ref;
6088
- },
6089
- set() {
6090
- throw new Error(
6091
- `defineTransaction: cannot assign to a field of ${containerOrigin}.`
6092
- );
6093
- },
6094
- defineProperty() {
6095
- throw new Error(
6096
- `defineTransaction: cannot define a property on ${containerOrigin}.`
6097
- );
6098
- },
6099
- deleteProperty() {
6100
- throw new Error(
6101
- `defineTransaction: cannot delete a field of ${containerOrigin}.`
6102
- );
6103
- }
6104
- });
6105
- }
6106
- function makeFieldRef(token, origin, marker, onCoerce) {
6107
- const target = /* @__PURE__ */ Object.create(null);
6108
- const proxy = new Proxy(target, {
6109
- get(_t, key) {
6110
- if (key === TX_REF_BRAND) return true;
6111
- if (key === "token") return token;
6112
- if (key === "origin") return origin;
6113
- if (key === Symbol.toPrimitive) {
6114
- return (hint) => {
6115
- onCoerce();
6116
- return hint === "number" ? NaN : marker;
6117
- };
6118
- }
6119
- if (key === "toString" || key === Symbol.toStringTag) {
6120
- return () => {
6121
- onCoerce();
6122
- return marker;
6123
- };
6124
- }
6125
- if (key === "valueOf") {
6126
- return () => {
6127
- onCoerce();
6128
- return NaN;
6129
- };
6130
- }
6131
- 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")) {
6132
- return void 0;
6133
- }
6134
- const name = typeof key === "symbol" ? key.toString() : String(key);
6135
- throw new Error(
6136
- `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.`
6137
- );
6138
- },
6139
- set() {
6140
- throw new Error(`defineTransaction: cannot assign on ${origin}.`);
6141
- },
6142
- defineProperty() {
6143
- throw new Error(`defineTransaction: cannot define a property on ${origin}.`);
6144
- },
6145
- deleteProperty() {
6146
- throw new Error(`defineTransaction: cannot delete on ${origin}.`);
6147
- }
6148
- });
6149
- return proxy;
6150
- }
6151
- var when = {
6152
- eq(left, right) {
6153
- assertRef(left, "when.eq");
6154
- return { op: "eq", left, right };
6155
- },
6156
- ne(left, right) {
6157
- assertRef(left, "when.ne");
6158
- return { op: "ne", left, right };
6159
- }
6160
- };
6161
- function assertRef(value, context) {
6162
- if (!isTransactionRef(value)) {
6163
- throw new Error(
6164
- `defineTransaction: ${context} expects a field reference (e.g. \`p.status\` or an element field) on its left-hand side.`
6165
- );
6166
- }
6167
- }
6168
- function entityRef2(model) {
6169
- const modelClass = resolveModelClass(model);
6170
- return { name: modelClass.name, modelClass };
6171
- }
6172
- function descriptorOf(p) {
6173
- if (p.kind === "array") {
6174
- const element = {};
6175
- for (const [field, fieldParam] of Object.entries(p.element ?? {})) {
6176
- element[field] = descriptorOf(fieldParam);
6177
- }
6178
- return { kind: "array", required: true, element };
6179
- }
6180
- return p.literals === void 0 ? { kind: p.kind, required: true } : { kind: p.kind, literals: p.literals, required: true };
6181
- }
6182
- function runBuildPass(params, build, scalarFields, arrayParams, pass, accessed, coerced) {
6183
- const noteAccess = (token, origin) => {
6184
- if (!accessed.has(token)) accessed.set(token, { token, origin });
6185
- };
6186
- const noteCoerce = (token, origin) => {
6187
- if (!coerced.has(token)) coerced.set(token, { token, origin });
6188
- };
6189
- const scalarSentinel = makeValueSentinel(
6190
- scalarFields,
6191
- (f) => `{${f}}`,
6192
- (f) => `param '${f}'`,
6193
- (f) => markerValue(pass, f),
6194
- "params",
6195
- (f) => noteAccess(`{${f}}`, `param '${f}'`),
6196
- (f) => noteCoerce(`{${f}}`, `param '${f}'`)
6197
- );
6198
- const pProxy = new Proxy(/* @__PURE__ */ Object.create(null), {
6199
- get(_t, key) {
6200
- if (typeof key === "symbol") {
6201
- 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")) {
6202
- return void 0;
6203
- }
6204
- throw new Error("defineTransaction: unsupported symbol access on params.");
6205
- }
6206
- if (arrayParams.has(key)) return params[key];
6207
- if (scalarFields.has(key)) return scalarSentinel[key];
6208
- throw new Error(
6209
- `defineTransaction: unknown parameter '${key}'. Declare it in the params map passed to defineTransaction.`
6210
- );
6211
- }
6212
- });
6213
- const instructions = [];
6214
- let currentBody = instructions;
6215
- let insideForEach = false;
6216
- const recordWrite = (instr) => {
6217
- currentBody.push(instr);
6218
- };
6219
- const recorder2 = {
6220
- put(model, item, options) {
6221
- recordWrite({
6222
- kind: "write",
6223
- operation: "put",
6224
- entity: entityRef2(model),
6225
- item,
6226
- ...options?.condition !== void 0 ? { condition: options.condition } : {},
6227
- ...options?.when !== void 0 ? { when: options.when } : {}
6228
- });
6229
- },
6230
- update(model, key, changes, options) {
6231
- recordWrite({
6232
- kind: "write",
6233
- operation: "update",
6234
- entity: entityRef2(model),
6235
- key,
6236
- changes,
6237
- ...options?.condition !== void 0 ? { condition: options.condition } : {},
6238
- ...options?.when !== void 0 ? { when: options.when } : {}
6239
- });
6240
- },
6241
- delete(model, key, options) {
6242
- recordWrite({
6243
- kind: "write",
6244
- operation: "delete",
6245
- entity: entityRef2(model),
6246
- key,
6247
- ...options?.condition !== void 0 ? { condition: options.condition } : {},
6248
- ...options?.when !== void 0 ? { when: options.when } : {}
6249
- });
6250
- },
6251
- conditionCheck(model, key, options) {
6252
- if (options?.condition === void 0 || options.condition === null) {
6253
- throw new Error(
6254
- "defineTransaction: tx.conditionCheck requires a `condition` (the read-only assertion it makes); e.g. `{ condition: { attributeExists: 'PK' } }`."
6255
- );
6256
- }
6257
- recordWrite({
6258
- kind: "write",
6259
- operation: "conditionCheck",
6260
- entity: entityRef2(model),
6261
- key,
6262
- condition: options.condition,
6263
- ...options.when !== void 0 ? { when: options.when } : {}
6264
- });
6265
- },
6266
- forEach(source, body, options) {
6267
- if (insideForEach) {
6268
- throw new Error(
6269
- "defineTransaction: nested forEach is not supported; flatten the loop."
6270
- );
6271
- }
6272
- if (!isParam(source) || source.kind !== "array") {
6273
- throw new Error(
6274
- "defineTransaction: forEach source must be a `param.array(...)` parameter accessed via `p.<name>`."
6275
- );
6276
- }
6277
- const arrayParam = source;
6278
- const sourceName = arrayParams.size ? Object.keys(params).find((k) => params[k] === arrayParam) : void 0;
6279
- if (sourceName === void 0) {
6280
- throw new Error(
6281
- "defineTransaction: forEach source is not a declared array param."
6282
- );
6283
- }
6284
- const elementFields = new Set(Object.keys(arrayParam.element ?? {}));
6285
- const elementProxy = makeValueSentinel(
6286
- elementFields,
6287
- (f) => `{item.${f}}`,
6288
- (f) => `element field '${f}' of '${sourceName}'`,
6289
- (f) => markerValue(pass, `item.${f}`),
6290
- `element of '${sourceName}'`,
6291
- (f) => noteAccess(`{item.${f}}`, `element field '${f}' of '${sourceName}'`),
6292
- (f) => noteCoerce(`{item.${f}}`, `element field '${f}' of '${sourceName}'`)
6293
- );
6294
- const block = {
6295
- kind: "forEach",
6296
- source: sourceName,
6297
- ...options?.when !== void 0 ? { when: options.when } : {},
6298
- body: []
6299
- };
6300
- const prevBody = currentBody;
6301
- currentBody = block.body;
6302
- insideForEach = true;
6303
- try {
6304
- body(elementProxy);
6305
- } finally {
6306
- insideForEach = false;
6307
- currentBody = prevBody;
6308
- }
6309
- currentBody.push(block);
6310
- }
6311
- };
6312
- build(recorder2, pProxy);
6313
- return instructions;
6314
- }
6315
- function defineTransaction(params, build) {
6316
- const descriptors = {};
6317
- for (const [name, value] of Object.entries(params)) {
6318
- if (!isParam(value)) {
6319
- throw new Error(
6320
- `defineTransaction: param '${name}' is not a param.* placeholder.`
6321
- );
6322
- }
6323
- descriptors[name] = descriptorOf(value);
6324
- }
6325
- const scalarFields = new Set(
6326
- Object.keys(params).filter((k) => params[k].kind !== "array")
6327
- );
6328
- const arrayParams = new Set(
6329
- Object.keys(params).filter((k) => params[k].kind === "array")
6330
- );
6331
- const accessed = /* @__PURE__ */ new Map();
6332
- const coerced = /* @__PURE__ */ new Map();
6333
- const passes = [];
6334
- for (let pass = 0; pass < MARKER_PASS_COUNT; pass++) {
6335
- passes.push(
6336
- runBuildPass(
6337
- params,
6338
- build,
6339
- scalarFields,
6340
- arrayParams,
6341
- pass,
6342
- accessed,
6343
- coerced
6344
- )
6345
- );
6346
- }
6347
- if (coerced.size > 0) {
6348
- const info = [...coerced.values()][0];
6349
- throw new Error(
6350
- `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.`
6351
- );
6352
- }
6353
- const surfaced = /* @__PURE__ */ new Set();
6354
- verifyInstructions(passes, surfaced);
6355
- for (const [token, info] of accessed) {
6356
- if (!surfaced.has(token)) {
6357
- throw new Error(
6358
- `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.`
6359
- );
6360
- }
6361
- }
6362
- return {
6363
- __isTransactionDefinition: true,
6364
- params: descriptors,
6365
- instructions: passes[0]
6366
- };
6367
- }
6368
- function verifyInstructions(passes, surfaced) {
6369
- const first = passes[0];
6370
- for (let i = 0; i < first.length; i++) {
6371
- const instrs = passes.map((p) => p[i]);
6372
- const kind = first[i].kind;
6373
- if (instrs.some((x) => x.kind !== kind)) {
6374
- throw new Error(
6375
- "defineTransaction: build is not deterministic across evaluation (instruction shape diverged between passes). The callback must be a pure, declarative description of the writes."
6376
- );
6377
- }
6378
- if (kind === "write") {
6379
- verifyWrite(instrs, surfaced);
6380
- } else {
6381
- const blocks = instrs;
6382
- verifyWhen(
6383
- blocks.map((b) => b.when),
6384
- surfaced,
6385
- `forEach '${blocks[0].source}' when`
6386
- );
6387
- verifyInstructions(
6388
- blocks.map((b) => b.body),
6389
- surfaced
6390
- );
6391
- }
6392
- }
6393
- }
6394
- function verifyWrite(instrs, surfaced) {
6395
- const op = instrs[0].operation;
6396
- if (instrs.some((x) => x.operation !== op)) {
6397
- throw new Error(
6398
- "defineTransaction: write operation diverged between evaluation passes; the callback must be a pure, declarative description of the writes."
6399
- );
6400
- }
6401
- verifyRecord(instrs.map((x) => x.item), surfaced, `${op} item`);
6402
- verifyRecord(instrs.map((x) => x.key), surfaced, `${op} key`);
6403
- verifyRecord(instrs.map((x) => x.changes), surfaced, `${op} changes`);
6404
- verifyCondition(
6405
- instrs.map((x) => x.condition),
6406
- surfaced,
6407
- `${op} condition`
6408
- );
6409
- verifyWhen(instrs.map((x) => x.when), surfaced, `${op} when`);
6410
- }
6411
- function verifyRecord(records, surfaced, context) {
6412
- const present = records.filter((r) => r !== void 0);
6413
- if (present.length === 0) return;
6414
- if (present.length !== records.length) {
6415
- throw new Error(
6416
- `defineTransaction: ${context} present in some evaluation passes but not others; the callback must be a pure, declarative description.`
6417
- );
6418
- }
6419
- const keys = Object.keys(present[0]).sort();
6420
- for (const r of present) {
6421
- const k = Object.keys(r).sort();
6422
- if (k.length !== keys.length || k.some((x, i) => x !== keys[i])) {
6423
- throw new Error(
6424
- `defineTransaction: ${context} field set diverged between evaluation passes; the callback must be a pure, declarative description.`
6425
- );
6426
- }
6427
- }
6428
- for (const field of keys) {
6429
- verifyLeaf(
6430
- present.map((r) => r[field]),
6431
- surfaced,
6432
- `${context} field '${field}'`
6433
- );
6434
- }
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
- }
6578
- function verifyWhen(whens, surfaced, context) {
6579
- const present = whens.filter((w) => w !== void 0);
6580
- if (present.length === 0) return;
6581
- if (present.length !== whens.length) {
6582
- throw new Error(
6583
- `defineTransaction: ${context} present in some evaluation passes but not others; the callback must be a pure, declarative description.`
6584
- );
6585
- }
6586
- for (const w of present) {
6587
- if (isTransactionRef(w.left)) surfaced.add(w.left.token);
6588
- }
6589
- verifyLeaf(present.map((w) => w.right), surfaced, `${context} right-hand value`);
6590
- }
6591
- function verifyLeaf(values, surfaced, context) {
6592
- const refTokens = values.map((v) => isTransactionRef(v) ? v.token : void 0);
6593
- const anyRef = refTokens.some((t) => t !== void 0);
6594
- if (anyRef) {
6595
- if (refTokens.some((t) => t === void 0)) {
6596
- throw new Error(
6597
- `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.`
6598
- );
6599
- }
6600
- const token = refTokens[0];
6601
- if (refTokens.some((t) => t !== token)) {
6602
- throw new Error(
6603
- `defineTransaction: ${context} resolves to different field references across evaluation passes \u2014 the value is not a stable, declarative reference.`
6604
- );
6605
- }
6606
- surfaced.add(token);
6607
- return;
6608
- }
6609
- const norm = values.map(normalizeLiteral);
6610
- for (let i = 1; i < norm.length; i++) {
6611
- if (norm[i] !== norm[0]) {
6612
- throw new Error(
6613
- `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.`
6614
- );
6615
- }
6616
- }
6617
- }
6618
- function normalizeLiteral(value) {
6619
- if (value === null) return "t:null";
6620
- if (value instanceof Date) return `t:date:${value.toISOString()}`;
6621
- const t = typeof value;
6622
- if (t === "string") return `t:s:${value}`;
6623
- if (t === "boolean") return `t:b:${String(value)}`;
6624
- if (t === "number") {
6625
- if (Number.isNaN(value)) {
6626
- throw new Error(
6627
- "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."
6628
- );
6629
- }
6630
- return `t:n:${String(value)}`;
6631
- }
6632
- throw new Error(
6633
- `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.`
6634
- );
6635
- }
6636
- function defineTransactions(definitions) {
6637
- for (const [name, def] of Object.entries(definitions)) {
6638
- if (def === null || typeof def !== "object" || def.__isTransactionDefinition !== true) {
6639
- throw new Error(
6640
- `defineTransactions: '${name}' is not a transaction definition. Use defineTransaction(...) to build entries.`
6641
- );
6642
- }
6643
- }
6644
- return definitions;
6645
- }
6646
-
6647
6077
  // src/spec/transaction.ts
6648
6078
  function paramSpec(d) {
6649
6079
  if (d.kind === "array") {
@@ -6730,10 +6160,66 @@ function keyConditionTemplate2(metadata, keyStructure, context) {
6730
6160
  if (sk !== void 0) out.SK = sk;
6731
6161
  return out;
6732
6162
  }
6733
- function whenSpec(when2) {
6734
- const left = when2.left.token;
6735
- const right = leafTemplate(when2.right, "when right-hand value");
6736
- 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 };
6737
6223
  }
6738
6224
  function conditionSpec(condition, commandName) {
6739
6225
  const spec = conditionInputToSpec(
@@ -6755,18 +6241,23 @@ var ITEM_TYPE = {
6755
6241
  delete: "Delete",
6756
6242
  conditionCheck: "ConditionCheck"
6757
6243
  };
6758
- function buildWriteItem(instr, forEachSource, txName) {
6244
+ function buildWriteItem(instr, forEachSource, txName, params) {
6759
6245
  const metadata = MetadataRegistry.get(instr.entity.modelClass);
6760
6246
  const tableName = TableMapping.resolve(metadata.tableName);
6761
6247
  const entity = instr.entity.name;
6762
6248
  const condition = conditionSpec(instr.condition, `${txName} (${entity})`);
6763
- 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
+ }
6764
6254
  const base = {
6765
6255
  type: ITEM_TYPE[instr.operation],
6766
6256
  tableName,
6767
6257
  entity,
6768
6258
  ...condition ? { condition } : {},
6769
- ...when2 ? { when: when2 } : {},
6259
+ ...when ? { when } : {},
6260
+ ...guard ? { guard } : {},
6770
6261
  ...forEachSource ? { forEach: { source: forEachSource } } : {}
6771
6262
  };
6772
6263
  if (instr.operation === "put") {
@@ -6846,13 +6337,13 @@ function buildTransactionSpec(txName, def) {
6846
6337
  let hasForEach = false;
6847
6338
  for (const instr of def.instructions) {
6848
6339
  if (instr.kind === "write") {
6849
- items.push(buildWriteItem(instr, void 0, txName));
6340
+ items.push(buildWriteItem(instr, void 0, txName, def.params));
6850
6341
  } else {
6851
6342
  hasForEach = true;
6852
6343
  const block = instr;
6853
6344
  const blockWhen = block.when ? whenSpec(block.when) : void 0;
6854
6345
  for (const w of block.body) {
6855
- const item = buildWriteItem(w, block.source, txName);
6346
+ const item = buildWriteItem(w, block.source, txName, def.params);
6856
6347
  items.push(blockWhen && !item.when ? { ...item, when: blockWhen } : item);
6857
6348
  }
6858
6349
  }
@@ -7128,7 +6619,7 @@ function convertCondition(condition, place, context) {
7128
6619
  return out;
7129
6620
  }
7130
6621
  var CONDITION_LOGICAL_KEYS = /* @__PURE__ */ new Set(["and", "or", "not"]);
7131
- function isConditionOperatorObject6(value) {
6622
+ function isConditionOperatorObject5(value) {
7132
6623
  if (typeof value !== "object" || value === null || Array.isArray(value) || value instanceof Date || isContractParamRef(value) || isContractKeyFieldRef(value)) {
7133
6624
  return false;
7134
6625
  }
@@ -7139,7 +6630,7 @@ function isConditionOperatorObject6(value) {
7139
6630
  function usesConditionTree(obj) {
7140
6631
  for (const [key, value] of Object.entries(obj)) {
7141
6632
  if (CONDITION_LOGICAL_KEYS.has(key)) return true;
7142
- if (isConditionOperatorObject6(value)) return true;
6633
+ if (isConditionOperatorObject5(value)) return true;
7143
6634
  }
7144
6635
  return false;
7145
6636
  }
@@ -7157,7 +6648,7 @@ function convertConditionTree(obj, place, context) {
7157
6648
  out[key] = convertConditionTree(value, place, `${context} not`);
7158
6649
  continue;
7159
6650
  }
7160
- if (!isConditionOperatorObject6(value)) {
6651
+ if (!isConditionOperatorObject5(value)) {
7161
6652
  out[key] = convertLeaf(value, key, place, `${context} field '${key}'`);
7162
6653
  continue;
7163
6654
  }
@@ -8508,14 +7999,17 @@ function buildOperations(queries = {}, commands = {}, transactions = {}, contrac
8508
7999
  mergeSpecs(transactionSpecs, built.transactions, "transaction");
8509
8000
  const contractSpecs = built.contracts;
8510
8001
  const contextSpecs = buildContexts(contextInputMap);
8511
- return {
8512
- version: SPEC_VERSION,
8002
+ const content = {
8513
8003
  queries: querySpecs,
8514
8004
  commands: commandSpecs,
8515
8005
  ...Object.keys(transactionSpecs).length > 0 ? { transactions: transactionSpecs } : {},
8516
8006
  ...Object.keys(contractSpecs).length > 0 ? { contracts: contractSpecs } : {},
8517
8007
  ...Object.keys(contextSpecs).length > 0 ? { contexts: contextSpecs } : {}
8518
8008
  };
8009
+ return {
8010
+ version: operationsSpecVersion(content),
8011
+ ...content
8012
+ };
8519
8013
  }
8520
8014
 
8521
8015
  // src/spec/prepared-artifact.ts
@@ -8754,8 +8248,6 @@ function buildBridgeBundle(queries = {}, commands = {}, registry = MetadataRegis
8754
8248
  }
8755
8249
 
8756
8250
  export {
8757
- SPEC_VERSION,
8758
- MARKER_ROW_ENTITY,
8759
8251
  buildManifestEntity,
8760
8252
  buildManifest,
8761
8253
  conditionParamName,
@@ -8829,10 +8321,6 @@ export {
8829
8321
  publicQueryModel,
8830
8322
  publicCommandModel,
8831
8323
  isPlannedCommandMethod,
8832
- isTransactionRef,
8833
- when,
8834
- defineTransaction,
8835
- defineTransactions,
8836
8324
  buildTransactionSpec,
8837
8325
  buildTransactions,
8838
8326
  collectContractN1Violations,