graphddb 0.7.9 → 0.8.0
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/README.md +6 -6
- package/dist/cdc/index.d.ts +389 -4
- package/dist/cdc/index.js +4 -3
- package/dist/{chunk-ZPNRLOKA.js → chunk-GS4C5VGO.js} +4 -6
- package/dist/chunk-HNY2EJPV.js +1184 -0
- package/dist/{chunk-NYM7K2ST.js → chunk-I4LEJ4TF.js} +3812 -6724
- package/dist/{chunk-PFFPLD4B.js → chunk-L2NEDS7U.js} +725 -2112
- package/dist/chunk-L4QRCHRQ.js +278 -0
- package/dist/chunk-LGHSZIEE.js +187 -0
- package/dist/chunk-N4NWYNGZ.js +1987 -0
- package/dist/{chunk-PDUVTYC5.js → chunk-XTWXMOHD.js} +0 -1
- package/dist/cli.js +63 -252
- package/dist/index.d.ts +23 -1548
- package/dist/index.js +100 -1778
- package/dist/internal/index.d.ts +84 -0
- package/dist/internal/index.js +701 -0
- package/dist/{maintenance-view-adapter-BATUh_I8.d.ts → key-DR7_lpyk.d.ts} +538 -2975
- package/dist/linter/index.d.ts +39 -6
- package/dist/linter/index.js +22 -4
- package/dist/{registry-CXhP4TaE.d.ts → linter-C-vypgut.d.ts} +22 -22
- package/dist/prepared-artifact-BpPgkXEo.d.ts +281 -0
- package/dist/spec/index.d.ts +506 -4
- package/dist/spec/index.js +36 -17
- package/dist/testing/index.d.ts +2 -2
- package/dist/testing/index.js +4 -3
- package/dist/transform/index.d.ts +460 -1
- package/dist/transform/index.js +2085 -2
- package/dist/types-2PMXEn5x.d.ts +1205 -0
- package/dist/types-BXLzIcQD.d.ts +450 -0
- package/docs/cdc-projection.md +5 -5
- package/docs/class-hydration.md +1 -1
- package/docs/cqrs-contract.md +28 -20
- package/docs/design-patterns.md +5 -5
- package/docs/docs-generation.md +6 -6
- package/docs/middleware.md +15 -15
- package/docs/mutation-command-derivation.md +52 -42
- package/docs/prepared-statements.md +14 -14
- package/docs/python-bridge.md +113 -66
- package/docs/spec.md +153 -124
- package/docs/testing.md +9 -8
- package/package.json +20 -5
- package/dist/chunk-MMVHOUM4.js +0 -24
- package/dist/from-change-DanwjE5b.d.ts +0 -327
- package/dist/index-CtPJSMrc.d.ts +0 -934
- package/dist/relation-depth-Dg3yhl7S.d.ts +0 -36
|
@@ -0,0 +1,701 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PREPARED_FORMAT_VERSION,
|
|
3
|
+
buildManifestEntity,
|
|
4
|
+
entityFingerprint
|
|
5
|
+
} from "../chunk-LGHSZIEE.js";
|
|
6
|
+
import {
|
|
7
|
+
LOGICAL_EFFECT_CATEGORIES,
|
|
8
|
+
PreparedReadStatement,
|
|
9
|
+
executeLogicalParallelWrites,
|
|
10
|
+
executeLogicalWriteOps,
|
|
11
|
+
serializeEffectParams
|
|
12
|
+
} from "../chunk-I4LEJ4TF.js";
|
|
13
|
+
import "../chunk-HNY2EJPV.js";
|
|
14
|
+
import {
|
|
15
|
+
MARKER_ROW_ENTITY,
|
|
16
|
+
SPEC_VERSION,
|
|
17
|
+
SPEC_VERSION_SUPPORTED
|
|
18
|
+
} from "../chunk-L4QRCHRQ.js";
|
|
19
|
+
import {
|
|
20
|
+
MetadataRegistry,
|
|
21
|
+
buildConditionExpression,
|
|
22
|
+
execItemKeySignature,
|
|
23
|
+
resolveConditionTree
|
|
24
|
+
} from "../chunk-L2NEDS7U.js";
|
|
25
|
+
import {
|
|
26
|
+
TableMapping
|
|
27
|
+
} from "../chunk-XTWXMOHD.js";
|
|
28
|
+
|
|
29
|
+
// src/runtime/prepared-loader.ts
|
|
30
|
+
import { validateEnvelope, EnvelopeFailure } from "behavior-contracts";
|
|
31
|
+
|
|
32
|
+
// src/operations/declarative-transaction.ts
|
|
33
|
+
import { renderTemplate, TemplateFailure } from "behavior-contracts";
|
|
34
|
+
|
|
35
|
+
// src/runtime/behavior-contracts-adapter.ts
|
|
36
|
+
function toSharedValue(v) {
|
|
37
|
+
if (v === null || v === void 0) return null;
|
|
38
|
+
switch (typeof v) {
|
|
39
|
+
case "string":
|
|
40
|
+
case "boolean":
|
|
41
|
+
case "bigint":
|
|
42
|
+
return v;
|
|
43
|
+
case "number":
|
|
44
|
+
return Number.isInteger(v) ? BigInt(v) : v;
|
|
45
|
+
default:
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
if (v instanceof Date) return v.toJSON();
|
|
49
|
+
if (Array.isArray(v)) return v.map((e) => toSharedValue(e));
|
|
50
|
+
if (typeof v === "object") {
|
|
51
|
+
const out = {};
|
|
52
|
+
for (const [k, field] of Object.entries(v)) {
|
|
53
|
+
if (field === void 0) continue;
|
|
54
|
+
out[k] = toSharedValue(field);
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
return v;
|
|
59
|
+
}
|
|
60
|
+
function toSharedParams(params) {
|
|
61
|
+
const out = {};
|
|
62
|
+
for (const [name, value] of Object.entries(params)) {
|
|
63
|
+
if (value === void 0) continue;
|
|
64
|
+
out[name] = toSharedValue(value);
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// src/runtime/guard-eval.ts
|
|
70
|
+
import { evaluateExpression, ExprFailure } from "behavior-contracts";
|
|
71
|
+
var GUARD_SCOPE_ROOTS = /* @__PURE__ */ new Set(["input", "item"]);
|
|
72
|
+
function assertGuardScopes(node, context) {
|
|
73
|
+
if (node === null || typeof node !== "object") return;
|
|
74
|
+
const obj = node;
|
|
75
|
+
const ref = obj.ref ?? obj.refOpt;
|
|
76
|
+
if (Array.isArray(ref)) {
|
|
77
|
+
const root = ref[0];
|
|
78
|
+
if (typeof root === "string" && !GUARD_SCOPE_ROOTS.has(root)) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
`${context}: this guard references '${root}', but a client-side guard may only reference the transaction 'input' / fan-out 'item' scopes. An expression that reads a DynamoDB attribute must lower to a ConditionExpression (write-condition class), not be evaluated client-side (issue #265). Refusing to evaluate a stale check.`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
for (const value of Object.values(obj)) {
|
|
86
|
+
if (Array.isArray(value)) {
|
|
87
|
+
for (const child of value) assertGuardScopes(child, context);
|
|
88
|
+
} else if (value !== null && typeof value === "object") {
|
|
89
|
+
for (const child of Object.values(value)) {
|
|
90
|
+
assertGuardScopes(child, context);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function evaluateGuard(guard, params, element, context) {
|
|
96
|
+
assertGuardScopes(guard.expr, context);
|
|
97
|
+
const scope = { input: toSharedParams(params) };
|
|
98
|
+
if (element !== void 0) {
|
|
99
|
+
scope.item = toSharedValue(element);
|
|
100
|
+
}
|
|
101
|
+
let result;
|
|
102
|
+
try {
|
|
103
|
+
result = evaluateExpression(guard.expr, scope);
|
|
104
|
+
} catch (exc) {
|
|
105
|
+
if (exc instanceof ExprFailure) {
|
|
106
|
+
throw new Error(`${context}: guard evaluation failed (${exc.code}): ${exc.message}`);
|
|
107
|
+
}
|
|
108
|
+
throw exc;
|
|
109
|
+
}
|
|
110
|
+
if (typeof result !== "boolean") {
|
|
111
|
+
throw new Error(
|
|
112
|
+
`${context}: a guard expression must evaluate to a boolean, got ${result === null ? "null" : typeof result} (issue #265).`
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// src/operations/declarative-transaction.ts
|
|
119
|
+
var PLACEHOLDER_RE = /\{[^{}]+\}/g;
|
|
120
|
+
function hasNonCoreScope(template) {
|
|
121
|
+
return (template.match(PLACEHOLDER_RE) ?? []).some((m) => {
|
|
122
|
+
const name = m.slice(1, -1);
|
|
123
|
+
return name.startsWith("item.") || name.startsWith("result.");
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
function resolveTemplate(template, params, element) {
|
|
127
|
+
if (hasNonCoreScope(template)) {
|
|
128
|
+
return template.replace(PLACEHOLDER_RE, (match) => {
|
|
129
|
+
const name = match.slice(1, -1);
|
|
130
|
+
if (name.startsWith("item.")) {
|
|
131
|
+
const field = name.slice("item.".length);
|
|
132
|
+
if (!element || !(field in element)) {
|
|
133
|
+
throw new Error(
|
|
134
|
+
`declarative transaction: element field '${field}' is not bound; template '${template}' is only valid inside a forEach over the array param that supplies '${field}'.`
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
return String(element[field]);
|
|
138
|
+
}
|
|
139
|
+
if (!(name in params) || params[name] === void 0 || params[name] === null) {
|
|
140
|
+
throw new Error(
|
|
141
|
+
`declarative transaction: template '${template}' references unbound parameter '${name}'.`
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
return String(params[name]);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
try {
|
|
148
|
+
return renderTemplate(template, toSharedParams(params));
|
|
149
|
+
} catch (exc) {
|
|
150
|
+
if (exc instanceof TemplateFailure && exc.code === "UNBOUND_PARAM") {
|
|
151
|
+
throw new Error(`declarative transaction: ${exc.message}.`);
|
|
152
|
+
}
|
|
153
|
+
throw exc;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
function isWholePlaceholder(template) {
|
|
157
|
+
return /^\{[^{}]+\}$/.test(template);
|
|
158
|
+
}
|
|
159
|
+
function resolveValue(template, params, element) {
|
|
160
|
+
if (typeof template !== "string") return template;
|
|
161
|
+
if (isWholePlaceholder(template)) {
|
|
162
|
+
const name = template.slice(1, -1);
|
|
163
|
+
if (name.startsWith("item.")) {
|
|
164
|
+
const field = name.slice("item.".length);
|
|
165
|
+
if (element && field in element) return element[field];
|
|
166
|
+
} else if (name in params) {
|
|
167
|
+
return params[name];
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return resolveTemplate(template, params, element);
|
|
171
|
+
}
|
|
172
|
+
function resolveRecord(record, params, element) {
|
|
173
|
+
const out = {};
|
|
174
|
+
for (const [field, tmpl] of Object.entries(record ?? {})) {
|
|
175
|
+
out[field] = resolveValue(tmpl, params, element);
|
|
176
|
+
}
|
|
177
|
+
return out;
|
|
178
|
+
}
|
|
179
|
+
function fillKeyTemplate(template, item) {
|
|
180
|
+
return template.replace(PLACEHOLDER_RE, (match) => {
|
|
181
|
+
const name = match.slice(1, -1);
|
|
182
|
+
return item[name] === void 0 ? "" : String(item[name]);
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
function keyTemplateFillable(template, item) {
|
|
186
|
+
const matches = template.match(PLACEHOLDER_RE) ?? [];
|
|
187
|
+
return matches.every((m) => m.slice(1, -1) in item);
|
|
188
|
+
}
|
|
189
|
+
function addKeyAttributes(entity, item) {
|
|
190
|
+
if (entity.key) {
|
|
191
|
+
item.PK = fillKeyTemplate(entity.key.pkTemplate, item);
|
|
192
|
+
if (entity.key.skTemplate !== null) {
|
|
193
|
+
item.SK = fillKeyTemplate(entity.key.skTemplate, item);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
for (const gsi of entity.gsis) {
|
|
197
|
+
if (keyTemplateFillable(gsi.pkTemplate, item)) {
|
|
198
|
+
item[`${gsi.indexName}PK`] = fillKeyTemplate(gsi.pkTemplate, item);
|
|
199
|
+
}
|
|
200
|
+
if (gsi.skTemplate !== null && keyTemplateFillable(gsi.skTemplate, item)) {
|
|
201
|
+
item[`${gsi.indexName}SK`] = fillKeyTemplate(gsi.skTemplate, item);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
function whenHolds(when, params, element) {
|
|
206
|
+
const left = resolveTemplate(when.left, params, element);
|
|
207
|
+
const right = resolveTemplate(when.right, params, element);
|
|
208
|
+
return when.op === "eq" ? left === right : left !== right;
|
|
209
|
+
}
|
|
210
|
+
function resolveRawConditionValue(val, params, element) {
|
|
211
|
+
if (val !== null && typeof val === "object" && typeof val.$param === "string") {
|
|
212
|
+
return resolveValue(`{${val.$param}}`, params, element);
|
|
213
|
+
}
|
|
214
|
+
return val;
|
|
215
|
+
}
|
|
216
|
+
function applyCondition(condition, target, params, element) {
|
|
217
|
+
if (!condition) return;
|
|
218
|
+
const names = target.ExpressionAttributeNames ??= {};
|
|
219
|
+
if (condition.kind === "notExists") {
|
|
220
|
+
names["#pk"] = "PK";
|
|
221
|
+
target.ConditionExpression = "attribute_not_exists(#pk)";
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
if (condition.kind === "attributeExists" || condition.kind === "attributeNotExists") {
|
|
225
|
+
const fn = condition.kind === "attributeExists" ? "attribute_exists" : "attribute_not_exists";
|
|
226
|
+
names["#ce"] = condition.field;
|
|
227
|
+
target.ConditionExpression = `${fn}(#ce)`;
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
const values = target.ExpressionAttributeValues ??= {};
|
|
231
|
+
if (condition.kind === "raw") {
|
|
232
|
+
Object.assign(names, condition.names);
|
|
233
|
+
for (const [alias, val] of Object.entries(condition.values)) {
|
|
234
|
+
values[alias] = resolveRawConditionValue(val, params, element);
|
|
235
|
+
}
|
|
236
|
+
target.ConditionExpression = condition.expression;
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
if (condition.kind === "scpExpr") {
|
|
240
|
+
throw new Error(
|
|
241
|
+
"declarative transaction: 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."
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
if (condition.kind === "expr") {
|
|
245
|
+
const concrete = resolveConditionTree(
|
|
246
|
+
condition.declarative,
|
|
247
|
+
(name) => resolveValue(`{${name}}`, params, element)
|
|
248
|
+
);
|
|
249
|
+
const compiled = buildConditionExpression(concrete);
|
|
250
|
+
Object.assign(names, compiled.names);
|
|
251
|
+
Object.assign(values, compiled.values);
|
|
252
|
+
target.ConditionExpression = compiled.expression;
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
const clauses = [];
|
|
256
|
+
let i = 0;
|
|
257
|
+
for (const [field, tmpl] of Object.entries(condition.fields)) {
|
|
258
|
+
const n = `#e${i}`;
|
|
259
|
+
const v = `:e${i}`;
|
|
260
|
+
names[n] = field;
|
|
261
|
+
values[v] = resolveValue(tmpl, params, element);
|
|
262
|
+
clauses.push(`${n} = ${v}`);
|
|
263
|
+
i++;
|
|
264
|
+
}
|
|
265
|
+
target.ConditionExpression = clauses.join(" AND ");
|
|
266
|
+
}
|
|
267
|
+
function entityByName(manifest, name) {
|
|
268
|
+
const entity = manifest.entities[name];
|
|
269
|
+
if (!entity) {
|
|
270
|
+
throw new Error(`declarative transaction: unknown entity '${name}' in manifest.`);
|
|
271
|
+
}
|
|
272
|
+
return entity;
|
|
273
|
+
}
|
|
274
|
+
function physicalTableName(manifest, logical) {
|
|
275
|
+
return manifest.tables[logical]?.physicalName ?? logical;
|
|
276
|
+
}
|
|
277
|
+
function applyMaintainTransform(op, args, value) {
|
|
278
|
+
if (op === "identity") return value;
|
|
279
|
+
if (op === "preview") {
|
|
280
|
+
const n = args[0];
|
|
281
|
+
if (typeof n !== "number" || !Number.isInteger(n) || n <= 0) {
|
|
282
|
+
throw new Error(
|
|
283
|
+
`declarative transaction: a maintenance \`preview\` projection has a non-positive-integer length bound (${JSON.stringify(n)}).`
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
if (value === null || value === void 0) return value;
|
|
287
|
+
return String(value).slice(0, n);
|
|
288
|
+
}
|
|
289
|
+
throw new Error(
|
|
290
|
+
`declarative transaction: unknown maintenance projection op '${String(op)}' (expected 'identity' / 'preview').`
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
function buildMaintainProjection(maintain, params, element) {
|
|
294
|
+
const out = {};
|
|
295
|
+
for (const [attr, transform] of Object.entries(maintain.projection)) {
|
|
296
|
+
const value = resolveValue(`{${transform.inputField}}`, params, element);
|
|
297
|
+
out[attr] = applyMaintainTransform(transform.op, transform.args, value);
|
|
298
|
+
}
|
|
299
|
+
return out;
|
|
300
|
+
}
|
|
301
|
+
function buildMaintainUpdateItem(spec, maintain, table, key, params, element) {
|
|
302
|
+
const update = { TableName: table, Key: key };
|
|
303
|
+
if (maintain.kind === "counter") {
|
|
304
|
+
const counter = maintain.counter;
|
|
305
|
+
if (counter === void 0) {
|
|
306
|
+
throw new Error(
|
|
307
|
+
`declarative transaction: a 'counter' maintenance write on '${spec.entity}' has no \`counter\` payload.`
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
update.UpdateExpression = "ADD #a0 :a0";
|
|
311
|
+
update.ExpressionAttributeNames = { "#a0": counter.attribute };
|
|
312
|
+
update.ExpressionAttributeValues = {
|
|
313
|
+
":a0": typeof counter.delta === "string" ? Number(counter.delta) : counter.delta
|
|
314
|
+
};
|
|
315
|
+
return { Update: update };
|
|
316
|
+
}
|
|
317
|
+
const projection = buildMaintainProjection(maintain, params, element);
|
|
318
|
+
if (maintain.kind === "collection") {
|
|
319
|
+
const field = maintain.collection?.field;
|
|
320
|
+
if (field === void 0) {
|
|
321
|
+
throw new Error(
|
|
322
|
+
`declarative transaction: a 'collection' maintenance write on '${spec.entity}' has no \`collection.field\`.`
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
update.UpdateExpression = "SET #c = list_append(if_not_exists(#c, :empty), :item)";
|
|
326
|
+
update.ExpressionAttributeNames = { "#c": field };
|
|
327
|
+
update.ExpressionAttributeValues = { ":empty": [], ":item": [projection] };
|
|
328
|
+
} else {
|
|
329
|
+
const names = {};
|
|
330
|
+
const values = {};
|
|
331
|
+
const sets = [];
|
|
332
|
+
let i = 0;
|
|
333
|
+
for (const [attr, value] of Object.entries(projection)) {
|
|
334
|
+
names[`#m${i}`] = attr;
|
|
335
|
+
values[`:m${i}`] = value;
|
|
336
|
+
sets.push(`#m${i} = :m${i}`);
|
|
337
|
+
i++;
|
|
338
|
+
}
|
|
339
|
+
if (sets.length === 0) {
|
|
340
|
+
throw new Error(
|
|
341
|
+
`declarative transaction: a 'snapshot' maintenance write on '${spec.entity}' has an empty projection.`
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
update.UpdateExpression = `SET ${sets.join(", ")}`;
|
|
345
|
+
update.ExpressionAttributeNames = names;
|
|
346
|
+
update.ExpressionAttributeValues = values;
|
|
347
|
+
}
|
|
348
|
+
return { Update: update };
|
|
349
|
+
}
|
|
350
|
+
function buildTransactItem(spec, manifest, params, element) {
|
|
351
|
+
const literal = spec.literalKey === true;
|
|
352
|
+
const entity = literal ? void 0 : entityByName(manifest, spec.entity);
|
|
353
|
+
const table = literal ? physicalTableName(manifest, spec.tableName) : entity.physicalName;
|
|
354
|
+
if (spec.type === "Put") {
|
|
355
|
+
const item = resolveRecord(spec.item, params, element);
|
|
356
|
+
if (entity) addKeyAttributes(entity, item);
|
|
357
|
+
const put = { TableName: table, Item: item };
|
|
358
|
+
applyCondition(spec.condition, put, params, element);
|
|
359
|
+
return { Put: put };
|
|
360
|
+
}
|
|
361
|
+
const key = resolveRecord(spec.keyCondition, params, element);
|
|
362
|
+
if (spec.type === "Delete") {
|
|
363
|
+
const del = { TableName: table, Key: key };
|
|
364
|
+
applyCondition(spec.condition, del, params, element);
|
|
365
|
+
return { Delete: del };
|
|
366
|
+
}
|
|
367
|
+
if (spec.type === "ConditionCheck") {
|
|
368
|
+
if (!spec.condition) {
|
|
369
|
+
throw new Error(
|
|
370
|
+
`declarative transaction: ConditionCheck on '${spec.entity}' has no condition; a ConditionCheck must carry the assertion it makes.`
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
const check = { TableName: table, Key: key };
|
|
374
|
+
applyCondition(spec.condition, check, params, element);
|
|
375
|
+
return { ConditionCheck: check };
|
|
376
|
+
}
|
|
377
|
+
if (spec.maintain !== void 0) {
|
|
378
|
+
return buildMaintainUpdateItem(spec, spec.maintain, table, key, params, element);
|
|
379
|
+
}
|
|
380
|
+
const changes = resolveRecord(spec.changes, params, element);
|
|
381
|
+
const adds = resolveRecord(spec.add, params, element);
|
|
382
|
+
const names = {};
|
|
383
|
+
const values = {};
|
|
384
|
+
const sets = [];
|
|
385
|
+
const addClauses = [];
|
|
386
|
+
let i = 0;
|
|
387
|
+
for (const [field, value] of Object.entries(changes)) {
|
|
388
|
+
const n = `#c${i}`;
|
|
389
|
+
const v = `:c${i}`;
|
|
390
|
+
names[n] = field;
|
|
391
|
+
values[v] = value;
|
|
392
|
+
sets.push(`${n} = ${v}`);
|
|
393
|
+
i++;
|
|
394
|
+
}
|
|
395
|
+
let j = 0;
|
|
396
|
+
for (const [field, value] of Object.entries(adds)) {
|
|
397
|
+
const n = `#a${j}`;
|
|
398
|
+
const v = `:a${j}`;
|
|
399
|
+
names[n] = field;
|
|
400
|
+
values[v] = typeof value === "string" ? Number(value) : value;
|
|
401
|
+
addClauses.push(`${n} ${v}`);
|
|
402
|
+
j++;
|
|
403
|
+
}
|
|
404
|
+
const update = { TableName: table, Key: key };
|
|
405
|
+
const exprParts = [];
|
|
406
|
+
if (sets.length > 0) exprParts.push("SET " + sets.join(", "));
|
|
407
|
+
if (addClauses.length > 0) exprParts.push("ADD " + addClauses.join(", "));
|
|
408
|
+
if (exprParts.length > 0) {
|
|
409
|
+
update.UpdateExpression = exprParts.join(" ");
|
|
410
|
+
update.ExpressionAttributeNames = names;
|
|
411
|
+
update.ExpressionAttributeValues = values;
|
|
412
|
+
}
|
|
413
|
+
applyCondition(spec.condition, update, params, element);
|
|
414
|
+
return { Update: update };
|
|
415
|
+
}
|
|
416
|
+
function expandTransactionItems(spec, manifest, params) {
|
|
417
|
+
const expanded = [];
|
|
418
|
+
const emit = (item, element) => {
|
|
419
|
+
const model = item.literalKey === true || item.entity === MARKER_ROW_ENTITY ? void 0 : item.entity;
|
|
420
|
+
expanded.push({ item: buildTransactItem(item, manifest, params, element), model });
|
|
421
|
+
};
|
|
422
|
+
for (const item of spec.items) {
|
|
423
|
+
if (item.forEach) {
|
|
424
|
+
const source = params[item.forEach.source];
|
|
425
|
+
if (!Array.isArray(source)) {
|
|
426
|
+
throw new Error(
|
|
427
|
+
`declarative transaction: forEach source '${item.forEach.source}' must be an array param; got ${typeof source}.`
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
for (const element of source) {
|
|
431
|
+
if (item.when && !whenHolds(item.when, params, element)) continue;
|
|
432
|
+
if (item.guard !== void 0 && !evaluateGuard(item.guard, params, element, "declarative transaction guard"))
|
|
433
|
+
continue;
|
|
434
|
+
emit(item, element);
|
|
435
|
+
}
|
|
436
|
+
} else {
|
|
437
|
+
if (item.when && !whenHolds(item.when, params, void 0)) continue;
|
|
438
|
+
if (item.guard !== void 0 && !evaluateGuard(item.guard, params, void 0, "declarative transaction guard"))
|
|
439
|
+
continue;
|
|
440
|
+
emit(item, void 0);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
return expanded;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// src/runtime/prepared-loader.ts
|
|
447
|
+
var LOAD_LABEL = "graphddb.loadPreparedPlan";
|
|
448
|
+
function slotOfBind(bind) {
|
|
449
|
+
if ("param" in bind) return { kind: "param", name: bind.param };
|
|
450
|
+
if ("literal" in bind) return { kind: "literal", value: bind.literal };
|
|
451
|
+
if ("literalDate" in bind) return { kind: "literal", value: new Date(bind.literalDate) };
|
|
452
|
+
return { kind: "literal", value: BigInt(bind.literalBigInt) };
|
|
453
|
+
}
|
|
454
|
+
function slotsOfBindMap(map) {
|
|
455
|
+
const out = {};
|
|
456
|
+
for (const [field, bind] of Object.entries(map ?? {})) out[field] = slotOfBind(bind);
|
|
457
|
+
return out;
|
|
458
|
+
}
|
|
459
|
+
function bindValues(slots, params) {
|
|
460
|
+
const out = {};
|
|
461
|
+
for (const [field, slot] of Object.entries(slots)) {
|
|
462
|
+
out[field] = slot.kind === "param" ? params[slot.name] : slot.value;
|
|
463
|
+
}
|
|
464
|
+
return out;
|
|
465
|
+
}
|
|
466
|
+
function resolveModelByName(name, planId) {
|
|
467
|
+
for (const modelClass of MetadataRegistry.getAll().keys()) {
|
|
468
|
+
if (modelClass.name === name) return modelClass;
|
|
469
|
+
}
|
|
470
|
+
throw new Error(
|
|
471
|
+
`${LOAD_LABEL}: static plan '${planId}' targets entity '${name}', which is not a registered model in this process. Ensure the model module is imported before the plan executes, or regenerate the artifact (graphddb transform prepared --aot).`
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
function bindEntities(plan, planId) {
|
|
475
|
+
const classes = /* @__PURE__ */ new Map();
|
|
476
|
+
const entities = {};
|
|
477
|
+
const tables = {};
|
|
478
|
+
for (const [name, expected] of Object.entries(plan.entities)) {
|
|
479
|
+
const modelClass = resolveModelByName(name, planId);
|
|
480
|
+
const metadata = MetadataRegistry.get(modelClass);
|
|
481
|
+
const actual = entityFingerprint(metadata);
|
|
482
|
+
if (actual !== expected) {
|
|
483
|
+
throw new Error(
|
|
484
|
+
`${LOAD_LABEL}: static plan '${planId}' is STALE \u2014 entity '${name}' has changed since the plan artifact was generated (declaration fingerprint mismatch: expected ${expected}, live ${actual}). A stale static plan never executes. Regenerate the artifact: \`graphddb transform prepared --aot <artifact> --write\` (CI: run the check mode to catch this at build time).`
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
classes.set(name, modelClass);
|
|
488
|
+
const entity = buildManifestEntity(metadata);
|
|
489
|
+
entities[name] = entity;
|
|
490
|
+
tables[metadata.tableName] = { physicalName: TableMapping.resolve(metadata.tableName) };
|
|
491
|
+
}
|
|
492
|
+
return { classes, manifest: { version: SPEC_VERSION, tables, entities } };
|
|
493
|
+
}
|
|
494
|
+
function renderSpecItem(transaction, index, manifest, params) {
|
|
495
|
+
const single = {
|
|
496
|
+
params: transaction.params,
|
|
497
|
+
items: [transaction.items[index]]
|
|
498
|
+
};
|
|
499
|
+
return expandTransactionItems(single, manifest, params);
|
|
500
|
+
}
|
|
501
|
+
function renderEffectsForOp(op, boundParams) {
|
|
502
|
+
const rendered = serializeEffectParams([op.modelClass], boundParams);
|
|
503
|
+
const effects = {};
|
|
504
|
+
const checks = [];
|
|
505
|
+
const modelBySignature = /* @__PURE__ */ new Map();
|
|
506
|
+
const { transaction, categories } = op.spec;
|
|
507
|
+
for (let i = 0; i < transaction.items.length; i++) {
|
|
508
|
+
const category = categories[i];
|
|
509
|
+
if (category === "base") continue;
|
|
510
|
+
for (const out of renderSpecItem(transaction, i, op.manifest, rendered)) {
|
|
511
|
+
const item = out.item;
|
|
512
|
+
if (category === "check") {
|
|
513
|
+
if (item.ConditionCheck !== void 0) checks.push(item.ConditionCheck);
|
|
514
|
+
continue;
|
|
515
|
+
}
|
|
516
|
+
const family = category;
|
|
517
|
+
if (!LOGICAL_EFFECT_CATEGORIES.includes(family)) {
|
|
518
|
+
throw new Error(
|
|
519
|
+
`${LOAD_LABEL}: unknown effect category '${String(category)}' in a static plan (artifact format skew \u2014 regenerate the artifact).`
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
(effects[family] ??= []).push(item);
|
|
523
|
+
if (out.model !== void 0) {
|
|
524
|
+
modelBySignature.set(execItemKeySignature(item), out.model);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
return { effects, checks, modelBySignature };
|
|
529
|
+
}
|
|
530
|
+
var INTENT_TO_KIND = { create: "put", update: "update", remove: "delete", upsert: "put" };
|
|
531
|
+
var AotPreparedWriteStatement = class {
|
|
532
|
+
/** @internal */
|
|
533
|
+
constructor(ops) {
|
|
534
|
+
this.ops = ops;
|
|
535
|
+
}
|
|
536
|
+
ops;
|
|
537
|
+
async execute(params = {}, options = {}) {
|
|
538
|
+
const logical = this.ops.map((op) => {
|
|
539
|
+
const bound = {
|
|
540
|
+
...bindValues(op.inputSlots, params),
|
|
541
|
+
...bindValues(op.keySlots, params)
|
|
542
|
+
};
|
|
543
|
+
const condition = op.conditionSlots !== void 0 ? bindValues(op.conditionSlots, params) : void 0;
|
|
544
|
+
return {
|
|
545
|
+
kind: INTENT_TO_KIND[op.spec.intent],
|
|
546
|
+
modelClass: op.modelClass,
|
|
547
|
+
modelStatic: op.modelStatic,
|
|
548
|
+
keyFields: op.spec.keyFields,
|
|
549
|
+
params: bound,
|
|
550
|
+
// A `create` guards with attribute_not_exists — the same condition the
|
|
551
|
+
// compiled base op carries; an explicit gate on a create was rejected at
|
|
552
|
+
// BUILD time (mirroring the compiled core's `withCondition` reject).
|
|
553
|
+
...op.spec.intent === "create" ? { condition: { notExists: true } } : condition !== void 0 ? { condition } : {},
|
|
554
|
+
...op.spec.result !== void 0 ? { result: op.spec.result } : {},
|
|
555
|
+
renderEffects: (postW1) => renderEffectsForOp(op, postW1)
|
|
556
|
+
};
|
|
557
|
+
});
|
|
558
|
+
const mode = options.mode ?? "transaction";
|
|
559
|
+
const runOpts = {
|
|
560
|
+
label: LOAD_LABEL,
|
|
561
|
+
...options.retry !== void 0 ? { retry: options.retry } : {},
|
|
562
|
+
...options.context !== void 0 ? { context: options.context } : {}
|
|
563
|
+
};
|
|
564
|
+
if (mode === "transaction") {
|
|
565
|
+
const { readBacks } = await executeLogicalWriteOps(logical, runOpts);
|
|
566
|
+
const out2 = {};
|
|
567
|
+
this.ops.forEach((op, i) => {
|
|
568
|
+
if (op.spec.result !== void 0) out2[op.alias] = readBacks[i];
|
|
569
|
+
});
|
|
570
|
+
return out2;
|
|
571
|
+
}
|
|
572
|
+
const settled = await executeLogicalParallelWrites(logical, runOpts);
|
|
573
|
+
const out = {};
|
|
574
|
+
this.ops.forEach((op, i) => {
|
|
575
|
+
const res = settled[i];
|
|
576
|
+
out[op.alias] = res.ok ? { ok: true, value: op.spec.result !== void 0 ? res.value : void 0 } : { ok: false, error: res.error };
|
|
577
|
+
});
|
|
578
|
+
return out;
|
|
579
|
+
}
|
|
580
|
+
};
|
|
581
|
+
function bindReadPlan(plan, planId) {
|
|
582
|
+
const { classes } = bindEntities(plan, planId);
|
|
583
|
+
const routes = (plan.reads ?? []).map((r) => {
|
|
584
|
+
const modelClass = classes.get(r.entity);
|
|
585
|
+
if (modelClass === void 0) {
|
|
586
|
+
throw new Error(
|
|
587
|
+
`${LOAD_LABEL}: static plan '${planId}' route '${r.alias}' targets entity '${r.entity}', which the plan does not fingerprint (artifact skew \u2014 regenerate).`
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
const consistentReadSlot = r.consistentRead !== void 0 ? slotOfBind(r.consistentRead) : void 0;
|
|
591
|
+
const limitSlot = r.limit !== void 0 ? slotOfBind(r.limit) : void 0;
|
|
592
|
+
const afterSlot = r.after !== void 0 ? slotOfBind(r.after) : void 0;
|
|
593
|
+
return {
|
|
594
|
+
alias: r.alias,
|
|
595
|
+
kind: r.op,
|
|
596
|
+
modelClass,
|
|
597
|
+
select: r.select,
|
|
598
|
+
keySlots: slotsOfBindMap(r.key),
|
|
599
|
+
...r.maxDepth !== void 0 ? { maxDepth: r.maxDepth } : {},
|
|
600
|
+
...r.order !== void 0 ? { order: r.order } : {},
|
|
601
|
+
...r.filter !== void 0 ? { filter: r.filter } : {},
|
|
602
|
+
...consistentReadSlot !== void 0 ? { consistentReadSlot } : {},
|
|
603
|
+
...limitSlot !== void 0 ? { limitSlot } : {},
|
|
604
|
+
...afterSlot !== void 0 ? { afterSlot } : {}
|
|
605
|
+
};
|
|
606
|
+
});
|
|
607
|
+
return new PreparedReadStatement(routes);
|
|
608
|
+
}
|
|
609
|
+
function bindWritePlan(plan, planId) {
|
|
610
|
+
const { classes, manifest } = bindEntities(plan, planId);
|
|
611
|
+
const ops = (plan.writes ?? []).map((w) => {
|
|
612
|
+
const modelClass = classes.get(w.entity);
|
|
613
|
+
if (modelClass === void 0) {
|
|
614
|
+
throw new Error(
|
|
615
|
+
`${LOAD_LABEL}: static plan '${planId}' op '${w.alias}' targets entity '${w.entity}', which the plan does not fingerprint (artifact skew \u2014 regenerate).`
|
|
616
|
+
);
|
|
617
|
+
}
|
|
618
|
+
const modelStatic = modelClass.asModel();
|
|
619
|
+
return {
|
|
620
|
+
alias: w.alias,
|
|
621
|
+
spec: w,
|
|
622
|
+
modelClass,
|
|
623
|
+
modelStatic,
|
|
624
|
+
keySlots: slotsOfBindMap(w.key),
|
|
625
|
+
inputSlots: slotsOfBindMap(w.input),
|
|
626
|
+
...w.condition !== void 0 ? { conditionSlots: slotsOfBindMap(w.condition) } : {},
|
|
627
|
+
manifest
|
|
628
|
+
};
|
|
629
|
+
});
|
|
630
|
+
return new AotPreparedWriteStatement(ops);
|
|
631
|
+
}
|
|
632
|
+
function bindPlan(doc, planId) {
|
|
633
|
+
if (doc === null || typeof doc !== "object" || doc.plans === void 0) {
|
|
634
|
+
throw new Error(`${LOAD_LABEL}: not a prepared-plan document.`);
|
|
635
|
+
}
|
|
636
|
+
if (doc.formatVersion !== PREPARED_FORMAT_VERSION) {
|
|
637
|
+
if (doc.formatVersion === "1") {
|
|
638
|
+
throw new Error(
|
|
639
|
+
`${LOAD_LABEL}: this artifact uses prepared-plan format version '1' (generated by graphddb <= 0.7.2). This runtime speaks format '${PREPARED_FORMAT_VERSION}' (the entity fingerprints changed algorithm), so the artifact must be regenerated with the transform: \`graphddb transform prepared --aot <artifact> --write\`.`
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
throw new Error(
|
|
643
|
+
`${LOAD_LABEL}: unsupported artifact format version '${String(doc.formatVersion)}' (this runtime supports '${PREPARED_FORMAT_VERSION}'). Regenerate the artifact: \`graphddb transform prepared --aot <artifact> --write\`.`
|
|
644
|
+
);
|
|
645
|
+
}
|
|
646
|
+
try {
|
|
647
|
+
validateEnvelope(doc, SPEC_VERSION_SUPPORTED, { field: "specVersion", label: LOAD_LABEL });
|
|
648
|
+
} catch (exc) {
|
|
649
|
+
if (exc instanceof EnvelopeFailure) {
|
|
650
|
+
throw new Error(
|
|
651
|
+
`${LOAD_LABEL}: the artifact was compiled against operation-IR version '${String(doc.specVersion)}' but this runtime speaks '${SPEC_VERSION_SUPPORTED}'. A version-skewed static plan never executes \u2014 regenerate the artifact.`
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
throw exc;
|
|
655
|
+
}
|
|
656
|
+
const plan = doc.plans[planId];
|
|
657
|
+
if (plan === void 0) {
|
|
658
|
+
const available = Object.keys(doc.plans).sort().slice(0, 8).join(", ");
|
|
659
|
+
throw new Error(
|
|
660
|
+
`${LOAD_LABEL}: the artifact carries no plan '${planId}' (available: ${available}${Object.keys(doc.plans).length > 8 ? ", \u2026" : ""}). The source and the artifact have drifted \u2014 regenerate: \`graphddb transform prepared --aot <artifact> --write\`.`
|
|
661
|
+
);
|
|
662
|
+
}
|
|
663
|
+
return plan.kind === "read" ? bindReadPlan(plan, planId) : bindWritePlan(plan, planId);
|
|
664
|
+
}
|
|
665
|
+
var BOUND = /* @__PURE__ */ new WeakMap();
|
|
666
|
+
var LazyAotHandle = class {
|
|
667
|
+
constructor(doc, planId) {
|
|
668
|
+
this.doc = doc;
|
|
669
|
+
this.planId = planId;
|
|
670
|
+
}
|
|
671
|
+
doc;
|
|
672
|
+
planId;
|
|
673
|
+
bound;
|
|
674
|
+
resolve() {
|
|
675
|
+
if (this.bound !== void 0) return this.bound;
|
|
676
|
+
let perDoc = BOUND.get(this.doc);
|
|
677
|
+
if (perDoc === void 0) {
|
|
678
|
+
perDoc = /* @__PURE__ */ new Map();
|
|
679
|
+
BOUND.set(this.doc, perDoc);
|
|
680
|
+
}
|
|
681
|
+
let handle = perDoc.get(this.planId);
|
|
682
|
+
if (handle === void 0) {
|
|
683
|
+
handle = bindPlan(this.doc, this.planId);
|
|
684
|
+
perDoc.set(this.planId, handle);
|
|
685
|
+
}
|
|
686
|
+
this.bound = handle;
|
|
687
|
+
return handle;
|
|
688
|
+
}
|
|
689
|
+
async execute(params, options) {
|
|
690
|
+
const target = this.resolve();
|
|
691
|
+
return target.execute(params, options);
|
|
692
|
+
}
|
|
693
|
+
};
|
|
694
|
+
function loadPreparedPlan(doc, planId, body) {
|
|
695
|
+
void body;
|
|
696
|
+
return new LazyAotHandle(doc, planId);
|
|
697
|
+
}
|
|
698
|
+
export {
|
|
699
|
+
AotPreparedWriteStatement,
|
|
700
|
+
loadPreparedPlan
|
|
701
|
+
};
|