graphddb 0.8.1 → 0.9.1
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.
- package/dist/behaviors-DDltNivc.d.ts +217 -0
- package/dist/cdc/index.d.ts +5 -4
- package/dist/cdc/index.js +3 -3
- package/dist/{chunk-HNY2EJPV.js → chunk-7OCXY4R6.js} +1 -1
- package/dist/{chunk-L2NEDS7U.js → chunk-GWWRXIHF.js} +260 -12
- package/dist/{chunk-T44OB5GU.js → chunk-HLFNCKFV.js} +105 -11
- package/dist/{chunk-LAT64YCZ.js → chunk-MBJ4JVRM.js} +680 -25
- package/dist/{chunk-S2NI4PBW.js → chunk-PHXUFAY2.js} +7 -5
- package/dist/{chunk-GS4C5VGO.js → chunk-VECUS35D.js} +2 -2
- package/dist/{chunk-L4QRCHRQ.js → chunk-WOFRHRXY.js} +6 -25
- package/dist/cli.js +366 -80
- package/dist/index.d.ts +131 -5
- package/dist/index.js +121 -5
- package/dist/internal/index.d.ts +6 -5
- package/dist/internal/index.js +7 -7
- package/dist/{key-DZtjAQDh.d.ts → key-CWytoEaE.d.ts} +93 -5
- package/dist/linter/index.d.ts +6 -5
- package/dist/{linter-DQY7gUEk.d.ts → linter-jEwmZotm.d.ts} +1 -1
- package/dist/{prepared-artifact-HFealr1q.d.ts → prepared-artifact-CwH5ezFq.d.ts} +4 -4
- package/dist/spec/index.d.ts +225 -24
- package/dist/spec/index.js +17 -9
- package/dist/testing/index.d.ts +3 -2
- package/dist/testing/index.js +32 -5
- package/dist/transform/index.d.ts +2 -1
- package/dist/transform/index.js +2 -2
- package/dist/{types-2PMXEn5x.d.ts → types-CgXS-4Ox.d.ts} +299 -28
- package/dist/{types-DW__-Icc.d.ts → types-nk5okD7d.d.ts} +1 -1
- package/docs/python-bridge.md +33 -49
- package/package.json +11 -3
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
PREPARED_FORMAT_VERSION,
|
|
3
3
|
buildManifest,
|
|
4
4
|
entityFingerprint
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-PHXUFAY2.js";
|
|
6
6
|
import {
|
|
7
7
|
analyzeWriteRoute,
|
|
8
8
|
assertBundleSerializable,
|
|
@@ -21,33 +21,96 @@ import {
|
|
|
21
21
|
isContractKeyRef,
|
|
22
22
|
isContractParamRef,
|
|
23
23
|
isQueryModelContract
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-HLFNCKFV.js";
|
|
25
25
|
import {
|
|
26
26
|
detectRelationFields,
|
|
27
27
|
isInlineSnapshotSpec,
|
|
28
28
|
normalizeSelectSpec
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-7OCXY4R6.js";
|
|
30
30
|
import {
|
|
31
|
+
EXPR_VERSION,
|
|
31
32
|
MARKER_ROW_ENTITY,
|
|
32
|
-
|
|
33
|
+
SPEC_VERSION_KEY_EXPR,
|
|
33
34
|
operationsSpecVersion
|
|
34
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-WOFRHRXY.js";
|
|
35
36
|
import {
|
|
36
37
|
BATCH_GET_MAX_KEYS,
|
|
37
38
|
CONDITION_OPERATOR_KEYS,
|
|
39
|
+
GRAPHDDB_CATALOG,
|
|
38
40
|
MAX_TRANSACT_ITEMS,
|
|
39
41
|
MetadataRegistry,
|
|
42
|
+
assertComponentsInCatalog,
|
|
43
|
+
assertPublishSplitMatchesDerived,
|
|
44
|
+
deriveComponentEffects,
|
|
40
45
|
isParam,
|
|
41
46
|
isRawCondition,
|
|
42
47
|
param,
|
|
43
48
|
resolveModelClass
|
|
44
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-GWWRXIHF.js";
|
|
45
50
|
import {
|
|
46
51
|
TableMapping,
|
|
47
52
|
isColumn,
|
|
48
53
|
resolveKey
|
|
49
54
|
} from "./chunk-XTWXMOHD.js";
|
|
50
55
|
|
|
56
|
+
// src/spec/key-expr.ts
|
|
57
|
+
var PLACEHOLDER = /\{([^{}]+)\}/g;
|
|
58
|
+
var RESULT_PREFIX = "result.";
|
|
59
|
+
var ITEM_PREFIX = "item.";
|
|
60
|
+
function tokenRef(token, template) {
|
|
61
|
+
if (token.startsWith(ITEM_PREFIX)) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
`lowerKeyTemplate: read-path key template '${template}' references an '${token}' (item/Map-element) binding, which is a Phase 3 write-path token, not a Phase 2 read-path key wiring. Refusing to lower it.`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
if (token.startsWith(RESULT_PREFIX)) {
|
|
67
|
+
return { ref: ["result", token.slice(RESULT_PREFIX.length)] };
|
|
68
|
+
}
|
|
69
|
+
return { ref: ["input", token] };
|
|
70
|
+
}
|
|
71
|
+
function lowerKeyTemplate(template) {
|
|
72
|
+
const parts = [];
|
|
73
|
+
let last = 0;
|
|
74
|
+
PLACEHOLDER.lastIndex = 0;
|
|
75
|
+
for (let m = PLACEHOLDER.exec(template); m !== null; m = PLACEHOLDER.exec(template)) {
|
|
76
|
+
const literal = template.slice(last, m.index);
|
|
77
|
+
if (literal !== "") parts.push(literal);
|
|
78
|
+
parts.push(tokenRef(m[1], template));
|
|
79
|
+
last = m.index + m[0].length;
|
|
80
|
+
}
|
|
81
|
+
const tail = template.slice(last);
|
|
82
|
+
if (tail !== "") parts.push(tail);
|
|
83
|
+
if (parts.length === 0) return "";
|
|
84
|
+
if (parts.length === 1) return parts[0];
|
|
85
|
+
return { concat: parts };
|
|
86
|
+
}
|
|
87
|
+
function lowerKeyCondition(keyCondition) {
|
|
88
|
+
const out = {};
|
|
89
|
+
for (const [attr, template] of Object.entries(keyCondition)) {
|
|
90
|
+
out[attr] = lowerKeyTemplate(template);
|
|
91
|
+
}
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
function renderKeyExprToTemplate(node) {
|
|
95
|
+
if (typeof node === "string") return node;
|
|
96
|
+
if (node !== null && typeof node === "object") {
|
|
97
|
+
const obj = node;
|
|
98
|
+
if (Array.isArray(obj.ref)) return refToToken(obj.ref);
|
|
99
|
+
if (Array.isArray(obj.concat)) {
|
|
100
|
+
return obj.concat.map(renderKeyExprToTemplate).join("");
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
throw new Error(
|
|
104
|
+
`renderKeyExprToTemplate: node is not a key-wiring expression (string literal / ref / concat): ${JSON.stringify(node)}`
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
function refToToken(path) {
|
|
108
|
+
const [root, ...rest] = path;
|
|
109
|
+
if (root === "result") return `{result.${rest.join(".")}}`;
|
|
110
|
+
if (root === "input") return `{${rest.join(".")}}`;
|
|
111
|
+
throw new Error(`renderKeyExprToTemplate: unexpected ref root '${root}' in key wiring`);
|
|
112
|
+
}
|
|
113
|
+
|
|
51
114
|
// src/spec/contract-n1-check.ts
|
|
52
115
|
function isQueryMethodSpec(spec) {
|
|
53
116
|
return typeof spec.resolution === "string" && spec.resolution !== void 0;
|
|
@@ -133,7 +196,9 @@ ${rest.map((v) => ` - ${v.message}`).join("\n")})` : "";
|
|
|
133
196
|
|
|
134
197
|
// src/spec/contracts.ts
|
|
135
198
|
function kindForDynamoType(dynamoType) {
|
|
136
|
-
|
|
199
|
+
if (dynamoType === "N") return "number";
|
|
200
|
+
if (dynamoType === "M") return "map";
|
|
201
|
+
return "string";
|
|
137
202
|
}
|
|
138
203
|
function recoverKind(metadata, fieldName) {
|
|
139
204
|
const field = metadata.fields.find((f) => f.propertyName === fieldName);
|
|
@@ -167,6 +232,7 @@ function opToDefinition(contractName, methodName, op) {
|
|
|
167
232
|
}
|
|
168
233
|
const select = op.select !== void 0 ? assertBooleanProjection(op.select, `${contractName}.${methodName} select`) : void 0;
|
|
169
234
|
const changes = op.changes !== void 0 ? convertStructure(op.changes, place, `${contractName}.${methodName} changes`) : void 0;
|
|
235
|
+
const add = op.add !== void 0 ? convertStructure(op.add, place, `${contractName}.${methodName} add`) : void 0;
|
|
170
236
|
const condition = op.condition !== void 0 ? convertCondition(op.condition, place, `${contractName}.${methodName} condition`) : void 0;
|
|
171
237
|
return {
|
|
172
238
|
__isOperationDefinition: true,
|
|
@@ -175,6 +241,7 @@ function opToDefinition(contractName, methodName, op) {
|
|
|
175
241
|
key,
|
|
176
242
|
select,
|
|
177
243
|
changes,
|
|
244
|
+
...add !== void 0 ? { add } : {},
|
|
178
245
|
...condition !== void 0 ? { condition } : {},
|
|
179
246
|
// #154: the use-case description captured on the recorded op (from the
|
|
180
247
|
// descriptor's `description`) flows into the flattened `queries[op]` /
|
|
@@ -185,6 +252,11 @@ function opToDefinition(contractName, methodName, op) {
|
|
|
185
252
|
};
|
|
186
253
|
}
|
|
187
254
|
function paramOfKind(recovered) {
|
|
255
|
+
if (recovered.kind === "map") {
|
|
256
|
+
throw new Error(
|
|
257
|
+
`A \`@map\` field cannot bind a key-position param: a DynamoDB key is composed of scalar segments, but this key field's model storage type is 'M' (issue #295). Bind map values at a value position (\`input\`) instead.`
|
|
258
|
+
);
|
|
259
|
+
}
|
|
188
260
|
if (recovered.kind === "number") return param.number();
|
|
189
261
|
if (recovered.kind === "literal" && recovered.literals !== void 0 && recovered.literals.length > 0) {
|
|
190
262
|
return param.literal(
|
|
@@ -402,6 +474,13 @@ function composeSpecOf(node, contractName, methodName, contracts) {
|
|
|
402
474
|
`Contract '${contractName}.${methodName}': composed child '${c.as}' must declare a \`resolution\` of 'point' or 'range'.`
|
|
403
475
|
);
|
|
404
476
|
}
|
|
477
|
+
const target = contracts[c.contract];
|
|
478
|
+
if (target !== void 0 && isQueryModelContract(target)) {
|
|
479
|
+
const targetMethod = target.methods[c.method];
|
|
480
|
+
if (targetMethod !== void 0) {
|
|
481
|
+
assertComposeTargetNotNested(contractName, methodName, c.as, c.contract, c.method, targetMethod.op);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
405
484
|
const bindIn = c.bind ?? {};
|
|
406
485
|
const bind = {};
|
|
407
486
|
for (const field of Object.keys(bindIn).sort()) {
|
|
@@ -435,6 +514,7 @@ function recordedComposeSpec(node, contractName, methodName, contracts) {
|
|
|
435
514
|
contracts
|
|
436
515
|
);
|
|
437
516
|
const refMethod = node.method;
|
|
517
|
+
assertComposeTargetNotNested(contractName, methodName, node.as, refName, refMethodName, refMethod.op);
|
|
438
518
|
const bind = {};
|
|
439
519
|
for (const field of Object.keys(node.bind).sort()) {
|
|
440
520
|
bind[field] = node.bind[field].path;
|
|
@@ -470,6 +550,12 @@ function composeOf(op) {
|
|
|
470
550
|
const raw = op.compose;
|
|
471
551
|
return Array.isArray(raw) ? raw : [];
|
|
472
552
|
}
|
|
553
|
+
function assertComposeTargetNotNested(contractName, methodName, as, targetContract, targetMethod, targetOp) {
|
|
554
|
+
if (targetOp === void 0 || composeOf(targetOp).length === 0) return;
|
|
555
|
+
throw new Error(
|
|
556
|
+
`Contract '${contractName}.${methodName}': composed child '${as}' references '${targetContract}.${targetMethod}', which itself declares a composition (nested compose). No runtime resolves a composed child's own compose edges, so '${targetContract}.${targetMethod}'s compose field(s) would never be populated through '${contractName}.${methodName}' \u2014 the generated result type would over-promise. Compose the grandchild directly from '${contractName}.${methodName}' instead (issue #256).`
|
|
557
|
+
);
|
|
558
|
+
}
|
|
473
559
|
function compositionPlanOf(compose) {
|
|
474
560
|
if (compose.length === 0) return void 0;
|
|
475
561
|
const { stages, concurrency } = deriveCompositionPlan(compose.length);
|
|
@@ -482,6 +568,9 @@ function compositionPlanOf(compose) {
|
|
|
482
568
|
function batchTxName(contractName, methodName) {
|
|
483
569
|
return `${opRefName(contractName, methodName)}__batch`;
|
|
484
570
|
}
|
|
571
|
+
function batchWriteTxName(contractName, methodName) {
|
|
572
|
+
return `${opRefName(contractName, methodName)}__batchWrite`;
|
|
573
|
+
}
|
|
485
574
|
function keyParamNames(op) {
|
|
486
575
|
return keyFieldsOf(op);
|
|
487
576
|
}
|
|
@@ -497,6 +586,14 @@ function toElementTemplate(template, keyParams) {
|
|
|
497
586
|
);
|
|
498
587
|
}
|
|
499
588
|
function toElementRecord(record, keyParams) {
|
|
589
|
+
const out = {};
|
|
590
|
+
for (const field of Object.keys(record)) {
|
|
591
|
+
const value = record[field];
|
|
592
|
+
out[field] = typeof value === "string" ? toElementTemplate(value, keyParams) : value;
|
|
593
|
+
}
|
|
594
|
+
return out;
|
|
595
|
+
}
|
|
596
|
+
function toElementKeyRecord(record, keyParams) {
|
|
500
597
|
const out = {};
|
|
501
598
|
for (const field of Object.keys(record)) {
|
|
502
599
|
out[field] = toElementTemplate(record[field], keyParams);
|
|
@@ -549,13 +646,51 @@ function synthesizeBatchTransaction(commandSpec, op) {
|
|
|
549
646
|
tableName: commandSpec.tableName,
|
|
550
647
|
entity: commandSpec.entity,
|
|
551
648
|
...commandSpec.item !== void 0 ? { item: toElementRecord(commandSpec.item, keyParams) } : {},
|
|
552
|
-
...commandSpec.keyCondition !== void 0 ? { keyCondition:
|
|
649
|
+
...commandSpec.keyCondition !== void 0 ? { keyCondition: toElementKeyRecord(commandSpec.keyCondition, keyParams) } : {},
|
|
553
650
|
...commandSpec.changes !== void 0 ? { changes: toElementRecord(commandSpec.changes, keyParams) } : {},
|
|
554
651
|
...condition !== void 0 ? { condition } : {},
|
|
555
652
|
forEach: { source: "keys" }
|
|
556
653
|
};
|
|
557
654
|
return { params, items: [item] };
|
|
558
655
|
}
|
|
656
|
+
function synthesizeBatchWriteTransaction(contractName, methodName, commandSpec) {
|
|
657
|
+
const at = `Contract '${contractName}.${methodName}'`;
|
|
658
|
+
if (commandSpec.type === "UpdateItem") {
|
|
659
|
+
throw new Error(
|
|
660
|
+
`${at}: mode 'batchWrite' cannot express an update \u2014 DynamoDB BatchWriteItem carries only PutRequest / DeleteRequest (internal invariant; the authoring layer should have rejected this).`
|
|
661
|
+
);
|
|
662
|
+
}
|
|
663
|
+
if (commandSpec.condition !== void 0) {
|
|
664
|
+
throw new Error(
|
|
665
|
+
`${at}: mode 'batchWrite' cannot carry a write condition \u2014 DynamoDB BatchWriteItem has no ConditionExpression (internal invariant; the authoring layer should have rejected this).`
|
|
666
|
+
);
|
|
667
|
+
}
|
|
668
|
+
const elementFields = {};
|
|
669
|
+
for (const name of Object.keys(commandSpec.params).sort()) {
|
|
670
|
+
elementFields[name] = commandSpec.params[name];
|
|
671
|
+
}
|
|
672
|
+
const params = {
|
|
673
|
+
items: { type: "array", required: true, element: elementFields }
|
|
674
|
+
};
|
|
675
|
+
const allParams = new Set(Object.keys(commandSpec.params));
|
|
676
|
+
const toElement = (record) => {
|
|
677
|
+
const out = {};
|
|
678
|
+
for (const field of Object.keys(record)) {
|
|
679
|
+
const v = record[field];
|
|
680
|
+
out[field] = typeof v === "string" ? toElementTemplate(v, allParams) : v;
|
|
681
|
+
}
|
|
682
|
+
return out;
|
|
683
|
+
};
|
|
684
|
+
const item = {
|
|
685
|
+
type: TX_ITEM_TYPE[commandSpec.type],
|
|
686
|
+
tableName: commandSpec.tableName,
|
|
687
|
+
entity: commandSpec.entity,
|
|
688
|
+
...commandSpec.item !== void 0 ? { item: toElement(commandSpec.item) } : {},
|
|
689
|
+
...commandSpec.keyCondition !== void 0 ? { keyCondition: toElement(commandSpec.keyCondition) } : {},
|
|
690
|
+
forEach: { source: "items" }
|
|
691
|
+
};
|
|
692
|
+
return { params, items: [item], batchWrite: true };
|
|
693
|
+
}
|
|
559
694
|
function composeFragmentTransaction(contractName, methodName, ops) {
|
|
560
695
|
const items = [];
|
|
561
696
|
const params = {};
|
|
@@ -704,8 +839,9 @@ function buildDerivedUpdateItems(derivedUpdates) {
|
|
|
704
839
|
tableName,
|
|
705
840
|
entity: update.entity.name,
|
|
706
841
|
keyCondition,
|
|
707
|
-
// The delta is a compile-time constant → a
|
|
708
|
-
|
|
842
|
+
// The delta is a compile-time constant → a TYPED numeric literal (no param),
|
|
843
|
+
// carried verbatim so it marshals as `N`, not a stringified `S "1"` (#299).
|
|
844
|
+
add: { [update.attribute]: update.amount }
|
|
709
845
|
});
|
|
710
846
|
collectTemplateParams(keyCondition, metadata, params);
|
|
711
847
|
}
|
|
@@ -765,7 +901,8 @@ function buildMaintainWriteItems(maintainWrites) {
|
|
|
765
901
|
...maintain.kind === "counter" && maintain.counter !== void 0 && maintain.counter.op === "count" ? {
|
|
766
902
|
counter: {
|
|
767
903
|
attribute: maintain.counter.attribute,
|
|
768
|
-
delta
|
|
904
|
+
// TYPED numeric delta (#299): carried verbatim so it marshals as `N`.
|
|
905
|
+
delta: maintain.counter.delta
|
|
769
906
|
}
|
|
770
907
|
} : {}
|
|
771
908
|
};
|
|
@@ -923,6 +1060,15 @@ function modeTargetFor(mode, op, commandSpec, contractName, methodName, opName,
|
|
|
923
1060
|
if (mode === "parallel") {
|
|
924
1061
|
return { mode: "parallel", operation: opName };
|
|
925
1062
|
}
|
|
1063
|
+
if (mode === "batchWrite") {
|
|
1064
|
+
const txName2 = batchWriteTxName(contractName, methodName);
|
|
1065
|
+
transactionSpecs[txName2] = synthesizeBatchWriteTransaction(
|
|
1066
|
+
contractName,
|
|
1067
|
+
methodName,
|
|
1068
|
+
commandSpec
|
|
1069
|
+
);
|
|
1070
|
+
return { mode: "batchWrite", transaction: txName2 };
|
|
1071
|
+
}
|
|
926
1072
|
const txName = batchTxName(contractName, methodName);
|
|
927
1073
|
transactionSpecs[txName] = synthesizeBatchTransaction(commandSpec, op);
|
|
928
1074
|
return { mode: "transaction", transaction: txName };
|
|
@@ -1138,6 +1284,11 @@ function serializeCommandContract(contractName, contract, commandSpecs, transact
|
|
|
1138
1284
|
returnSelection
|
|
1139
1285
|
);
|
|
1140
1286
|
}
|
|
1287
|
+
if (method.mode === "batchWrite" && promotedToTransaction) {
|
|
1288
|
+
throw new Error(
|
|
1289
|
+
`Contract '${contractName}.${methodName}': mode 'batchWrite' on a method promoted to an atomic TransactWriteItems (derived effects / multi-fragment) \u2014 BatchWriteItem cannot compose those items (internal invariant).`
|
|
1290
|
+
);
|
|
1291
|
+
}
|
|
1141
1292
|
const batch = method.mode !== void 0 && !promotedToTransaction ? modeTargetFor(
|
|
1142
1293
|
method.mode,
|
|
1143
1294
|
op,
|
|
@@ -1314,6 +1465,417 @@ ${rest.map((v) => ` - ${v.message}`).join("\n")})` : "";
|
|
|
1314
1465
|
throw new Error(first.message + suffix);
|
|
1315
1466
|
}
|
|
1316
1467
|
|
|
1468
|
+
// src/spec/components.ts
|
|
1469
|
+
import {
|
|
1470
|
+
buildComponentDefinition
|
|
1471
|
+
} from "behavior-contracts";
|
|
1472
|
+
function registerComponent(name, inputPorts, body, output, plan) {
|
|
1473
|
+
return buildComponentDefinition(
|
|
1474
|
+
{ name, inputPorts, body, output, plan },
|
|
1475
|
+
{ catalog: GRAPHDDB_CATALOG }
|
|
1476
|
+
);
|
|
1477
|
+
}
|
|
1478
|
+
function remapRefRoots(node, resultRoot, itemRoot) {
|
|
1479
|
+
if (node === null || typeof node !== "object") return node;
|
|
1480
|
+
if (Array.isArray(node)) {
|
|
1481
|
+
return node.map((e) => remapRefRoots(e, resultRoot, itemRoot));
|
|
1482
|
+
}
|
|
1483
|
+
const obj = node;
|
|
1484
|
+
const keys = Object.keys(obj);
|
|
1485
|
+
if (keys.length === 1) {
|
|
1486
|
+
const key = keys[0];
|
|
1487
|
+
const arg = obj[key];
|
|
1488
|
+
if (key === "ref" || key === "refOpt") {
|
|
1489
|
+
const path = arg;
|
|
1490
|
+
const [root, ...rest] = path;
|
|
1491
|
+
const remapped = root === "input" ? rest : root === "result" ? [...resultRoot, ...rest] : root === "item" && itemRoot !== void 0 ? [...itemRoot, ...rest] : path;
|
|
1492
|
+
return { [key]: remapped };
|
|
1493
|
+
}
|
|
1494
|
+
if (key === "int" || key === "float") return node;
|
|
1495
|
+
if (key === "obj") {
|
|
1496
|
+
const out = {};
|
|
1497
|
+
for (const [k, v] of Object.entries(arg)) {
|
|
1498
|
+
out[k] = remapRefRoots(v, resultRoot, itemRoot);
|
|
1499
|
+
}
|
|
1500
|
+
return { obj: out };
|
|
1501
|
+
}
|
|
1502
|
+
if (Array.isArray(arg)) {
|
|
1503
|
+
return {
|
|
1504
|
+
[key]: arg.map((e) => remapRefRoots(e, resultRoot, itemRoot))
|
|
1505
|
+
};
|
|
1506
|
+
}
|
|
1507
|
+
return {
|
|
1508
|
+
[key]: remapRefRoots(arg, resultRoot, itemRoot)
|
|
1509
|
+
};
|
|
1510
|
+
}
|
|
1511
|
+
return node;
|
|
1512
|
+
}
|
|
1513
|
+
function stableJson(value) {
|
|
1514
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
1515
|
+
if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
|
|
1516
|
+
const keys = Object.keys(value).sort();
|
|
1517
|
+
const body = keys.map((k) => `${JSON.stringify(k)}:${stableJson(value[k])}`).join(",");
|
|
1518
|
+
return `{${body}}`;
|
|
1519
|
+
}
|
|
1520
|
+
function collectParamLeaves(tree, out) {
|
|
1521
|
+
if (tree === null || typeof tree !== "object") return;
|
|
1522
|
+
if (Array.isArray(tree)) {
|
|
1523
|
+
for (const e of tree) collectParamLeaves(e, out);
|
|
1524
|
+
return;
|
|
1525
|
+
}
|
|
1526
|
+
const rec = tree;
|
|
1527
|
+
if (typeof rec.$param === "string" && Object.keys(rec).length === 1) {
|
|
1528
|
+
out.add(rec.$param);
|
|
1529
|
+
return;
|
|
1530
|
+
}
|
|
1531
|
+
for (const v of Object.values(rec)) collectParamLeaves(v, out);
|
|
1532
|
+
}
|
|
1533
|
+
function pathToId(resultPath) {
|
|
1534
|
+
if (resultPath === "$") return "root";
|
|
1535
|
+
return resultPath.startsWith("$.") ? resultPath.slice(2) : resultPath;
|
|
1536
|
+
}
|
|
1537
|
+
function parentPath(op) {
|
|
1538
|
+
const tokens = op.resultPath.split(".");
|
|
1539
|
+
const drop = op.resultPath.endsWith(".items") ? 2 : 1;
|
|
1540
|
+
const p = tokens.slice(0, tokens.length - drop).join(".");
|
|
1541
|
+
return p === "" ? "$" : p;
|
|
1542
|
+
}
|
|
1543
|
+
function relationProp(op) {
|
|
1544
|
+
const tokens = op.resultPath.split(".");
|
|
1545
|
+
const idx = op.resultPath.endsWith(".items") ? tokens.length - 2 : tokens.length - 1;
|
|
1546
|
+
return tokens[idx];
|
|
1547
|
+
}
|
|
1548
|
+
function relationKindOf(op) {
|
|
1549
|
+
return op.resultPath.endsWith(".items") ? "connection" : "single";
|
|
1550
|
+
}
|
|
1551
|
+
function readPorts(op, resultRoot) {
|
|
1552
|
+
const ports = { table: op.tableName };
|
|
1553
|
+
if (op.indexName !== void 0) ports.indexName = op.indexName;
|
|
1554
|
+
for (const [attr, node] of Object.entries(op.keyExpr ?? {})) {
|
|
1555
|
+
ports[attr] = remapRefRoots(node, resultRoot);
|
|
1556
|
+
}
|
|
1557
|
+
if (op.rangeCondition !== void 0) {
|
|
1558
|
+
ports.rangeKey = op.rangeCondition.key;
|
|
1559
|
+
ports.rangeOperator = op.rangeCondition.operator;
|
|
1560
|
+
ports.rangeValue = remapRefRoots(
|
|
1561
|
+
op.rangeCondition.valueExpr ?? "",
|
|
1562
|
+
resultRoot
|
|
1563
|
+
);
|
|
1564
|
+
}
|
|
1565
|
+
if (op.limit !== void 0) ports.limit = op.limit;
|
|
1566
|
+
if (op.filter !== void 0) ports.filter = stableJson(op.filter.declarative);
|
|
1567
|
+
ports.projection = { arr: [...op.projection] };
|
|
1568
|
+
return ports;
|
|
1569
|
+
}
|
|
1570
|
+
function buildQueryComponent(name, query) {
|
|
1571
|
+
const ops = query.operations;
|
|
1572
|
+
const idByPath = /* @__PURE__ */ new Map();
|
|
1573
|
+
const opByPath = /* @__PURE__ */ new Map();
|
|
1574
|
+
for (const op of ops) {
|
|
1575
|
+
idByPath.set(op.resultPath, pathToId(op.resultPath));
|
|
1576
|
+
opByPath.set(op.resultPath, op);
|
|
1577
|
+
}
|
|
1578
|
+
const lineages = /* @__PURE__ */ new Map();
|
|
1579
|
+
const elementInfo = /* @__PURE__ */ new Map();
|
|
1580
|
+
const body = ops.map((op) => {
|
|
1581
|
+
const id = pathToId(op.resultPath);
|
|
1582
|
+
if (op.resultPath === "$") {
|
|
1583
|
+
const ports = readPorts(op, []);
|
|
1584
|
+
if (query.cardinality === "one" && op.type === "Query") ports.cardinality = "one";
|
|
1585
|
+
const node2 = { id, component: op.type, ports };
|
|
1586
|
+
return node2;
|
|
1587
|
+
}
|
|
1588
|
+
const reject = (kind) => {
|
|
1589
|
+
throw new Error(`buildQueryComponent('${name}'): op '${op.resultPath}': ${kind}`);
|
|
1590
|
+
};
|
|
1591
|
+
const pp = parentPath(op);
|
|
1592
|
+
const producerId = idByPath.get(pp) ?? reject(`no producer op at '${pp}'`);
|
|
1593
|
+
if (op.sourceList !== void 0) {
|
|
1594
|
+
if (pp !== "$") reject("nested sourceList not expressible (v2 map)");
|
|
1595
|
+
const as = op.sourceList.as ?? `$${op.sourceList.key}`;
|
|
1596
|
+
const over = remapRefRoots(
|
|
1597
|
+
op.sourceList.over ?? { ref: ["result", op.sourceList.from] },
|
|
1598
|
+
[producerId]
|
|
1599
|
+
);
|
|
1600
|
+
const ports = readPorts(op, [as]);
|
|
1601
|
+
if (op.sourceList.implicit === true) ports.implicitSourceField = op.sourceList.from;
|
|
1602
|
+
const node2 = {
|
|
1603
|
+
id,
|
|
1604
|
+
map: {
|
|
1605
|
+
over,
|
|
1606
|
+
as,
|
|
1607
|
+
component: op.type,
|
|
1608
|
+
ports,
|
|
1609
|
+
parent: producerId,
|
|
1610
|
+
relationKind: relationKindOf(op),
|
|
1611
|
+
batched: true
|
|
1612
|
+
}
|
|
1613
|
+
};
|
|
1614
|
+
lineages.set(op.resultPath, { tailExpr: { ref: [id] }, tailId: id });
|
|
1615
|
+
return node2;
|
|
1616
|
+
}
|
|
1617
|
+
const producerOp = opByPath.get(pp);
|
|
1618
|
+
const producerIsConnection = producerOp !== void 0 && pp.endsWith(".items");
|
|
1619
|
+
const producerElement = elementInfo.get(pp);
|
|
1620
|
+
if (producerIsConnection || producerElement !== void 0) {
|
|
1621
|
+
const lineageKey = producerElement?.lineageKey ?? pp;
|
|
1622
|
+
const lineage = producerElement !== void 0 && pp.endsWith(".items") ? reject("nested per-element connection not expressible (v2 map)") : lineages.get(lineageKey) ?? reject(`no element chain for '${lineageKey}'`);
|
|
1623
|
+
const as = "$el";
|
|
1624
|
+
const attach = producerElement?.attachSeg ?? [];
|
|
1625
|
+
const into = relationProp(op);
|
|
1626
|
+
const node2 = {
|
|
1627
|
+
id,
|
|
1628
|
+
map: {
|
|
1629
|
+
over: lineage.tailExpr,
|
|
1630
|
+
as,
|
|
1631
|
+
component: op.type,
|
|
1632
|
+
ports: readPorts(op, [as, ...attach]),
|
|
1633
|
+
parent: lineage.tailId,
|
|
1634
|
+
relationKind: relationKindOf(op),
|
|
1635
|
+
into,
|
|
1636
|
+
// A belongsTo child the old path resolves via ONE deduped BatchGetItem
|
|
1637
|
+
// (gap 6) is batched; a per-element Query stays per-element.
|
|
1638
|
+
...op.type === "BatchGetItem" ? { batched: true } : {}
|
|
1639
|
+
}
|
|
1640
|
+
};
|
|
1641
|
+
lineages.set(lineageKey, { tailExpr: { ref: [id] }, tailId: id });
|
|
1642
|
+
elementInfo.set(op.resultPath, { lineageKey, attachSeg: [into] });
|
|
1643
|
+
return node2;
|
|
1644
|
+
}
|
|
1645
|
+
const node = {
|
|
1646
|
+
id,
|
|
1647
|
+
component: op.type,
|
|
1648
|
+
ports: readPorts(op, [producerId]),
|
|
1649
|
+
parent: producerId,
|
|
1650
|
+
...op.sourceField !== void 0 ? { bindField: op.sourceField } : {},
|
|
1651
|
+
relationKind: relationKindOf(op)
|
|
1652
|
+
};
|
|
1653
|
+
if (relationKindOf(op) === "connection") {
|
|
1654
|
+
lineages.set(op.resultPath, { tailExpr: { ref: [id, "items"] }, tailId: id });
|
|
1655
|
+
}
|
|
1656
|
+
return node;
|
|
1657
|
+
});
|
|
1658
|
+
const relOps = ops.filter((op) => op.resultPath !== "$");
|
|
1659
|
+
let output;
|
|
1660
|
+
if (relOps.length === 0) {
|
|
1661
|
+
output = { ref: ["root"] };
|
|
1662
|
+
} else {
|
|
1663
|
+
const outputObj = { root: { ref: ["root"] } };
|
|
1664
|
+
for (const op of relOps) {
|
|
1665
|
+
const id = pathToId(op.resultPath);
|
|
1666
|
+
if (elementInfo.has(op.resultPath)) continue;
|
|
1667
|
+
const lineage = lineages.get(op.resultPath);
|
|
1668
|
+
const key = parentPath(op) === "$" ? relationProp(op) : id;
|
|
1669
|
+
if (lineage !== void 0 && lineage.tailId !== id && relationKindOf(op) === "connection" && op.sourceList === void 0) {
|
|
1670
|
+
outputObj[key] = {
|
|
1671
|
+
obj: { cursor: { ref: [id, "cursor"] }, items: lineage.tailExpr }
|
|
1672
|
+
};
|
|
1673
|
+
} else {
|
|
1674
|
+
outputObj[key] = { ref: [id] };
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
output = { obj: outputObj };
|
|
1678
|
+
}
|
|
1679
|
+
let plan;
|
|
1680
|
+
if (query.executionPlan) {
|
|
1681
|
+
const idToIndex = new Map(body.map((n, i) => [n.id, i]));
|
|
1682
|
+
const stageOf = [];
|
|
1683
|
+
const groups = [];
|
|
1684
|
+
body.forEach((n, i) => {
|
|
1685
|
+
const pid = "map" in n ? n.map.parent : "cond" in n ? n.cond.parent : n.parent;
|
|
1686
|
+
const stage = pid === void 0 ? 0 : stageOf[idToIndex.get(pid)] + 1;
|
|
1687
|
+
stageOf[i] = stage;
|
|
1688
|
+
(groups[stage] ??= []).push(i);
|
|
1689
|
+
});
|
|
1690
|
+
plan = { groups, concurrency: query.executionPlan.concurrency };
|
|
1691
|
+
}
|
|
1692
|
+
return registerComponent(name, portSchemas(query.params), body, output, plan ?? null);
|
|
1693
|
+
}
|
|
1694
|
+
function lowerTransactionTemplate(template) {
|
|
1695
|
+
const PLACEHOLDER2 = /\{([^{}]+)\}/g;
|
|
1696
|
+
const parts = [];
|
|
1697
|
+
let last = 0;
|
|
1698
|
+
PLACEHOLDER2.lastIndex = 0;
|
|
1699
|
+
for (let m = PLACEHOLDER2.exec(template); m !== null; m = PLACEHOLDER2.exec(template)) {
|
|
1700
|
+
const literal = template.slice(last, m.index);
|
|
1701
|
+
if (literal !== "") parts.push(literal);
|
|
1702
|
+
const token = m[1];
|
|
1703
|
+
parts.push(
|
|
1704
|
+
token.startsWith("item.") ? { ref: ["$item", token.slice("item.".length)] } : { ref: [token] }
|
|
1705
|
+
);
|
|
1706
|
+
last = m.index + m[0].length;
|
|
1707
|
+
}
|
|
1708
|
+
const tail = template.slice(last);
|
|
1709
|
+
if (tail !== "") parts.push(tail);
|
|
1710
|
+
if (parts.length === 0) return "";
|
|
1711
|
+
if (parts.length === 1) return parts[0];
|
|
1712
|
+
return { concat: parts };
|
|
1713
|
+
}
|
|
1714
|
+
function lowerTransactionValue(value) {
|
|
1715
|
+
return typeof value === "string" ? lowerTransactionTemplate(value) : value;
|
|
1716
|
+
}
|
|
1717
|
+
function conditionPorts(condition, ports) {
|
|
1718
|
+
ports["condition.kind"] = condition.kind;
|
|
1719
|
+
switch (condition.kind) {
|
|
1720
|
+
case "notExists":
|
|
1721
|
+
return;
|
|
1722
|
+
case "attributeExists":
|
|
1723
|
+
case "attributeNotExists":
|
|
1724
|
+
ports["condition.field"] = condition.field;
|
|
1725
|
+
return;
|
|
1726
|
+
case "equals": {
|
|
1727
|
+
for (const field of Object.keys(condition.fields).sort()) {
|
|
1728
|
+
ports[`condition.equals.${field}`] = lowerTransactionValue(condition.fields[field]);
|
|
1729
|
+
}
|
|
1730
|
+
return;
|
|
1731
|
+
}
|
|
1732
|
+
case "expr": {
|
|
1733
|
+
ports["condition.declarative"] = stableJson(condition.declarative);
|
|
1734
|
+
const params = /* @__PURE__ */ new Set();
|
|
1735
|
+
collectParamLeaves(condition.declarative, params);
|
|
1736
|
+
for (const p of [...params].sort()) {
|
|
1737
|
+
ports[`condition.params.${p}`] = { ref: [p] };
|
|
1738
|
+
}
|
|
1739
|
+
return;
|
|
1740
|
+
}
|
|
1741
|
+
case "raw": {
|
|
1742
|
+
ports["condition.expression"] = condition.expression;
|
|
1743
|
+
ports["condition.names"] = stableJson(condition.names);
|
|
1744
|
+
for (const slot of Object.keys(condition.values).sort()) {
|
|
1745
|
+
const v = condition.values[slot];
|
|
1746
|
+
const leaf = v !== null && typeof v === "object" && typeof v.$param === "string" ? { ref: [v.$param] } : v;
|
|
1747
|
+
ports[`condition.values.${slot}`] = leaf;
|
|
1748
|
+
}
|
|
1749
|
+
return;
|
|
1750
|
+
}
|
|
1751
|
+
case "scpExpr":
|
|
1752
|
+
ports["condition.expression"] = stableJson(condition.expression);
|
|
1753
|
+
return;
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
function transactionItemPorts(item) {
|
|
1757
|
+
const ports = { table: item.tableName, entity: item.entity };
|
|
1758
|
+
const record = (src, group) => {
|
|
1759
|
+
if (src === void 0) return;
|
|
1760
|
+
for (const [field, value] of Object.entries(src)) {
|
|
1761
|
+
ports[`${group}.${field}`] = lowerTransactionValue(value);
|
|
1762
|
+
}
|
|
1763
|
+
};
|
|
1764
|
+
record(item.item, "item");
|
|
1765
|
+
record(item.keyCondition, "key");
|
|
1766
|
+
record(item.changes, "changes");
|
|
1767
|
+
record(item.add, "add");
|
|
1768
|
+
if (item.literalKey === true) ports.literalKey = true;
|
|
1769
|
+
if (item.condition !== void 0) conditionPorts(item.condition, ports);
|
|
1770
|
+
if (item.maintain !== void 0) ports.maintain = stableJson(item.maintain);
|
|
1771
|
+
return ports;
|
|
1772
|
+
}
|
|
1773
|
+
function lowerItemGuard(item) {
|
|
1774
|
+
if (item.guard !== void 0) {
|
|
1775
|
+
return remapRefRoots(item.guard.expr, [], ["$item"]);
|
|
1776
|
+
}
|
|
1777
|
+
const when = item.when;
|
|
1778
|
+
if (when === void 0) return void 0;
|
|
1779
|
+
const args = [lowerTransactionTemplate(when.left), lowerTransactionTemplate(when.right)];
|
|
1780
|
+
return when.op === "eq" ? { eq: args } : { ne: args };
|
|
1781
|
+
}
|
|
1782
|
+
function buildTransactionComponent(name, tx) {
|
|
1783
|
+
const catalog = {
|
|
1784
|
+
Put: "PutItem",
|
|
1785
|
+
Update: "UpdateItem",
|
|
1786
|
+
Delete: "DeleteItem",
|
|
1787
|
+
ConditionCheck: "ConditionCheck"
|
|
1788
|
+
};
|
|
1789
|
+
const body = tx.items.map((item, i) => {
|
|
1790
|
+
const id = `item${i}`;
|
|
1791
|
+
const component = catalog[item.type];
|
|
1792
|
+
const when = lowerItemGuard(item);
|
|
1793
|
+
if (tx.batchWrite === true) {
|
|
1794
|
+
const reject = (kind) => {
|
|
1795
|
+
throw new Error(
|
|
1796
|
+
`buildTransactionComponent('${name}'): batchWrite item${i}: ${kind} \u2014 DynamoDB BatchWriteItem carries only unconditional PutRequest / DeleteRequest (internal invariant; the emitter synthesizes this form).`
|
|
1797
|
+
);
|
|
1798
|
+
};
|
|
1799
|
+
if (component !== "PutItem" && component !== "DeleteItem") reject(`'${component}' item`);
|
|
1800
|
+
if (item.condition !== void 0) reject("a write condition");
|
|
1801
|
+
if (when !== void 0) reject("a when/guard");
|
|
1802
|
+
if (item.forEach === void 0) reject("no forEach (the bulk form is forEach-over-items)");
|
|
1803
|
+
const node2 = {
|
|
1804
|
+
id,
|
|
1805
|
+
map: {
|
|
1806
|
+
over: { ref: [item.forEach.source] },
|
|
1807
|
+
as: "$item",
|
|
1808
|
+
component,
|
|
1809
|
+
ports: transactionItemPorts(item),
|
|
1810
|
+
batched: true
|
|
1811
|
+
}
|
|
1812
|
+
};
|
|
1813
|
+
return node2;
|
|
1814
|
+
}
|
|
1815
|
+
if (item.forEach !== void 0) {
|
|
1816
|
+
const node2 = {
|
|
1817
|
+
id,
|
|
1818
|
+
map: {
|
|
1819
|
+
over: { ref: [item.forEach.source] },
|
|
1820
|
+
as: "$item",
|
|
1821
|
+
component,
|
|
1822
|
+
ports: transactionItemPorts(item),
|
|
1823
|
+
...when !== void 0 ? { when } : {}
|
|
1824
|
+
}
|
|
1825
|
+
};
|
|
1826
|
+
return node2;
|
|
1827
|
+
}
|
|
1828
|
+
if (when !== void 0) {
|
|
1829
|
+
const node2 = {
|
|
1830
|
+
id,
|
|
1831
|
+
map: {
|
|
1832
|
+
over: { arr: [true] },
|
|
1833
|
+
as: "$item",
|
|
1834
|
+
component,
|
|
1835
|
+
ports: transactionItemPorts(item),
|
|
1836
|
+
when
|
|
1837
|
+
}
|
|
1838
|
+
};
|
|
1839
|
+
return node2;
|
|
1840
|
+
}
|
|
1841
|
+
const node = { id, component, ports: transactionItemPorts(item) };
|
|
1842
|
+
return node;
|
|
1843
|
+
});
|
|
1844
|
+
return registerComponent(
|
|
1845
|
+
name,
|
|
1846
|
+
portSchemas(tx.params),
|
|
1847
|
+
body,
|
|
1848
|
+
{ arr: body.map((n) => ({ ref: [n.id] })) },
|
|
1849
|
+
null
|
|
1850
|
+
);
|
|
1851
|
+
}
|
|
1852
|
+
function portSchemas(params) {
|
|
1853
|
+
const out = {};
|
|
1854
|
+
for (const name of Object.keys(params).sort()) {
|
|
1855
|
+
const p = params[name];
|
|
1856
|
+
out[name] = { type: p.type, required: p.required };
|
|
1857
|
+
}
|
|
1858
|
+
return out;
|
|
1859
|
+
}
|
|
1860
|
+
function buildComponents(queries, transactions) {
|
|
1861
|
+
const components = [];
|
|
1862
|
+
for (const name of Object.keys(queries).sort()) {
|
|
1863
|
+
components.push(buildQueryComponent(name, queries[name]));
|
|
1864
|
+
}
|
|
1865
|
+
for (const name of Object.keys(transactions).sort()) {
|
|
1866
|
+
components.push(buildTransactionComponent(name, transactions[name]));
|
|
1867
|
+
}
|
|
1868
|
+
assertComponentsInCatalog(components);
|
|
1869
|
+
return components;
|
|
1870
|
+
}
|
|
1871
|
+
function portableIrDocument(components) {
|
|
1872
|
+
return {
|
|
1873
|
+
irVersion: 1,
|
|
1874
|
+
exprVersion: EXPR_VERSION,
|
|
1875
|
+
components: [...components]
|
|
1876
|
+
};
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1317
1879
|
// src/spec/operations.ts
|
|
1318
1880
|
function paramSpecs(params) {
|
|
1319
1881
|
const out = {};
|
|
@@ -1332,11 +1894,12 @@ function paramSpecs(params) {
|
|
|
1332
1894
|
function metaFor(def) {
|
|
1333
1895
|
return MetadataRegistry.get(def.entity.modelClass);
|
|
1334
1896
|
}
|
|
1335
|
-
function
|
|
1897
|
+
function templateValueLeaf(key, value) {
|
|
1336
1898
|
if (isContractParamRef(value)) return value.token;
|
|
1337
1899
|
if (isContractKeyFieldRef(value)) return `{${value.field}}`;
|
|
1338
1900
|
if (isParam(value)) return `{${key}}`;
|
|
1339
1901
|
if (value instanceof Date) return value.toISOString();
|
|
1902
|
+
if (typeof value === "number" || typeof value === "boolean") return value;
|
|
1340
1903
|
return String(value);
|
|
1341
1904
|
}
|
|
1342
1905
|
function keyFieldNames(key) {
|
|
@@ -1415,7 +1978,20 @@ function buildReadOperations(def) {
|
|
|
1415
1978
|
};
|
|
1416
1979
|
const ops = [root];
|
|
1417
1980
|
ops.push(...buildRelationOperations(metadata, select, "$"));
|
|
1418
|
-
return injectRefsSourceProjections(ops);
|
|
1981
|
+
return injectRefsSourceProjections(ops).map(lowerOperationKeyExprs);
|
|
1982
|
+
}
|
|
1983
|
+
function lowerOperationKeyExprs(op) {
|
|
1984
|
+
const keyExpr = op.keyCondition ? lowerKeyCondition(op.keyCondition) : {};
|
|
1985
|
+
const { keyCondition: _dropKeyCondition, rangeCondition, ...rest } = op;
|
|
1986
|
+
const nextRange = rangeCondition ? (() => {
|
|
1987
|
+
const { value: _dropValue, ...rc } = rangeCondition;
|
|
1988
|
+
return { ...rc, valueExpr: lowerKeyTemplate(rangeCondition.value ?? "") };
|
|
1989
|
+
})() : void 0;
|
|
1990
|
+
return {
|
|
1991
|
+
...rest,
|
|
1992
|
+
keyExpr,
|
|
1993
|
+
...nextRange ? { rangeCondition: nextRange } : {}
|
|
1994
|
+
};
|
|
1419
1995
|
}
|
|
1420
1996
|
function projectionFields(select) {
|
|
1421
1997
|
const out = [];
|
|
@@ -1424,7 +2000,7 @@ function projectionFields(select) {
|
|
|
1424
2000
|
}
|
|
1425
2001
|
return out.sort();
|
|
1426
2002
|
}
|
|
1427
|
-
function buildRelationOperations(metadata, select,
|
|
2003
|
+
function buildRelationOperations(metadata, select, parentPath2) {
|
|
1428
2004
|
const ops = [];
|
|
1429
2005
|
const relations = detectRelationFields(select, metadata);
|
|
1430
2006
|
for (const rel of [...relations].sort(
|
|
@@ -1434,7 +2010,7 @@ function buildRelationOperations(metadata, select, parentPath) {
|
|
|
1434
2010
|
const childSelect = spec.select ?? {};
|
|
1435
2011
|
const targetClass = rel.targetFactory();
|
|
1436
2012
|
const targetMeta = MetadataRegistry.get(targetClass);
|
|
1437
|
-
const childPath = `${
|
|
2013
|
+
const childPath = `${parentPath2}.${rel.propertyName}`;
|
|
1438
2014
|
if (rel.type === "refs") {
|
|
1439
2015
|
if (spec.filter !== void 0) {
|
|
1440
2016
|
throw new Error(
|
|
@@ -1587,7 +2163,16 @@ function buildRefsOperation(rel, targetMeta, select, resultPath) {
|
|
|
1587
2163
|
projection: projectionFields(select),
|
|
1588
2164
|
resultPath,
|
|
1589
2165
|
sourceField,
|
|
1590
|
-
|
|
2166
|
+
// #289 P2-5: the sourceList carries its Phase-3 Map shape prep — `over` is
|
|
2167
|
+
// the Expression-IR ref to the parent list attribute the Map iterates, `as`
|
|
2168
|
+
// is the per-element binding name. Descriptive only in Phase 2 (behavior is
|
|
2169
|
+
// still driven by `from`/`key`); Phase 3 lowers the fan-out body onto them.
|
|
2170
|
+
sourceList: {
|
|
2171
|
+
from: refs.from,
|
|
2172
|
+
key: refs.key,
|
|
2173
|
+
over: { ref: ["result", refs.from] },
|
|
2174
|
+
as: `$${refs.key}`
|
|
2175
|
+
}
|
|
1591
2176
|
};
|
|
1592
2177
|
}
|
|
1593
2178
|
function injectRefsSourceProjections(ops) {
|
|
@@ -1595,11 +2180,11 @@ function injectRefsSourceProjections(ops) {
|
|
|
1595
2180
|
const op = ops[i];
|
|
1596
2181
|
if (op.sourceList === void 0) continue;
|
|
1597
2182
|
const tokens = op.resultPath.split(".");
|
|
1598
|
-
const
|
|
1599
|
-
const parentIndex = ops.findIndex((p) => p.resultPath ===
|
|
2183
|
+
const parentPath2 = tokens.slice(0, -2).join(".") || "$";
|
|
2184
|
+
const parentIndex = ops.findIndex((p) => p.resultPath === parentPath2);
|
|
1600
2185
|
if (parentIndex === -1) {
|
|
1601
2186
|
throw new Error(
|
|
1602
|
-
`refs fan-out at '${op.resultPath}': no parent operation at '${
|
|
2187
|
+
`refs fan-out at '${op.resultPath}': no parent operation at '${parentPath2}'.`
|
|
1603
2188
|
);
|
|
1604
2189
|
}
|
|
1605
2190
|
const parent = ops[parentIndex];
|
|
@@ -1648,6 +2233,8 @@ function buildCommandSpec(def) {
|
|
|
1648
2233
|
...description
|
|
1649
2234
|
};
|
|
1650
2235
|
}
|
|
2236
|
+
const addStructure = def.add;
|
|
2237
|
+
const add = addStructure !== void 0 && Object.keys(addStructure).length > 0 ? { add: templateRecord(addStructure) } : {};
|
|
1651
2238
|
return {
|
|
1652
2239
|
type: "UpdateItem",
|
|
1653
2240
|
tableName,
|
|
@@ -1655,6 +2242,7 @@ function buildCommandSpec(def) {
|
|
|
1655
2242
|
params,
|
|
1656
2243
|
keyCondition: writeKeyCondition(metadata, def.key, entity),
|
|
1657
2244
|
changes: templateRecord(def.changes),
|
|
2245
|
+
...add,
|
|
1658
2246
|
...condition ? { condition } : {},
|
|
1659
2247
|
...description
|
|
1660
2248
|
};
|
|
@@ -1681,7 +2269,7 @@ function writeKeyCondition(metadata, key, entity) {
|
|
|
1681
2269
|
function templateRecord(structure) {
|
|
1682
2270
|
const out = {};
|
|
1683
2271
|
for (const key of Object.keys(structure).sort()) {
|
|
1684
|
-
out[key] =
|
|
2272
|
+
out[key] = templateValueLeaf(key, structure[key]);
|
|
1685
2273
|
}
|
|
1686
2274
|
return out;
|
|
1687
2275
|
}
|
|
@@ -1689,7 +2277,7 @@ function extractCondition(def) {
|
|
|
1689
2277
|
return conditionInputToSpec(
|
|
1690
2278
|
def.condition,
|
|
1691
2279
|
`Command on '${def.entity.name}'`,
|
|
1692
|
-
(field, value) =>
|
|
2280
|
+
(field, value) => templateValueLeaf(field, value),
|
|
1693
2281
|
(value, name) => conditionTreeLeaf(value, name),
|
|
1694
2282
|
// A raw `cond` value slot (issue #114-B): a contract param / key ref carries
|
|
1695
2283
|
// its own name; a plain `param.*` falls through to the positional default.
|
|
@@ -1728,6 +2316,27 @@ function buildQuerySpec(def) {
|
|
|
1728
2316
|
...def.description !== void 0 ? { description: def.description } : {}
|
|
1729
2317
|
};
|
|
1730
2318
|
}
|
|
2319
|
+
function toPortableQuery(name, spec) {
|
|
2320
|
+
const root = spec.operations[0];
|
|
2321
|
+
if (root === void 0 || root.resultPath !== "$") {
|
|
2322
|
+
throw new Error(
|
|
2323
|
+
`toPortableQuery('${name}'): expected the root op at operations[0] (resultPath '$'); got ${root === void 0 ? "no operations" : `'${root.resultPath}'`}.`
|
|
2324
|
+
);
|
|
2325
|
+
}
|
|
2326
|
+
const { resultPath: _rp, sourceField: _sf, sourceList: _sl, ...portableRoot } = root;
|
|
2327
|
+
return {
|
|
2328
|
+
params: spec.params,
|
|
2329
|
+
operations: [portableRoot],
|
|
2330
|
+
...spec.cardinality !== void 0 ? { cardinality: spec.cardinality } : {},
|
|
2331
|
+
...spec.description !== void 0 ? { description: spec.description } : {}
|
|
2332
|
+
};
|
|
2333
|
+
}
|
|
2334
|
+
function toPortableTransaction(spec) {
|
|
2335
|
+
return {
|
|
2336
|
+
params: spec.params,
|
|
2337
|
+
...spec.batchWrite === true ? { batchWrite: true } : {}
|
|
2338
|
+
};
|
|
2339
|
+
}
|
|
1731
2340
|
function mergeSpecs(base, synthesized, kind) {
|
|
1732
2341
|
for (const name of Object.keys(synthesized)) {
|
|
1733
2342
|
if (Object.prototype.hasOwnProperty.call(base, name)) {
|
|
@@ -1759,16 +2368,55 @@ function buildOperations(queries = {}, commands = {}, transactions = {}, contrac
|
|
|
1759
2368
|
mergeSpecs(transactionSpecs, built.transactions, "transaction");
|
|
1760
2369
|
const contractSpecs = built.contracts;
|
|
1761
2370
|
const contextSpecs = buildContexts(contextInputMap);
|
|
2371
|
+
const components = buildComponents(querySpecs, transactionSpecs);
|
|
2372
|
+
const behaviorNames = /* @__PURE__ */ new Set();
|
|
2373
|
+
{
|
|
2374
|
+
const behaviorInputs = contractInputs.behaviors ?? {};
|
|
2375
|
+
const taken = new Set(components.map((c) => c.name));
|
|
2376
|
+
for (const key of Object.keys(behaviorInputs).sort()) {
|
|
2377
|
+
for (const c of behaviorInputs[key].components) {
|
|
2378
|
+
const name = `${key}__${c.name}`;
|
|
2379
|
+
if (taken.has(name)) {
|
|
2380
|
+
throw new Error(
|
|
2381
|
+
`publishBehaviors registration '${key}' produced component '${name}', which collides with another emitted component. Rename the behavior registration or the method.`
|
|
2382
|
+
);
|
|
2383
|
+
}
|
|
2384
|
+
taken.add(name);
|
|
2385
|
+
behaviorNames.add(name);
|
|
2386
|
+
components.push({ ...c, name });
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
2389
|
+
if (behaviorNames.size > 0) assertComponentsInCatalog(components);
|
|
2390
|
+
}
|
|
2391
|
+
assertPublishSplitMatchesDerived(
|
|
2392
|
+
deriveComponentEffects(components),
|
|
2393
|
+
{
|
|
2394
|
+
queries: new Set(Object.keys(querySpecs)),
|
|
2395
|
+
transactions: new Set(Object.keys(transactionSpecs)),
|
|
2396
|
+
commands: new Set(Object.keys(commandSpecs)),
|
|
2397
|
+
behaviors: behaviorNames
|
|
2398
|
+
},
|
|
2399
|
+
contractSpecs
|
|
2400
|
+
);
|
|
2401
|
+
const portableQueries = {};
|
|
2402
|
+
for (const [name, spec] of Object.entries(querySpecs)) {
|
|
2403
|
+
portableQueries[name] = toPortableQuery(name, spec);
|
|
2404
|
+
}
|
|
2405
|
+
const portableTransactions = {};
|
|
2406
|
+
for (const [name, spec] of Object.entries(transactionSpecs)) {
|
|
2407
|
+
portableTransactions[name] = toPortableTransaction(spec);
|
|
2408
|
+
}
|
|
1762
2409
|
const content = {
|
|
1763
|
-
queries:
|
|
2410
|
+
queries: portableQueries,
|
|
1764
2411
|
commands: commandSpecs,
|
|
1765
|
-
...Object.keys(
|
|
2412
|
+
...Object.keys(portableTransactions).length > 0 ? { transactions: portableTransactions } : {},
|
|
1766
2413
|
...Object.keys(contractSpecs).length > 0 ? { contracts: contractSpecs } : {},
|
|
1767
2414
|
...Object.keys(contextSpecs).length > 0 ? { contexts: contextSpecs } : {}
|
|
1768
2415
|
};
|
|
1769
2416
|
return {
|
|
1770
2417
|
version: operationsSpecVersion(content),
|
|
1771
|
-
...content
|
|
2418
|
+
...content,
|
|
2419
|
+
...components.length > 0 ? { components } : {}
|
|
1772
2420
|
};
|
|
1773
2421
|
}
|
|
1774
2422
|
|
|
@@ -1957,7 +2605,9 @@ function buildPreparedPlanDocument(plans) {
|
|
|
1957
2605
|
for (const id of Object.keys(plans).sort()) sorted[id] = plans[id];
|
|
1958
2606
|
return {
|
|
1959
2607
|
formatVersion: PREPARED_FORMAT_VERSION,
|
|
1960
|
-
|
|
2608
|
+
// #289 Phase 2: the embedded operations IR is 2.0 (major bump); the prepared
|
|
2609
|
+
// artifact's specVersion tracks it so a version-skewed plan fails closed.
|
|
2610
|
+
specVersion: SPEC_VERSION_KEY_EXPR,
|
|
1961
2611
|
plans: sorted
|
|
1962
2612
|
};
|
|
1963
2613
|
}
|
|
@@ -1973,12 +2623,17 @@ function buildBridgeBundle(queries = {}, commands = {}, registry = MetadataRegis
|
|
|
1973
2623
|
}
|
|
1974
2624
|
|
|
1975
2625
|
export {
|
|
2626
|
+
renderKeyExprToTemplate,
|
|
1976
2627
|
collectContractN1Violations,
|
|
1977
2628
|
assertContractN1Safe,
|
|
1978
2629
|
buildContracts,
|
|
1979
2630
|
buildContexts,
|
|
1980
2631
|
collectContractBoundaryViolations,
|
|
1981
2632
|
assertContractBoundaries,
|
|
2633
|
+
buildQueryComponent,
|
|
2634
|
+
buildTransactionComponent,
|
|
2635
|
+
buildComponents,
|
|
2636
|
+
portableIrDocument,
|
|
1982
2637
|
buildQuerySpec,
|
|
1983
2638
|
buildOperations,
|
|
1984
2639
|
compilePreparedPlan,
|