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.
@@ -0,0 +1,860 @@
1
+ import {
2
+ CONDITION_OPERATOR_KEYS,
3
+ isParam,
4
+ isRawCondition,
5
+ resolveModelClass
6
+ } from "./chunk-3ZU2VW3L.js";
7
+
8
+ // src/spec/types.ts
9
+ var SPEC_VERSION = "1.1";
10
+ var SPEC_VERSION_SCP = "1.2";
11
+ var SPEC_VERSION_SUPPORTED = SPEC_VERSION_SCP;
12
+ var EXPR_VERSION = 1;
13
+ var MARKER_ROW_ENTITY = "__marker__";
14
+
15
+ // src/spec/expression.ts
16
+ var UNARY_OPS = /* @__PURE__ */ new Set(["neg", "not", "len"]);
17
+ var BINARY_OPS = /* @__PURE__ */ new Set([
18
+ "add",
19
+ "sub",
20
+ "mul",
21
+ "div",
22
+ "mod",
23
+ "concat",
24
+ "eq",
25
+ "ne",
26
+ "lt",
27
+ "le",
28
+ "gt",
29
+ "ge",
30
+ "and",
31
+ "or",
32
+ "coalesce"
33
+ ]);
34
+ var TERNARY_OPS = /* @__PURE__ */ new Set(["cond"]);
35
+ var ALL_OPS = /* @__PURE__ */ new Set([...UNARY_OPS, ...BINARY_OPS, ...TERNARY_OPS]);
36
+ var I64_MIN = -(2n ** 63n);
37
+ var I64_MAX = 2n ** 63n - 1n;
38
+ function cmpCodePoints(a, b) {
39
+ const ia = a[Symbol.iterator]();
40
+ const ib = b[Symbol.iterator]();
41
+ for (; ; ) {
42
+ const ra = ia.next();
43
+ const rb = ib.next();
44
+ if (ra.done && rb.done) return 0;
45
+ if (ra.done) return -1;
46
+ if (rb.done) return 1;
47
+ const ca = ra.value.codePointAt(0);
48
+ const cb = rb.value.codePointAt(0);
49
+ if (ca !== cb) return ca < cb ? -1 : 1;
50
+ }
51
+ }
52
+ function invalid(context, path, message) {
53
+ throw new Error(`${context}: invalid SCP expression at ${path}: ${message}`);
54
+ }
55
+ function isPlainObject(v) {
56
+ if (typeof v !== "object" || v === null || Array.isArray(v)) return false;
57
+ const proto = Object.getPrototypeOf(v);
58
+ return proto === Object.prototype || proto === null;
59
+ }
60
+ function canonicalizeNode(node, context, path) {
61
+ if (node === null) return null;
62
+ if (typeof node === "string" || typeof node === "boolean") return node;
63
+ if (typeof node === "number") {
64
+ if (!Number.isFinite(node)) {
65
+ invalid(context, path, `non-finite number literal (${node}).`);
66
+ }
67
+ if (Number.isInteger(node) && !Number.isSafeInteger(node)) {
68
+ invalid(
69
+ context,
70
+ path,
71
+ `integral number literal ${node} exceeds the safe-integer range; carry it as {int:"${node}"} (expression-ir.md \xA72.3).`
72
+ );
73
+ }
74
+ return node;
75
+ }
76
+ if (Array.isArray(node)) {
77
+ invalid(context, path, "a bare array is not an expression (use {arr:[\u2026]}).");
78
+ }
79
+ if (!isPlainObject(node)) {
80
+ invalid(context, path, `expected a literal or a single-key node object.`);
81
+ }
82
+ const keys = Object.keys(node);
83
+ if (keys.length !== 1) {
84
+ invalid(
85
+ context,
86
+ path,
87
+ `a node object must have exactly one key, got [${keys.join(", ")}].`
88
+ );
89
+ }
90
+ const key = keys[0];
91
+ const arg = node[key];
92
+ if (key === "int") {
93
+ if (typeof arg !== "string" || !/^-?[0-9]+$/.test(arg)) {
94
+ invalid(context, `${path}.int`, '{int:"\u2026"} expects an integer string.');
95
+ }
96
+ const v = BigInt(arg);
97
+ if (v < I64_MIN || v > I64_MAX) {
98
+ invalid(context, `${path}.int`, `int literal ${arg} exceeds i64.`);
99
+ }
100
+ if (v >= BigInt(Number.MIN_SAFE_INTEGER) && v <= BigInt(Number.MAX_SAFE_INTEGER)) {
101
+ return Number(v);
102
+ }
103
+ return { int: v.toString() };
104
+ }
105
+ if (key === "float") {
106
+ if (typeof arg !== "number" || !Number.isFinite(arg)) {
107
+ invalid(context, `${path}.float`, "{float:n} expects a finite number.");
108
+ }
109
+ return Number.isInteger(arg) ? { float: arg } : arg;
110
+ }
111
+ if (key === "ref" || key === "refOpt") {
112
+ if (!Array.isArray(arg) || arg.length === 0) {
113
+ invalid(context, `${path}.${key}`, `{${key}:[\u2026]} expects a non-empty path array.`);
114
+ }
115
+ const segs = arg.map((seg, i) => {
116
+ if (typeof seg !== "string" || seg.length === 0) {
117
+ invalid(
118
+ context,
119
+ `${path}.${key}[${i}]`,
120
+ "a reference path segment must be a non-empty string."
121
+ );
122
+ }
123
+ return seg;
124
+ });
125
+ return key === "ref" ? { ref: segs } : { refOpt: segs };
126
+ }
127
+ if (key === "obj") {
128
+ if (!isPlainObject(arg)) {
129
+ invalid(context, `${path}.obj`, "{obj:{\u2026}} expects a plain object.");
130
+ }
131
+ const out = {};
132
+ for (const k of Object.keys(arg).sort(cmpCodePoints)) {
133
+ out[k] = canonicalizeNode(arg[k], context, `${path}.obj.${k}`);
134
+ }
135
+ return { obj: out };
136
+ }
137
+ if (key === "arr") {
138
+ if (!Array.isArray(arg)) {
139
+ invalid(context, `${path}.arr`, "{arr:[\u2026]} expects an array.");
140
+ }
141
+ return {
142
+ arr: arg.map((el, i) => canonicalizeNode(el, context, `${path}.arr[${i}]`))
143
+ };
144
+ }
145
+ if (!ALL_OPS.has(key)) {
146
+ invalid(
147
+ context,
148
+ path,
149
+ `unknown operator '${key}' \u2014 outside the Expression IR v1 closed set.`
150
+ );
151
+ }
152
+ const op = key;
153
+ if (!Array.isArray(arg)) {
154
+ invalid(context, `${path}.${op}`, `'${op}' expects an argument array.`);
155
+ }
156
+ const arity = UNARY_OPS.has(op) ? 1 : TERNARY_OPS.has(op) ? 3 : 2;
157
+ if (arg.length !== arity) {
158
+ invalid(
159
+ context,
160
+ `${path}.${op}`,
161
+ `'${op}' expects ${arity} argument(s), got ${arg.length}.`
162
+ );
163
+ }
164
+ const args = arg.map((a, i) => canonicalizeNode(a, context, `${path}.${op}[${i}]`));
165
+ return { [op]: args };
166
+ }
167
+ function canonicalizeExpressionSpec(input, context) {
168
+ if (!isPlainObject(input)) {
169
+ throw new Error(
170
+ `${context}: an SCP expression must be a plain \`{ exprVersion: 1, expr: <node> }\` object.`
171
+ );
172
+ }
173
+ const keys = Object.keys(input).sort();
174
+ if (keys.length !== 2 || keys[0] !== "expr" || keys[1] !== "exprVersion") {
175
+ throw new Error(
176
+ `${context}: an SCP expression envelope must have exactly the keys \`exprVersion\` and \`expr\`; got [${Object.keys(input).join(", ")}].`
177
+ );
178
+ }
179
+ if (input.exprVersion !== EXPR_VERSION) {
180
+ throw new Error(
181
+ `${context}: unsupported exprVersion ${JSON.stringify(input.exprVersion)} \u2014 this build understands exprVersion ${EXPR_VERSION} only (fail-closed).`
182
+ );
183
+ }
184
+ return {
185
+ exprVersion: EXPR_VERSION,
186
+ expr: canonicalizeNode(input.expr, context, "$")
187
+ };
188
+ }
189
+ function isScpExprConditionInput(condition) {
190
+ return "scpExpr" in condition;
191
+ }
192
+ function conditionHasScp(condition) {
193
+ return condition?.kind === "scpExpr";
194
+ }
195
+ function operationsContainScpNodes(doc) {
196
+ for (const command of Object.values(doc.commands)) {
197
+ if (conditionHasScp(command.condition)) return true;
198
+ }
199
+ for (const transaction of Object.values(doc.transactions ?? {})) {
200
+ for (const item of transaction.items) {
201
+ if (item.guard !== void 0 || conditionHasScp(item.condition)) return true;
202
+ }
203
+ }
204
+ for (const contract of Object.values(doc.contracts ?? {})) {
205
+ if (contract.kind !== "command") continue;
206
+ for (const method of Object.values(contract.methods)) {
207
+ if (method.guard !== void 0) return true;
208
+ }
209
+ }
210
+ return false;
211
+ }
212
+ function operationsSpecVersion(doc) {
213
+ return operationsContainScpNodes(doc) ? SPEC_VERSION_SCP : SPEC_VERSION;
214
+ }
215
+
216
+ // src/define/transaction.ts
217
+ var TX_REF_BRAND = /* @__PURE__ */ Symbol("graphddb:txref");
218
+ function isTransactionRef(value) {
219
+ return typeof value === "object" && value !== null && value[TX_REF_BRAND] === true;
220
+ }
221
+ var MARKER_TAGS = [
222
+ // pass 0: short, begins with 'A'.
223
+ "Axqv",
224
+ // pass 1: long, begins with 'Z'. The two tags share NO common prefix and NO
225
+ // common substring of length >= 2 (their alphabets are disjoint), and differ
226
+ // in length, so any fragment-extracting / length-dependent transform
227
+ // (`slice(0,n)`, `[i]`, `charAt`, `padStart`, `padEnd`) yields *different*
228
+ // fragments across the two passes -- the differential check then rejects it.
229
+ // (Convergence-to-constant escapes that even disjoint markers cannot separate
230
+ // -- `slice(0,0)` -> '' in both passes, `includes('x')` -> a constant boolean
231
+ // -- are caught instead by the per-access coercion ledger; see
232
+ // {@link makeFieldRef} and {@link defineTransaction}.)
233
+ "Zmnoprstuwy0123456789WIDEMARKERBODYFILLERZ"
234
+ ];
235
+ var MARKER_PASS_COUNT = MARKER_TAGS.length;
236
+ function markerValue(pass, field) {
237
+ const tag = MARKER_TAGS[pass];
238
+ return `${tag}::${field}::${tag}`;
239
+ }
240
+ function makeValueSentinel(fields, tokenFor, originFor, markerFor, containerOrigin, onAccess, onCoerce) {
241
+ const cache = /* @__PURE__ */ new Map();
242
+ return new Proxy(/* @__PURE__ */ Object.create(null), {
243
+ get(_t, key) {
244
+ if (typeof key === "symbol") {
245
+ 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")) {
246
+ return void 0;
247
+ }
248
+ throw new Error(
249
+ `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.`
250
+ );
251
+ }
252
+ if (!fields.has(key)) {
253
+ throw new Error(
254
+ `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.`
255
+ );
256
+ }
257
+ onAccess(key);
258
+ let ref = cache.get(key);
259
+ if (!ref) {
260
+ ref = makeFieldRef(
261
+ tokenFor(key),
262
+ originFor(key),
263
+ markerFor(key),
264
+ () => onCoerce(key)
265
+ );
266
+ cache.set(key, ref);
267
+ }
268
+ return ref;
269
+ },
270
+ set() {
271
+ throw new Error(
272
+ `defineTransaction: cannot assign to a field of ${containerOrigin}.`
273
+ );
274
+ },
275
+ defineProperty() {
276
+ throw new Error(
277
+ `defineTransaction: cannot define a property on ${containerOrigin}.`
278
+ );
279
+ },
280
+ deleteProperty() {
281
+ throw new Error(
282
+ `defineTransaction: cannot delete a field of ${containerOrigin}.`
283
+ );
284
+ }
285
+ });
286
+ }
287
+ function makeFieldRef(token, origin, marker, onCoerce) {
288
+ const target = /* @__PURE__ */ Object.create(null);
289
+ const proxy = new Proxy(target, {
290
+ get(_t, key) {
291
+ if (key === TX_REF_BRAND) return true;
292
+ if (key === "token") return token;
293
+ if (key === "origin") return origin;
294
+ if (key === Symbol.toPrimitive) {
295
+ return (hint) => {
296
+ onCoerce();
297
+ return hint === "number" ? NaN : marker;
298
+ };
299
+ }
300
+ if (key === "toString" || key === Symbol.toStringTag) {
301
+ return () => {
302
+ onCoerce();
303
+ return marker;
304
+ };
305
+ }
306
+ if (key === "valueOf") {
307
+ return () => {
308
+ onCoerce();
309
+ return NaN;
310
+ };
311
+ }
312
+ 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")) {
313
+ return void 0;
314
+ }
315
+ const name = typeof key === "symbol" ? key.toString() : String(key);
316
+ throw new Error(
317
+ `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.`
318
+ );
319
+ },
320
+ set() {
321
+ throw new Error(`defineTransaction: cannot assign on ${origin}.`);
322
+ },
323
+ defineProperty() {
324
+ throw new Error(`defineTransaction: cannot define a property on ${origin}.`);
325
+ },
326
+ deleteProperty() {
327
+ throw new Error(`defineTransaction: cannot delete on ${origin}.`);
328
+ }
329
+ });
330
+ return proxy;
331
+ }
332
+ var when = {
333
+ eq(left, right) {
334
+ assertRef(left, "when.eq");
335
+ return { op: "eq", left, right };
336
+ },
337
+ ne(left, right) {
338
+ assertRef(left, "when.ne");
339
+ return { op: "ne", left, right };
340
+ }
341
+ };
342
+ function assertRef(value, context) {
343
+ if (!isTransactionRef(value)) {
344
+ throw new Error(
345
+ `defineTransaction: ${context} expects a field reference (e.g. \`p.status\` or an element field) on its left-hand side.`
346
+ );
347
+ }
348
+ }
349
+ function entityRef(model) {
350
+ const modelClass = resolveModelClass(model);
351
+ return { name: modelClass.name, modelClass };
352
+ }
353
+ function descriptorOf(p) {
354
+ if (p.kind === "array") {
355
+ const element = {};
356
+ for (const [field, fieldParam] of Object.entries(p.element ?? {})) {
357
+ element[field] = descriptorOf(fieldParam);
358
+ }
359
+ return { kind: "array", required: true, element };
360
+ }
361
+ return p.literals === void 0 ? { kind: p.kind, required: true } : { kind: p.kind, literals: p.literals, required: true };
362
+ }
363
+ function runBuildPass(params, build, scalarFields, arrayParams, pass, accessed, coerced) {
364
+ const noteAccess = (token, origin) => {
365
+ if (!accessed.has(token)) accessed.set(token, { token, origin });
366
+ };
367
+ const noteCoerce = (token, origin) => {
368
+ if (!coerced.has(token)) coerced.set(token, { token, origin });
369
+ };
370
+ const scalarSentinel = makeValueSentinel(
371
+ scalarFields,
372
+ (f) => `{${f}}`,
373
+ (f) => `param '${f}'`,
374
+ (f) => markerValue(pass, f),
375
+ "params",
376
+ (f) => noteAccess(`{${f}}`, `param '${f}'`),
377
+ (f) => noteCoerce(`{${f}}`, `param '${f}'`)
378
+ );
379
+ const pProxy = new Proxy(/* @__PURE__ */ Object.create(null), {
380
+ get(_t, key) {
381
+ if (typeof key === "symbol") {
382
+ 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")) {
383
+ return void 0;
384
+ }
385
+ throw new Error("defineTransaction: unsupported symbol access on params.");
386
+ }
387
+ if (arrayParams.has(key)) return params[key];
388
+ if (scalarFields.has(key)) return scalarSentinel[key];
389
+ throw new Error(
390
+ `defineTransaction: unknown parameter '${key}'. Declare it in the params map passed to defineTransaction.`
391
+ );
392
+ }
393
+ });
394
+ const instructions = [];
395
+ let currentBody = instructions;
396
+ let insideForEach = false;
397
+ const recordWrite = (instr) => {
398
+ currentBody.push(instr);
399
+ };
400
+ const recorder = {
401
+ put(model, item, options) {
402
+ recordWrite({
403
+ kind: "write",
404
+ operation: "put",
405
+ entity: entityRef(model),
406
+ item,
407
+ ...options?.condition !== void 0 ? { condition: options.condition } : {},
408
+ ...options?.when !== void 0 ? { when: options.when } : {},
409
+ ...options?.guard !== void 0 ? { guard: options.guard } : {}
410
+ });
411
+ },
412
+ update(model, key, changes, options) {
413
+ recordWrite({
414
+ kind: "write",
415
+ operation: "update",
416
+ entity: entityRef(model),
417
+ key,
418
+ changes,
419
+ ...options?.condition !== void 0 ? { condition: options.condition } : {},
420
+ ...options?.when !== void 0 ? { when: options.when } : {},
421
+ ...options?.guard !== void 0 ? { guard: options.guard } : {}
422
+ });
423
+ },
424
+ delete(model, key, options) {
425
+ recordWrite({
426
+ kind: "write",
427
+ operation: "delete",
428
+ entity: entityRef(model),
429
+ key,
430
+ ...options?.condition !== void 0 ? { condition: options.condition } : {},
431
+ ...options?.when !== void 0 ? { when: options.when } : {},
432
+ ...options?.guard !== void 0 ? { guard: options.guard } : {}
433
+ });
434
+ },
435
+ conditionCheck(model, key, options) {
436
+ if (options?.condition === void 0 || options.condition === null) {
437
+ throw new Error(
438
+ "defineTransaction: tx.conditionCheck requires a `condition` (the read-only assertion it makes); e.g. `{ condition: { attributeExists: 'PK' } }`."
439
+ );
440
+ }
441
+ recordWrite({
442
+ kind: "write",
443
+ operation: "conditionCheck",
444
+ entity: entityRef(model),
445
+ key,
446
+ condition: options.condition,
447
+ ...options.when !== void 0 ? { when: options.when } : {},
448
+ ...options.guard !== void 0 ? { guard: options.guard } : {}
449
+ });
450
+ },
451
+ forEach(source, body, options) {
452
+ if (insideForEach) {
453
+ throw new Error(
454
+ "defineTransaction: nested forEach is not supported; flatten the loop."
455
+ );
456
+ }
457
+ if (!isParam(source) || source.kind !== "array") {
458
+ throw new Error(
459
+ "defineTransaction: forEach source must be a `param.array(...)` parameter accessed via `p.<name>`."
460
+ );
461
+ }
462
+ const arrayParam = source;
463
+ const sourceName = arrayParams.size ? Object.keys(params).find((k) => params[k] === arrayParam) : void 0;
464
+ if (sourceName === void 0) {
465
+ throw new Error(
466
+ "defineTransaction: forEach source is not a declared array param."
467
+ );
468
+ }
469
+ const elementFields = new Set(Object.keys(arrayParam.element ?? {}));
470
+ const elementProxy = makeValueSentinel(
471
+ elementFields,
472
+ (f) => `{item.${f}}`,
473
+ (f) => `element field '${f}' of '${sourceName}'`,
474
+ (f) => markerValue(pass, `item.${f}`),
475
+ `element of '${sourceName}'`,
476
+ (f) => noteAccess(`{item.${f}}`, `element field '${f}' of '${sourceName}'`),
477
+ (f) => noteCoerce(`{item.${f}}`, `element field '${f}' of '${sourceName}'`)
478
+ );
479
+ const block = {
480
+ kind: "forEach",
481
+ source: sourceName,
482
+ ...options?.when !== void 0 ? { when: options.when } : {},
483
+ body: []
484
+ };
485
+ const prevBody = currentBody;
486
+ currentBody = block.body;
487
+ insideForEach = true;
488
+ try {
489
+ body(elementProxy);
490
+ } finally {
491
+ insideForEach = false;
492
+ currentBody = prevBody;
493
+ }
494
+ currentBody.push(block);
495
+ }
496
+ };
497
+ build(recorder, pProxy);
498
+ return instructions;
499
+ }
500
+ function defineTransaction(params, build) {
501
+ const descriptors = {};
502
+ for (const [name, value] of Object.entries(params)) {
503
+ if (!isParam(value)) {
504
+ throw new Error(
505
+ `defineTransaction: param '${name}' is not a param.* placeholder.`
506
+ );
507
+ }
508
+ descriptors[name] = descriptorOf(value);
509
+ }
510
+ const scalarFields = new Set(
511
+ Object.keys(params).filter((k) => params[k].kind !== "array")
512
+ );
513
+ const arrayParams = new Set(
514
+ Object.keys(params).filter((k) => params[k].kind === "array")
515
+ );
516
+ const accessed = /* @__PURE__ */ new Map();
517
+ const coerced = /* @__PURE__ */ new Map();
518
+ const passes = [];
519
+ for (let pass = 0; pass < MARKER_PASS_COUNT; pass++) {
520
+ passes.push(
521
+ runBuildPass(
522
+ params,
523
+ build,
524
+ scalarFields,
525
+ arrayParams,
526
+ pass,
527
+ accessed,
528
+ coerced
529
+ )
530
+ );
531
+ }
532
+ if (coerced.size > 0) {
533
+ const info = [...coerced.values()][0];
534
+ throw new Error(
535
+ `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.`
536
+ );
537
+ }
538
+ const surfaced = /* @__PURE__ */ new Set();
539
+ verifyInstructions(passes, surfaced);
540
+ for (const [token, info] of accessed) {
541
+ if (!surfaced.has(token)) {
542
+ throw new Error(
543
+ `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.`
544
+ );
545
+ }
546
+ }
547
+ return {
548
+ __isTransactionDefinition: true,
549
+ params: descriptors,
550
+ instructions: passes[0]
551
+ };
552
+ }
553
+ function verifyInstructions(passes, surfaced) {
554
+ const first = passes[0];
555
+ for (let i = 0; i < first.length; i++) {
556
+ const instrs = passes.map((p) => p[i]);
557
+ const kind = first[i].kind;
558
+ if (instrs.some((x) => x.kind !== kind)) {
559
+ throw new Error(
560
+ "defineTransaction: build is not deterministic across evaluation (instruction shape diverged between passes). The callback must be a pure, declarative description of the writes."
561
+ );
562
+ }
563
+ if (kind === "write") {
564
+ verifyWrite(instrs, surfaced);
565
+ } else {
566
+ const blocks = instrs;
567
+ verifyWhen(
568
+ blocks.map((b) => b.when),
569
+ surfaced,
570
+ `forEach '${blocks[0].source}' when`
571
+ );
572
+ verifyInstructions(
573
+ blocks.map((b) => b.body),
574
+ surfaced
575
+ );
576
+ }
577
+ }
578
+ }
579
+ function verifyWrite(instrs, surfaced) {
580
+ const op = instrs[0].operation;
581
+ if (instrs.some((x) => x.operation !== op)) {
582
+ throw new Error(
583
+ "defineTransaction: write operation diverged between evaluation passes; the callback must be a pure, declarative description of the writes."
584
+ );
585
+ }
586
+ verifyRecord(instrs.map((x) => x.item), surfaced, `${op} item`);
587
+ verifyRecord(instrs.map((x) => x.key), surfaced, `${op} key`);
588
+ verifyRecord(instrs.map((x) => x.changes), surfaced, `${op} changes`);
589
+ verifyCondition(
590
+ instrs.map((x) => x.condition),
591
+ surfaced,
592
+ `${op} condition`
593
+ );
594
+ verifyWhen(instrs.map((x) => x.when), surfaced, `${op} when`);
595
+ }
596
+ function verifyRecord(records, surfaced, context) {
597
+ const present = records.filter((r) => r !== void 0);
598
+ if (present.length === 0) return;
599
+ if (present.length !== records.length) {
600
+ throw new Error(
601
+ `defineTransaction: ${context} present in some evaluation passes but not others; the callback must be a pure, declarative description.`
602
+ );
603
+ }
604
+ const keys = Object.keys(present[0]).sort();
605
+ for (const r of present) {
606
+ const k = Object.keys(r).sort();
607
+ if (k.length !== keys.length || k.some((x, i) => x !== keys[i])) {
608
+ throw new Error(
609
+ `defineTransaction: ${context} field set diverged between evaluation passes; the callback must be a pure, declarative description.`
610
+ );
611
+ }
612
+ }
613
+ for (const field of keys) {
614
+ verifyLeaf(
615
+ present.map((r) => r[field]),
616
+ surfaced,
617
+ `${context} field '${field}'`
618
+ );
619
+ }
620
+ }
621
+ function isConditionOperatorObject(value) {
622
+ if (typeof value !== "object" || value === null || Array.isArray(value) || value instanceof Date || isTransactionRef(value)) {
623
+ return false;
624
+ }
625
+ const keys = Object.keys(value);
626
+ if (keys.length === 0) return false;
627
+ return keys.every((k) => CONDITION_OPERATOR_KEYS.has(k));
628
+ }
629
+ function verifyCondition(conditions, surfaced, context) {
630
+ const present = conditions.filter(
631
+ (c) => c !== void 0 && c !== null
632
+ );
633
+ if (present.length === 0) return;
634
+ if (present.length !== conditions.length) {
635
+ throw new Error(
636
+ `defineTransaction: ${context} present in some evaluation passes but not others; the callback must be a pure, declarative description.`
637
+ );
638
+ }
639
+ if (present.some((c) => isRawCondition(c))) return;
640
+ if (present.some((c) => typeof c !== "object" || Array.isArray(c))) {
641
+ throw new Error(
642
+ `defineTransaction: ${context} must be a declarative condition object (field clause / operator object / \`and\` / \`or\` / \`not\` group).`
643
+ );
644
+ }
645
+ verifyConditionTree(
646
+ present.map((c) => c),
647
+ surfaced,
648
+ context
649
+ );
650
+ }
651
+ function verifyConditionTree(nodes, surfaced, context) {
652
+ const keys = Object.keys(nodes[0]).sort();
653
+ for (const n of nodes) {
654
+ const k = Object.keys(n).sort();
655
+ if (k.length !== keys.length || k.some((x, i) => x !== keys[i])) {
656
+ throw new Error(
657
+ `defineTransaction: ${context} key set diverged between evaluation passes; the callback must be a pure, declarative description.`
658
+ );
659
+ }
660
+ }
661
+ for (const key of keys) {
662
+ const values = nodes.map((n) => n[key]);
663
+ if (key === "and" || key === "or") {
664
+ const arrays = values.map((v) => {
665
+ if (!Array.isArray(v) || v.length === 0) {
666
+ throw new Error(
667
+ `defineTransaction: ${context} \`${key}\` must be a non-empty array of sub-conditions.`
668
+ );
669
+ }
670
+ return v;
671
+ });
672
+ const len = arrays[0].length;
673
+ if (arrays.some((a) => a.length !== len)) {
674
+ throw new Error(
675
+ `defineTransaction: ${context} \`${key}\` length diverged between evaluation passes; the callback must be declarative.`
676
+ );
677
+ }
678
+ for (let i = 0; i < len; i++) {
679
+ verifyCondition(
680
+ arrays.map((a) => a[i]),
681
+ surfaced,
682
+ `${context} ${key}[${i}]`
683
+ );
684
+ }
685
+ continue;
686
+ }
687
+ if (key === "not") {
688
+ verifyCondition(
689
+ values,
690
+ surfaced,
691
+ `${context} not`
692
+ );
693
+ continue;
694
+ }
695
+ if (!values.every((v) => isConditionOperatorObject(v))) {
696
+ if (values.some((v) => isConditionOperatorObject(v))) {
697
+ throw new Error(
698
+ `defineTransaction: ${context} field '${key}' is an operator object in some evaluation passes but a bare value in others; the callback must be declarative.`
699
+ );
700
+ }
701
+ verifyLeaf(values, surfaced, `${context} field '${key}'`);
702
+ continue;
703
+ }
704
+ verifyOperatorObject(
705
+ values.map((v) => v),
706
+ surfaced,
707
+ `${context} field '${key}'`
708
+ );
709
+ }
710
+ }
711
+ function verifyOperatorObject(objs, surfaced, context) {
712
+ const ops = Object.keys(objs[0]).sort();
713
+ for (const o of objs) {
714
+ const k = Object.keys(o).sort();
715
+ if (k.length !== ops.length || k.some((x, i) => x !== ops[i])) {
716
+ throw new Error(
717
+ `defineTransaction: ${context} operator set diverged between evaluation passes; the callback must be a pure, declarative description.`
718
+ );
719
+ }
720
+ }
721
+ for (const op of ops) {
722
+ const opVals = objs.map((o) => o[op]);
723
+ if (op === "between") {
724
+ const pairs = opVals.map((v) => {
725
+ if (!Array.isArray(v) || v.length !== 2) {
726
+ throw new Error(
727
+ `defineTransaction: ${context} \`between\` takes a \`[lo, hi]\` pair.`
728
+ );
729
+ }
730
+ return v;
731
+ });
732
+ verifyLeaf(pairs.map((p) => p[0]), surfaced, `${context} between[0]`);
733
+ verifyLeaf(pairs.map((p) => p[1]), surfaced, `${context} between[1]`);
734
+ } else if (op === "in") {
735
+ const arrays = opVals.map((v) => {
736
+ if (!Array.isArray(v) || v.length === 0) {
737
+ throw new Error(
738
+ `defineTransaction: ${context} \`in\` takes a non-empty array of operands.`
739
+ );
740
+ }
741
+ return v;
742
+ });
743
+ const len = arrays[0].length;
744
+ if (arrays.some((a) => a.length !== len)) {
745
+ throw new Error(
746
+ `defineTransaction: ${context} \`in\` length diverged between evaluation passes; the callback must be declarative.`
747
+ );
748
+ }
749
+ for (let i = 0; i < len; i++) {
750
+ verifyLeaf(arrays.map((a) => a[i]), surfaced, `${context} in[${i}]`);
751
+ }
752
+ } else if (op === "attributeExists" || op === "attributeType") {
753
+ if (opVals.some((v) => isTransactionRef(v))) {
754
+ throw new Error(
755
+ `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.`
756
+ );
757
+ }
758
+ } else {
759
+ verifyLeaf(opVals, surfaced, `${context} ${op}`);
760
+ }
761
+ }
762
+ }
763
+ function verifyWhen(whens, surfaced, context) {
764
+ const present = whens.filter((w) => w !== void 0);
765
+ if (present.length === 0) return;
766
+ if (present.length !== whens.length) {
767
+ throw new Error(
768
+ `defineTransaction: ${context} present in some evaluation passes but not others; the callback must be a pure, declarative description.`
769
+ );
770
+ }
771
+ for (const w of present) {
772
+ if (isTransactionRef(w.left)) surfaced.add(w.left.token);
773
+ }
774
+ verifyLeaf(present.map((w) => w.right), surfaced, `${context} right-hand value`);
775
+ }
776
+ function verifyLeaf(values, surfaced, context) {
777
+ const refTokens = values.map((v) => isTransactionRef(v) ? v.token : void 0);
778
+ const anyRef = refTokens.some((t) => t !== void 0);
779
+ if (anyRef) {
780
+ if (refTokens.some((t) => t === void 0)) {
781
+ throw new Error(
782
+ `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.`
783
+ );
784
+ }
785
+ const token = refTokens[0];
786
+ if (refTokens.some((t) => t !== token)) {
787
+ throw new Error(
788
+ `defineTransaction: ${context} resolves to different field references across evaluation passes \u2014 the value is not a stable, declarative reference.`
789
+ );
790
+ }
791
+ surfaced.add(token);
792
+ return;
793
+ }
794
+ const norm = values.map(normalizeLiteral);
795
+ for (let i = 1; i < norm.length; i++) {
796
+ if (norm[i] !== norm[0]) {
797
+ throw new Error(
798
+ `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.`
799
+ );
800
+ }
801
+ }
802
+ }
803
+ function normalizeLiteral(value) {
804
+ if (value === null) return "t:null";
805
+ if (value instanceof Date) return `t:date:${value.toISOString()}`;
806
+ const t = typeof value;
807
+ if (t === "string") return `t:s:${value}`;
808
+ if (t === "boolean") return `t:b:${String(value)}`;
809
+ if (t === "number") {
810
+ if (Number.isNaN(value)) {
811
+ throw new Error(
812
+ "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."
813
+ );
814
+ }
815
+ return `t:n:${String(value)}`;
816
+ }
817
+ throw new Error(
818
+ `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.`
819
+ );
820
+ }
821
+ var SCP_LOWERED_MARKER = "graphddb:scp-lowered/v1";
822
+ function defineScpTransaction(params, build, lowered) {
823
+ if (lowered !== SCP_LOWERED_MARKER) {
824
+ throw new Error(
825
+ "defineScpTransaction: this body has not been compiled by the SCP structural transform (issue #263). The native-syntax form must be built with `transformScpTransactionSource` (graphddb/transform), which lowers Guard / Conditional / .map onto the recorder seams and stamps the call with the lowered-body marker. Running it untransformed could silently record a wrong spec (a dropped or unguarded item), so it is rejected at definition time instead."
826
+ );
827
+ }
828
+ return defineTransaction(
829
+ params,
830
+ build
831
+ );
832
+ }
833
+ function defineTransactions(definitions) {
834
+ for (const [name, def] of Object.entries(definitions)) {
835
+ if (def === null || typeof def !== "object" || def.__isTransactionDefinition !== true) {
836
+ throw new Error(
837
+ `defineTransactions: '${name}' is not a transaction definition. Use defineTransaction(...) to build entries.`
838
+ );
839
+ }
840
+ }
841
+ return definitions;
842
+ }
843
+
844
+ export {
845
+ SPEC_VERSION,
846
+ SPEC_VERSION_SCP,
847
+ SPEC_VERSION_SUPPORTED,
848
+ EXPR_VERSION,
849
+ MARKER_ROW_ENTITY,
850
+ canonicalizeExpressionSpec,
851
+ isScpExprConditionInput,
852
+ operationsContainScpNodes,
853
+ operationsSpecVersion,
854
+ isTransactionRef,
855
+ when,
856
+ defineTransaction,
857
+ SCP_LOWERED_MARKER,
858
+ defineScpTransaction,
859
+ defineTransactions
860
+ };