graphddb 0.7.0 → 0.7.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/cdc/index.d.ts +2 -2
- package/dist/{chunk-GFGVDF4W.js → chunk-C5Q2NMRW.js} +581 -22
- package/dist/cli.js +186 -11
- package/dist/{from-change-pnURY-cV.d.ts → from-change-CWiXBcgi.d.ts} +1 -1
- package/dist/{index-Eg94ChE1.d.ts → index-CvS9ATKB.d.ts} +289 -104
- package/dist/index.d.ts +85 -7
- package/dist/index.js +259 -1
- package/dist/linter/index.d.ts +4 -4
- package/dist/{maintenance-view-adapter-BP2CJDdz.d.ts → maintenance-view-adapter-NBTZbE8-.d.ts} +57 -4
- package/dist/{registry-Cv9nl_3i.d.ts → registry-BndKbZbg.d.ts} +1 -1
- package/dist/{relation-depth-BR0y7Q1i.d.ts → relation-depth-CCGHLTOv.d.ts} +1 -1
- package/dist/spec/index.d.ts +3 -3
- package/dist/spec/index.js +13 -1
- package/dist/testing/index.d.ts +1 -1
- package/dist/transform/index.d.ts +51 -2
- package/dist/transform/index.js +264 -1
- package/docs/cqrs-contract.md +1 -1
- package/docs/design-patterns.md +9 -7
- package/docs/prepared-statements.md +70 -3
- package/docs/python-bridge.md +24 -4
- package/package.json +1 -1
|
@@ -67,7 +67,7 @@ function evaluateKey(segmented, scope, presentFields, nameOf = (f) => f, partial
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
// src/spec/types.ts
|
|
70
|
-
var SPEC_VERSION = "1.
|
|
70
|
+
var SPEC_VERSION = "1.1";
|
|
71
71
|
var MARKER_ROW_ENTITY = "__marker__";
|
|
72
72
|
|
|
73
73
|
// src/spec/manifest.ts
|
|
@@ -144,6 +144,9 @@ function buildFields(metadata) {
|
|
|
144
144
|
}
|
|
145
145
|
return sortedRecord(fields);
|
|
146
146
|
}
|
|
147
|
+
function buildManifestEntity(metadata) {
|
|
148
|
+
return buildEntity(metadata);
|
|
149
|
+
}
|
|
147
150
|
function buildEntity(metadata) {
|
|
148
151
|
return {
|
|
149
152
|
table: metadata.tableName,
|
|
@@ -3532,8 +3535,23 @@ async function persistSingleWriteWithHooks(w, runtime, origins, transaction, ret
|
|
|
3532
3535
|
});
|
|
3533
3536
|
}
|
|
3534
3537
|
async function runEnvelopeW1(op, runtime, transaction, writeCtxs) {
|
|
3535
|
-
const
|
|
3536
|
-
|
|
3538
|
+
const r = await runLogicalW1(
|
|
3539
|
+
{
|
|
3540
|
+
kind: op.fragment.op.operation,
|
|
3541
|
+
modelClass: op.fragment.op.entity.modelClass,
|
|
3542
|
+
keyFields: op.fragment.keyFields,
|
|
3543
|
+
params: op.params
|
|
3544
|
+
},
|
|
3545
|
+
runtime,
|
|
3546
|
+
transaction,
|
|
3547
|
+
writeCtxs
|
|
3548
|
+
);
|
|
3549
|
+
if (r.rewritten !== void 0) return { op, rewritten: r.rewritten };
|
|
3550
|
+
return { op: { ...op, params: r.params } };
|
|
3551
|
+
}
|
|
3552
|
+
async function runLogicalW1(op, runtime, transaction, writeCtxs) {
|
|
3553
|
+
const kind = op.kind;
|
|
3554
|
+
const keyFields = new Set(op.keyFields);
|
|
3537
3555
|
const input = {};
|
|
3538
3556
|
if (kind === "put") {
|
|
3539
3557
|
input.item = { ...op.params };
|
|
@@ -3547,15 +3565,14 @@ async function runEnvelopeW1(op, runtime, transaction, writeCtxs) {
|
|
|
3547
3565
|
input.key = key;
|
|
3548
3566
|
input.changes = changes;
|
|
3549
3567
|
}
|
|
3550
|
-
const
|
|
3551
|
-
const ctx = runtime.writeCtx(kind, ctxModelFor(modelClass), input, transaction);
|
|
3568
|
+
const ctx = runtime.writeCtx(kind, ctxModelFor(op.modelClass), input, transaction);
|
|
3552
3569
|
await runtime.runWriteBefore(ctx);
|
|
3553
3570
|
writeCtxs.push(ctx);
|
|
3554
3571
|
if (ctx.kind !== kind) {
|
|
3555
|
-
return { op, rewritten: renderRewrittenWrite(modelClass, ctx) };
|
|
3572
|
+
return { params: op.params, rewritten: renderRewrittenWrite(op.modelClass, ctx) };
|
|
3556
3573
|
}
|
|
3557
3574
|
const merged = ctx.kind === "put" ? { ...ctx.input.item ?? {} } : { ...ctx.input.key ?? {}, ...ctx.input.changes ?? {} };
|
|
3558
|
-
return {
|
|
3575
|
+
return { params: merged };
|
|
3559
3576
|
}
|
|
3560
3577
|
function renderRewrittenWrite(modelClass, ctx) {
|
|
3561
3578
|
const modelStatic = modelClass.asModel();
|
|
@@ -3646,6 +3663,199 @@ async function executeCompiledParallelWrites(ops, options) {
|
|
|
3646
3663
|
]);
|
|
3647
3664
|
return results;
|
|
3648
3665
|
}
|
|
3666
|
+
var LOGICAL_EFFECT_CATEGORIES = [
|
|
3667
|
+
"edge",
|
|
3668
|
+
"derive",
|
|
3669
|
+
"unique",
|
|
3670
|
+
"outbox",
|
|
3671
|
+
"idempotency",
|
|
3672
|
+
"maintain",
|
|
3673
|
+
"maintainOutbox"
|
|
3674
|
+
];
|
|
3675
|
+
function renderLogicalWrite(op, params) {
|
|
3676
|
+
const base = {
|
|
3677
|
+
operation: op.kind,
|
|
3678
|
+
modelClass: op.modelClass,
|
|
3679
|
+
modelStatic: op.modelStatic,
|
|
3680
|
+
key: params,
|
|
3681
|
+
...op.condition !== void 0 ? { condition: op.condition } : {}
|
|
3682
|
+
};
|
|
3683
|
+
if (op.kind === "put") return { ...base, item: params };
|
|
3684
|
+
if (op.kind === "update") {
|
|
3685
|
+
const keyFields = new Set(op.keyFields);
|
|
3686
|
+
const changes = {};
|
|
3687
|
+
for (const [k, v] of Object.entries(params)) {
|
|
3688
|
+
if (!keyFields.has(k)) changes[k] = v;
|
|
3689
|
+
}
|
|
3690
|
+
return { ...base, changes };
|
|
3691
|
+
}
|
|
3692
|
+
return base;
|
|
3693
|
+
}
|
|
3694
|
+
function renderLogicalOp(op, params) {
|
|
3695
|
+
const write = renderLogicalWrite(op, params);
|
|
3696
|
+
const rendered = op.renderEffects(params);
|
|
3697
|
+
const e = rendered.effects;
|
|
3698
|
+
const checkItems = rendered.checks.map((check) => ({ check }));
|
|
3699
|
+
const modelBySignature = new Map(rendered.modelBySignature);
|
|
3700
|
+
const pick = (c) => [...e[c] ?? []];
|
|
3701
|
+
const edgeItems = pick("edge");
|
|
3702
|
+
const updateItems = pick("derive");
|
|
3703
|
+
const guardItems = pick("unique");
|
|
3704
|
+
const outboxItems = pick("outbox");
|
|
3705
|
+
const idempotencyItems = pick("idempotency");
|
|
3706
|
+
const maintainItems = pick("maintain");
|
|
3707
|
+
const maintainOutboxItems = pick("maintainOutbox");
|
|
3708
|
+
return {
|
|
3709
|
+
write,
|
|
3710
|
+
edgeItems,
|
|
3711
|
+
updateItems,
|
|
3712
|
+
guardItems,
|
|
3713
|
+
outboxItems,
|
|
3714
|
+
idempotencyItems,
|
|
3715
|
+
maintainItems,
|
|
3716
|
+
maintainOutboxItems,
|
|
3717
|
+
checkItems,
|
|
3718
|
+
modelBySignature,
|
|
3719
|
+
hasDerivedEffects: edgeItems.length > 0 || updateItems.length > 0 || guardItems.length > 0 || outboxItems.length > 0 || idempotencyItems.length > 0 || maintainItems.length > 0 || maintainOutboxItems.length > 0 || checkItems.length > 0
|
|
3720
|
+
};
|
|
3721
|
+
}
|
|
3722
|
+
function readBackForLogicalOp(op, params) {
|
|
3723
|
+
const sel = op.result?.select;
|
|
3724
|
+
if (sel === void 0 || Object.keys(sel).length === 0) return void 0;
|
|
3725
|
+
const key = {};
|
|
3726
|
+
for (const f of op.keyFields) {
|
|
3727
|
+
if (f in params) key[f] = params[f];
|
|
3728
|
+
}
|
|
3729
|
+
return executeQuery(op.modelClass, key, sel, {
|
|
3730
|
+
consistentRead: op.result?.options?.consistentRead ?? true,
|
|
3731
|
+
maxDepth: op.result?.options?.maxDepth
|
|
3732
|
+
});
|
|
3733
|
+
}
|
|
3734
|
+
async function executeLogicalWriteOps(ops, options) {
|
|
3735
|
+
const label = options.label;
|
|
3736
|
+
const writeMw = buildWriteRuntime(options.context);
|
|
3737
|
+
const writeCtxs = [];
|
|
3738
|
+
const txId = { id: /* @__PURE__ */ Symbol("graphddb.prepared-aot") };
|
|
3739
|
+
const effectiveParams = [];
|
|
3740
|
+
const rewrites = [];
|
|
3741
|
+
if (writeMw.active) {
|
|
3742
|
+
for (const op of ops) {
|
|
3743
|
+
const r = await runLogicalW1(op, writeMw, txId, writeCtxs);
|
|
3744
|
+
effectiveParams.push(r.params);
|
|
3745
|
+
rewrites.push(r.rewritten ?? null);
|
|
3746
|
+
}
|
|
3747
|
+
} else {
|
|
3748
|
+
for (const op of ops) {
|
|
3749
|
+
effectiveParams.push(op.params);
|
|
3750
|
+
rewrites.push(null);
|
|
3751
|
+
}
|
|
3752
|
+
}
|
|
3753
|
+
const renderedOps = ops.map(
|
|
3754
|
+
(op, i) => rewrites[i] !== null ? rewrittenCompiledOp(rewrites[i]) : renderLogicalOp(op, effectiveParams[i])
|
|
3755
|
+
);
|
|
3756
|
+
const rendered = renderedOps.map((r) => r.write);
|
|
3757
|
+
const modelBySignature = /* @__PURE__ */ new Map();
|
|
3758
|
+
const edgeItems = [];
|
|
3759
|
+
const updateItems = [];
|
|
3760
|
+
const guardItems = [];
|
|
3761
|
+
const outboxItems = [];
|
|
3762
|
+
const idempotencyItems = [];
|
|
3763
|
+
const maintainItems = [];
|
|
3764
|
+
const maintainOutboxItems = [];
|
|
3765
|
+
const checkItems = [];
|
|
3766
|
+
for (const r of renderedOps) {
|
|
3767
|
+
for (const [sig, name] of r.modelBySignature) modelBySignature.set(sig, name);
|
|
3768
|
+
edgeItems.push(...r.edgeItems);
|
|
3769
|
+
updateItems.push(...r.updateItems);
|
|
3770
|
+
guardItems.push(...r.guardItems);
|
|
3771
|
+
outboxItems.push(...r.outboxItems);
|
|
3772
|
+
idempotencyItems.push(...r.idempotencyItems);
|
|
3773
|
+
maintainItems.push(...r.maintainItems);
|
|
3774
|
+
maintainOutboxItems.push(...r.maintainOutboxItems);
|
|
3775
|
+
checkItems.push(...r.checkItems);
|
|
3776
|
+
}
|
|
3777
|
+
const hasDerivedEffects = renderedOps.some((r) => r.hasDerivedEffects);
|
|
3778
|
+
const origins = writeMw.active ? rendered.map((w) => ({ model: ctxModelFor(w.modelClass), kind: w.operation })) : [];
|
|
3779
|
+
if (rendered.length === 1 && !hasDerivedEffects) {
|
|
3780
|
+
if (writeMw.active) {
|
|
3781
|
+
await persistSingleWriteWithHooks(rendered[0], writeMw, origins, txId, options.retry);
|
|
3782
|
+
} else {
|
|
3783
|
+
await executeSingleWrite(rendered[0], options.retry);
|
|
3784
|
+
}
|
|
3785
|
+
} else {
|
|
3786
|
+
await executeReferentialTransaction(
|
|
3787
|
+
rendered,
|
|
3788
|
+
edgeItems,
|
|
3789
|
+
updateItems,
|
|
3790
|
+
guardItems,
|
|
3791
|
+
outboxItems,
|
|
3792
|
+
idempotencyItems,
|
|
3793
|
+
maintainItems,
|
|
3794
|
+
maintainOutboxItems,
|
|
3795
|
+
checkItems,
|
|
3796
|
+
label,
|
|
3797
|
+
modelBySignature,
|
|
3798
|
+
options.retry,
|
|
3799
|
+
writeMw.active ? { runtime: writeMw, origins, transaction: txId } : void 0
|
|
3800
|
+
);
|
|
3801
|
+
}
|
|
3802
|
+
if (writeMw.active) {
|
|
3803
|
+
for (let i = writeCtxs.length - 1; i >= 0; i--) {
|
|
3804
|
+
await writeMw.runWriteAfter(writeCtxs[i], {});
|
|
3805
|
+
}
|
|
3806
|
+
}
|
|
3807
|
+
const readBacks = await Promise.all(
|
|
3808
|
+
ops.map((op, i) => readBackForLogicalOp(op, effectiveParams[i]))
|
|
3809
|
+
);
|
|
3810
|
+
return { readBacks };
|
|
3811
|
+
}
|
|
3812
|
+
async function executeLogicalParallelWrites(ops, options) {
|
|
3813
|
+
const label = options.label;
|
|
3814
|
+
const results = new Array(ops.length);
|
|
3815
|
+
const renderedOps = ops.map((op) => renderLogicalOp(op, op.params));
|
|
3816
|
+
const coalescibleIdx = [];
|
|
3817
|
+
const individualIdx = [];
|
|
3818
|
+
const hooksActive = buildWriteRuntime(options.context).active;
|
|
3819
|
+
renderedOps.forEach((r, i) => {
|
|
3820
|
+
const w = r.write;
|
|
3821
|
+
if (!hooksActive && !r.hasDerivedEffects && w.condition === void 0 && (w.operation === "put" || w.operation === "delete")) {
|
|
3822
|
+
coalescibleIdx.push(i);
|
|
3823
|
+
} else {
|
|
3824
|
+
individualIdx.push(i);
|
|
3825
|
+
}
|
|
3826
|
+
});
|
|
3827
|
+
await Promise.all([
|
|
3828
|
+
(async () => {
|
|
3829
|
+
if (coalescibleIdx.length === 0) return;
|
|
3830
|
+
const writes = coalescibleIdx.map((i) => renderedOps[i].write);
|
|
3831
|
+
try {
|
|
3832
|
+
await executeParallelWrites(writes);
|
|
3833
|
+
await Promise.all(
|
|
3834
|
+
coalescibleIdx.map(async (i) => {
|
|
3835
|
+
const value = await readBackForLogicalOp(ops[i], ops[i].params);
|
|
3836
|
+
results[i] = { ok: true, value };
|
|
3837
|
+
})
|
|
3838
|
+
);
|
|
3839
|
+
} catch (e) {
|
|
3840
|
+
const error = e instanceof Error ? e : new Error(String(e));
|
|
3841
|
+
for (const i of coalescibleIdx) results[i] = { ok: false, error };
|
|
3842
|
+
}
|
|
3843
|
+
})(),
|
|
3844
|
+
...individualIdx.map(async (i) => {
|
|
3845
|
+
try {
|
|
3846
|
+
const { readBacks } = await executeLogicalWriteOps([ops[i]], {
|
|
3847
|
+
label: `${label} (op #${i})`,
|
|
3848
|
+
...options.retry !== void 0 ? { retry: options.retry } : {},
|
|
3849
|
+
...options.context !== void 0 ? { context: options.context } : {}
|
|
3850
|
+
});
|
|
3851
|
+
results[i] = { ok: true, value: readBacks[0] };
|
|
3852
|
+
} catch (e) {
|
|
3853
|
+
results[i] = { ok: false, error: e instanceof Error ? e : new Error(String(e)) };
|
|
3854
|
+
}
|
|
3855
|
+
})
|
|
3856
|
+
]);
|
|
3857
|
+
return results;
|
|
3858
|
+
}
|
|
3649
3859
|
function withCondition(op, condition) {
|
|
3650
3860
|
if (op.condition === void 0) return { ...op, condition };
|
|
3651
3861
|
const base = op.condition;
|
|
@@ -4052,7 +4262,7 @@ function classify(routes) {
|
|
|
4052
4262
|
}
|
|
4053
4263
|
return kind;
|
|
4054
4264
|
}
|
|
4055
|
-
function
|
|
4265
|
+
function analyzeWriteRoute(alias, route) {
|
|
4056
4266
|
const present = INTENT_KEYS.filter((k) => route[k] !== void 0);
|
|
4057
4267
|
if (present.length !== 1) {
|
|
4058
4268
|
throw new Error(
|
|
@@ -4073,23 +4283,37 @@ function compileWriteRoute(alias, route) {
|
|
|
4073
4283
|
);
|
|
4074
4284
|
const conditionRec = route.condition !== void 0 ? recordSlots(route.condition, `${alias} condition`) : void 0;
|
|
4075
4285
|
if (route.result !== void 0) assertStaticTemplate(route.result, `${alias} result`);
|
|
4076
|
-
const fragment = compileWriteFragment({
|
|
4077
|
-
alias,
|
|
4078
|
-
intent,
|
|
4079
|
-
model,
|
|
4080
|
-
keyFieldNames: keyRec.fields,
|
|
4081
|
-
inputFieldNames: inputRec.fields
|
|
4082
|
-
});
|
|
4083
4286
|
return {
|
|
4084
4287
|
alias,
|
|
4085
4288
|
intent,
|
|
4086
|
-
|
|
4289
|
+
model,
|
|
4290
|
+
keyFields: keyRec.fields,
|
|
4291
|
+
inputFields: inputRec.fields,
|
|
4087
4292
|
keySlots: keyRec.slots,
|
|
4088
4293
|
inputSlots: inputRec.slots,
|
|
4089
4294
|
...conditionRec !== void 0 ? { conditionSlots: conditionRec.slots } : {},
|
|
4090
4295
|
...route.result !== void 0 ? { result: route.result } : {}
|
|
4091
4296
|
};
|
|
4092
4297
|
}
|
|
4298
|
+
function compileWriteRoute(alias, route) {
|
|
4299
|
+
const analyzed = analyzeWriteRoute(alias, route);
|
|
4300
|
+
const fragment = compileWriteFragment({
|
|
4301
|
+
alias,
|
|
4302
|
+
intent: analyzed.intent,
|
|
4303
|
+
model: analyzed.model,
|
|
4304
|
+
keyFieldNames: analyzed.keyFields,
|
|
4305
|
+
inputFieldNames: analyzed.inputFields
|
|
4306
|
+
});
|
|
4307
|
+
return {
|
|
4308
|
+
alias,
|
|
4309
|
+
intent: analyzed.intent,
|
|
4310
|
+
fragment,
|
|
4311
|
+
keySlots: analyzed.keySlots,
|
|
4312
|
+
inputSlots: analyzed.inputSlots,
|
|
4313
|
+
...analyzed.conditionSlots !== void 0 ? { conditionSlots: analyzed.conditionSlots } : {},
|
|
4314
|
+
...analyzed.result !== void 0 ? { result: analyzed.result } : {}
|
|
4315
|
+
};
|
|
4316
|
+
}
|
|
4093
4317
|
function compileReadRoute(alias, route) {
|
|
4094
4318
|
const isQuery = route.query !== void 0;
|
|
4095
4319
|
const isList = route.list !== void 0;
|
|
@@ -4256,6 +4480,25 @@ function stableJson(value) {
|
|
|
4256
4480
|
return v;
|
|
4257
4481
|
});
|
|
4258
4482
|
}
|
|
4483
|
+
function evaluatePreparedRoutes(body) {
|
|
4484
|
+
if (typeof body !== "function") {
|
|
4485
|
+
throw new Error(
|
|
4486
|
+
`${PREPARE_LABEL}: expected a body function \`($) => ({ alias: { ... } })\`.`
|
|
4487
|
+
);
|
|
4488
|
+
}
|
|
4489
|
+
const $ = makePreparedInputProxy();
|
|
4490
|
+
const map = body($);
|
|
4491
|
+
if (map === null || typeof map !== "object") {
|
|
4492
|
+
throw new Error(
|
|
4493
|
+
`${PREPARE_LABEL}: the body must return a descriptor map \`{ alias: { ... } }\`.`
|
|
4494
|
+
);
|
|
4495
|
+
}
|
|
4496
|
+
const routes = Object.keys(map).map((alias) => ({
|
|
4497
|
+
alias,
|
|
4498
|
+
route: map[alias]
|
|
4499
|
+
}));
|
|
4500
|
+
return { kind: classify(routes), routes };
|
|
4501
|
+
}
|
|
4259
4502
|
function prepare(body) {
|
|
4260
4503
|
if (typeof body !== "function") {
|
|
4261
4504
|
throw new Error(
|
|
@@ -6839,12 +7082,42 @@ function synthesizeMutationTransaction(contractName, methodName, ops, checks, ed
|
|
|
6839
7082
|
...maintainOutboxItems,
|
|
6840
7083
|
...checkItems
|
|
6841
7084
|
];
|
|
7085
|
+
const categories = [
|
|
7086
|
+
...base.items.map(() => "base"),
|
|
7087
|
+
...edgeItems.map(() => "edge"),
|
|
7088
|
+
...updateItems.map(() => "derive"),
|
|
7089
|
+
...guardItems.map(() => "unique"),
|
|
7090
|
+
...outboxItems.map(() => "outbox"),
|
|
7091
|
+
...idempotencyItems.map(() => "idempotency"),
|
|
7092
|
+
...maintainItems.map(() => "maintain"),
|
|
7093
|
+
...maintainOutboxItems.map(() => "maintainOutbox"),
|
|
7094
|
+
...checkItems.map(() => "check")
|
|
7095
|
+
];
|
|
6842
7096
|
if (items.length > MAX_TRANSACT_ITEMS) {
|
|
6843
7097
|
throw new Error(
|
|
6844
7098
|
`Contract '${contractName}.${methodName}': a mutation composes ${items.length} items (writes + edges + derived updates + uniqueness guards + outbox events + idempotency guard + ConditionChecks) into one TransactWriteItems, exceeding the DynamoDB limit of ${MAX_TRANSACT_ITEMS}. An atomic transaction cannot be split; reduce the fragment / effect count.`
|
|
6845
7099
|
);
|
|
6846
7100
|
}
|
|
6847
|
-
return {
|
|
7101
|
+
return {
|
|
7102
|
+
spec: { params: sortedParams(params), items, maxItems: items.length },
|
|
7103
|
+
categories
|
|
7104
|
+
};
|
|
7105
|
+
}
|
|
7106
|
+
function serializePreparedWriteFragment(label, fragment) {
|
|
7107
|
+
const { spec, categories } = synthesizeMutationTransaction(
|
|
7108
|
+
label,
|
|
7109
|
+
"plan",
|
|
7110
|
+
[fragment.op],
|
|
7111
|
+
fragment.conditionChecks ?? [],
|
|
7112
|
+
fragment.edgeWrites ?? [],
|
|
7113
|
+
fragment.derivedUpdates ?? [],
|
|
7114
|
+
fragment.uniqueGuards ?? [],
|
|
7115
|
+
fragment.outboxEvents ?? [],
|
|
7116
|
+
fragment.idempotencyGuard,
|
|
7117
|
+
fragment.maintainWrites ?? [],
|
|
7118
|
+
fragment.maintainOutbox ?? []
|
|
7119
|
+
);
|
|
7120
|
+
return { transaction: spec, categories };
|
|
6848
7121
|
}
|
|
6849
7122
|
function modeTargetFor(mode, op, commandSpec, contractName, methodName, opName, transactionSpecs) {
|
|
6850
7123
|
if (mode === "parallel") {
|
|
@@ -6967,7 +7240,7 @@ function serializeCommandContract(contractName, contract, commandSpecs, transact
|
|
|
6967
7240
|
idempotencyGuard,
|
|
6968
7241
|
maintainWrites,
|
|
6969
7242
|
maintainOutbox
|
|
6970
|
-
)
|
|
7243
|
+
).spec
|
|
6971
7244
|
) : (
|
|
6972
7245
|
// #90: writes only.
|
|
6973
7246
|
composeFragmentTransaction(contractName, methodName, ops)
|
|
@@ -7263,12 +7536,12 @@ function buildReadOperations(def) {
|
|
|
7263
7536
|
};
|
|
7264
7537
|
const ops = [root];
|
|
7265
7538
|
ops.push(...buildRelationOperations(metadata, select, "$"));
|
|
7266
|
-
return ops;
|
|
7539
|
+
return injectRefsSourceProjections(ops);
|
|
7267
7540
|
}
|
|
7268
7541
|
function projectionFields(select) {
|
|
7269
7542
|
const out = [];
|
|
7270
7543
|
for (const [field, value] of Object.entries(select)) {
|
|
7271
|
-
if (value === true) out.push(field);
|
|
7544
|
+
if (value === true || isInlineSnapshotSpec(value)) out.push(field);
|
|
7272
7545
|
}
|
|
7273
7546
|
return out.sort();
|
|
7274
7547
|
}
|
|
@@ -7284,9 +7557,16 @@ function buildRelationOperations(metadata, select, parentPath) {
|
|
|
7284
7557
|
const targetMeta = MetadataRegistry.get(targetClass);
|
|
7285
7558
|
const childPath = `${parentPath}.${rel.propertyName}`;
|
|
7286
7559
|
if (rel.type === "refs") {
|
|
7287
|
-
|
|
7288
|
-
|
|
7560
|
+
if (spec.filter !== void 0) {
|
|
7561
|
+
throw new Error(
|
|
7562
|
+
`Relation '${rel.propertyName}' (refs) declares a \`filter\`, which the static operations spec cannot apply (a BatchGetItem has no server-side FilterExpression and the spec runtimes run no client-side filter stage). Remove the filter from selects compiled to operations.json / generated Python, or filter in the consumer.`
|
|
7563
|
+
);
|
|
7564
|
+
}
|
|
7565
|
+
ops.push(
|
|
7566
|
+
buildRefsOperation(rel, targetMeta, childSelect, `${childPath}.items`)
|
|
7289
7567
|
);
|
|
7568
|
+
ops.push(...buildRelationOperations(targetMeta, childSelect, `${childPath}.items`));
|
|
7569
|
+
continue;
|
|
7290
7570
|
}
|
|
7291
7571
|
if (rel.type === "hasMany") {
|
|
7292
7572
|
const limit = spec.limit ?? rel.options?.limit?.default ?? 20;
|
|
@@ -7397,6 +7677,65 @@ function buildBatchGetOperation(rel, targetMeta, select, resultPath) {
|
|
|
7397
7677
|
sourceField
|
|
7398
7678
|
};
|
|
7399
7679
|
}
|
|
7680
|
+
function buildRefsOperation(rel, targetMeta, select, resultPath) {
|
|
7681
|
+
const refs = rel.refs;
|
|
7682
|
+
if (!refs) {
|
|
7683
|
+
throw new Error(
|
|
7684
|
+
`Relation '${rel.propertyName}' is type 'refs' but carries no refs binding.`
|
|
7685
|
+
);
|
|
7686
|
+
}
|
|
7687
|
+
const { targetField, sourceField } = singleSourceField(rel);
|
|
7688
|
+
const resolved = resolveKey([targetField], targetMeta);
|
|
7689
|
+
if (resolved.type !== "pk" || resolved.partial) {
|
|
7690
|
+
throw new Error(
|
|
7691
|
+
`Relation '${rel.propertyName}' (refs) must resolve to a full primary key (BatchGetItem); got ${resolved.type}${resolved.partial ? " (partial)" : ""}.`
|
|
7692
|
+
);
|
|
7693
|
+
}
|
|
7694
|
+
if (!targetMeta.primaryKey) throw new Error("Primary key not defined");
|
|
7695
|
+
const nameOf = (f) => f === targetField ? sourceField : f;
|
|
7696
|
+
const { pk, sk } = evaluateKey(
|
|
7697
|
+
targetMeta.primaryKey.segmented,
|
|
7698
|
+
"result",
|
|
7699
|
+
/* @__PURE__ */ new Set([targetField]),
|
|
7700
|
+
nameOf
|
|
7701
|
+
);
|
|
7702
|
+
const keyCondition = { PK: pk };
|
|
7703
|
+
if (sk !== void 0) keyCondition.SK = sk;
|
|
7704
|
+
return {
|
|
7705
|
+
type: "BatchGetItem",
|
|
7706
|
+
tableName: TableMapping.resolve(targetMeta.tableName),
|
|
7707
|
+
keyCondition,
|
|
7708
|
+
projection: projectionFields(select),
|
|
7709
|
+
resultPath,
|
|
7710
|
+
sourceField,
|
|
7711
|
+
sourceList: { from: refs.from, key: refs.key }
|
|
7712
|
+
};
|
|
7713
|
+
}
|
|
7714
|
+
function injectRefsSourceProjections(ops) {
|
|
7715
|
+
for (let i = 0; i < ops.length; i++) {
|
|
7716
|
+
const op = ops[i];
|
|
7717
|
+
if (op.sourceList === void 0) continue;
|
|
7718
|
+
const tokens = op.resultPath.split(".");
|
|
7719
|
+
const parentPath = tokens.slice(0, -2).join(".") || "$";
|
|
7720
|
+
const parentIndex = ops.findIndex((p) => p.resultPath === parentPath);
|
|
7721
|
+
if (parentIndex === -1) {
|
|
7722
|
+
throw new Error(
|
|
7723
|
+
`refs fan-out at '${op.resultPath}': no parent operation at '${parentPath}'.`
|
|
7724
|
+
);
|
|
7725
|
+
}
|
|
7726
|
+
const parent = ops[parentIndex];
|
|
7727
|
+
const from2 = op.sourceList.from;
|
|
7728
|
+
if (parent.projection.includes(from2)) {
|
|
7729
|
+
continue;
|
|
7730
|
+
}
|
|
7731
|
+
ops[parentIndex] = {
|
|
7732
|
+
...parent,
|
|
7733
|
+
projection: [...parent.projection, from2].sort()
|
|
7734
|
+
};
|
|
7735
|
+
ops[i] = { ...op, sourceList: { ...op.sourceList, implicit: true } };
|
|
7736
|
+
}
|
|
7737
|
+
return ops;
|
|
7738
|
+
}
|
|
7400
7739
|
function filterSpec(filter) {
|
|
7401
7740
|
return { declarative: filter };
|
|
7402
7741
|
}
|
|
@@ -7551,6 +7890,216 @@ function buildOperations(queries = {}, commands = {}, transactions = {}, contrac
|
|
|
7551
7890
|
};
|
|
7552
7891
|
}
|
|
7553
7892
|
|
|
7893
|
+
// src/spec/prepared.ts
|
|
7894
|
+
import { createHash } from "crypto";
|
|
7895
|
+
var PREPARED_FORMAT_VERSION = "1";
|
|
7896
|
+
function canonicalJson(value) {
|
|
7897
|
+
return JSON.stringify(value, (_k, v) => {
|
|
7898
|
+
if (v !== null && typeof v === "object" && !Array.isArray(v)) {
|
|
7899
|
+
const sorted = {};
|
|
7900
|
+
for (const key of Object.keys(v).sort()) {
|
|
7901
|
+
sorted[key] = v[key];
|
|
7902
|
+
}
|
|
7903
|
+
return sorted;
|
|
7904
|
+
}
|
|
7905
|
+
return v;
|
|
7906
|
+
});
|
|
7907
|
+
}
|
|
7908
|
+
function entityFingerprint(metadata) {
|
|
7909
|
+
return createHash("sha256").update(canonicalJson(buildManifestEntity(metadata))).digest("hex");
|
|
7910
|
+
}
|
|
7911
|
+
function planFingerprint(plan2) {
|
|
7912
|
+
return createHash("sha256").update(canonicalJson(plan2)).digest("hex");
|
|
7913
|
+
}
|
|
7914
|
+
function bindOfSlot(slot, where) {
|
|
7915
|
+
if (slot.kind === "param") return { param: slot.name };
|
|
7916
|
+
const v = slot.value;
|
|
7917
|
+
if (v === null || typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
|
|
7918
|
+
return { literal: v };
|
|
7919
|
+
}
|
|
7920
|
+
if (v instanceof Date) return { literalDate: v.toISOString() };
|
|
7921
|
+
if (typeof v === "bigint") return { literalBigInt: v.toString() };
|
|
7922
|
+
throw new Error(
|
|
7923
|
+
`prepared AOT: ${where} carries a non-serializable literal (${typeof v}).`
|
|
7924
|
+
);
|
|
7925
|
+
}
|
|
7926
|
+
function bindMapOf(slots, where) {
|
|
7927
|
+
const out = {};
|
|
7928
|
+
for (const [field, slot] of Object.entries(slots)) {
|
|
7929
|
+
out[field] = bindOfSlot(slot, `${where} field '${field}'`);
|
|
7930
|
+
}
|
|
7931
|
+
return out;
|
|
7932
|
+
}
|
|
7933
|
+
function paramKindForField(metadata, field) {
|
|
7934
|
+
const f = metadata.fields.find((x) => x.propertyName === field);
|
|
7935
|
+
return f?.dynamoType === "N" ? "number" : "string";
|
|
7936
|
+
}
|
|
7937
|
+
function addParam(params, name, kind) {
|
|
7938
|
+
if (!(name in params)) params[name] = { type: kind, required: true };
|
|
7939
|
+
}
|
|
7940
|
+
function readRouteQuerySpec(route, metadata) {
|
|
7941
|
+
const params = {};
|
|
7942
|
+
const key = {};
|
|
7943
|
+
for (const field of Object.keys(route.keySlots)) {
|
|
7944
|
+
const kind = paramKindForField(metadata, field);
|
|
7945
|
+
params[field] = { kind, required: true };
|
|
7946
|
+
key[field] = kind === "number" ? param.number() : param.string();
|
|
7947
|
+
}
|
|
7948
|
+
const def = {
|
|
7949
|
+
__isOperationDefinition: true,
|
|
7950
|
+
entity: { name: route.modelClass.name, modelClass: route.modelClass },
|
|
7951
|
+
operation: route.kind === "query" ? "query" : "list",
|
|
7952
|
+
key,
|
|
7953
|
+
select: route.select,
|
|
7954
|
+
params
|
|
7955
|
+
};
|
|
7956
|
+
return buildQuerySpec(def);
|
|
7957
|
+
}
|
|
7958
|
+
function compileReadPlan(routes) {
|
|
7959
|
+
const specs = [];
|
|
7960
|
+
const params = {};
|
|
7961
|
+
const entities = {};
|
|
7962
|
+
for (const { alias, route } of routes) {
|
|
7963
|
+
const compiled = compileReadRoute(alias, route);
|
|
7964
|
+
const metadata = MetadataRegistry.get(compiled.modelClass);
|
|
7965
|
+
const entityName = compiled.modelClass.name;
|
|
7966
|
+
entities[entityName] = entityFingerprint(metadata);
|
|
7967
|
+
for (const [field, slot] of Object.entries(compiled.keySlots)) {
|
|
7968
|
+
if (slot.kind === "param") {
|
|
7969
|
+
addParam(params, slot.name, paramKindForField(metadata, field));
|
|
7970
|
+
}
|
|
7971
|
+
}
|
|
7972
|
+
const dynamicParam = (slot, kind) => {
|
|
7973
|
+
if (slot?.kind === "param") addParam(params, slot.name, kind);
|
|
7974
|
+
};
|
|
7975
|
+
dynamicParam(compiled.limitSlot, "number");
|
|
7976
|
+
dynamicParam(compiled.afterSlot, "string");
|
|
7977
|
+
dynamicParam(compiled.consistentReadSlot, "string");
|
|
7978
|
+
specs.push({
|
|
7979
|
+
alias,
|
|
7980
|
+
op: compiled.kind,
|
|
7981
|
+
entity: entityName,
|
|
7982
|
+
select: compiled.select,
|
|
7983
|
+
key: bindMapOf(compiled.keySlots, `${alias} key`),
|
|
7984
|
+
...compiled.maxDepth !== void 0 ? { maxDepth: compiled.maxDepth } : {},
|
|
7985
|
+
...compiled.order !== void 0 ? { order: compiled.order } : {},
|
|
7986
|
+
...compiled.filter !== void 0 ? { filter: compiled.filter } : {},
|
|
7987
|
+
...compiled.consistentReadSlot !== void 0 ? { consistentRead: bindOfSlot(compiled.consistentReadSlot, `${alias} consistentRead`) } : {},
|
|
7988
|
+
...compiled.limitSlot !== void 0 ? { limit: bindOfSlot(compiled.limitSlot, `${alias} limit`) } : {},
|
|
7989
|
+
...compiled.afterSlot !== void 0 ? { after: bindOfSlot(compiled.afterSlot, `${alias} after`) } : {},
|
|
7990
|
+
query: readRouteQuerySpec(compiled, metadata)
|
|
7991
|
+
});
|
|
7992
|
+
}
|
|
7993
|
+
return { kind: "read", params, entities, reads: specs };
|
|
7994
|
+
}
|
|
7995
|
+
function compileWritePlan(routes, planLabel) {
|
|
7996
|
+
const specs = [];
|
|
7997
|
+
const params = {};
|
|
7998
|
+
const entities = {};
|
|
7999
|
+
for (const { alias, route } of routes) {
|
|
8000
|
+
const analyzed = analyzeWriteRoute(alias, route);
|
|
8001
|
+
if (analyzed.intent === "create" && analyzed.conditionSlots !== void 0) {
|
|
8002
|
+
throw new Error(
|
|
8003
|
+
`prepared AOT (${planLabel} '${alias}'): a \`condition\` on a 'create' is not supported \u2014 a create already guards with attribute_not_exists(PK). Use an 'update' with a condition, or drop the condition.`
|
|
8004
|
+
);
|
|
8005
|
+
}
|
|
8006
|
+
const modelClass = resolveModelClass(analyzed.model);
|
|
8007
|
+
const metadata = MetadataRegistry.get(modelClass);
|
|
8008
|
+
const entityName = modelClass.name;
|
|
8009
|
+
entities[entityName] = entityFingerprint(metadata);
|
|
8010
|
+
const fragment = compileWriteFragment({
|
|
8011
|
+
alias,
|
|
8012
|
+
intent: analyzed.intent,
|
|
8013
|
+
model: analyzed.model,
|
|
8014
|
+
keyFieldNames: analyzed.keyFields,
|
|
8015
|
+
inputFieldNames: analyzed.inputFields
|
|
8016
|
+
});
|
|
8017
|
+
const { transaction, categories } = serializePreparedWriteFragment(
|
|
8018
|
+
`${planLabel}.${alias}`,
|
|
8019
|
+
fragment
|
|
8020
|
+
);
|
|
8021
|
+
for (const item of transaction.items) {
|
|
8022
|
+
if (item.literalKey === true || item.entity === MARKER_ROW_ENTITY) continue;
|
|
8023
|
+
if (item.entity in entities) continue;
|
|
8024
|
+
const cls = resolveRegisteredModel(item.entity, `${planLabel}.${alias}`);
|
|
8025
|
+
entities[item.entity] = entityFingerprint(MetadataRegistry.get(cls));
|
|
8026
|
+
}
|
|
8027
|
+
for (const [field, slot] of Object.entries({
|
|
8028
|
+
...analyzed.inputSlots,
|
|
8029
|
+
...analyzed.keySlots,
|
|
8030
|
+
...analyzed.conditionSlots ?? {}
|
|
8031
|
+
})) {
|
|
8032
|
+
if (slot.kind === "param") {
|
|
8033
|
+
addParam(params, slot.name, paramKindForField(metadata, field));
|
|
8034
|
+
}
|
|
8035
|
+
}
|
|
8036
|
+
let readback;
|
|
8037
|
+
const resultSelect = analyzed.result?.select;
|
|
8038
|
+
if (resultSelect !== void 0 && Object.keys(resultSelect).length > 0) {
|
|
8039
|
+
const kf = {};
|
|
8040
|
+
const kp = {};
|
|
8041
|
+
for (const field of fragment.keyFields) {
|
|
8042
|
+
const kind = paramKindForField(metadata, field);
|
|
8043
|
+
kp[field] = { kind, required: true };
|
|
8044
|
+
kf[field] = kind === "number" ? param.number() : param.string();
|
|
8045
|
+
}
|
|
8046
|
+
readback = buildQuerySpec({
|
|
8047
|
+
__isOperationDefinition: true,
|
|
8048
|
+
entity: { name: entityName, modelClass },
|
|
8049
|
+
operation: "query",
|
|
8050
|
+
key: kf,
|
|
8051
|
+
select: resultSelect,
|
|
8052
|
+
params: kp
|
|
8053
|
+
});
|
|
8054
|
+
}
|
|
8055
|
+
specs.push({
|
|
8056
|
+
alias,
|
|
8057
|
+
entity: entityName,
|
|
8058
|
+
intent: analyzed.intent,
|
|
8059
|
+
keyFields: fragment.keyFields,
|
|
8060
|
+
key: bindMapOf(analyzed.keySlots, `${alias} key`),
|
|
8061
|
+
input: bindMapOf(analyzed.inputSlots, `${alias} input`),
|
|
8062
|
+
...analyzed.conditionSlots !== void 0 ? { condition: bindMapOf(analyzed.conditionSlots, `${alias} condition`) } : {},
|
|
8063
|
+
...analyzed.result !== void 0 ? { result: analyzed.result } : {},
|
|
8064
|
+
...readback !== void 0 ? { readback } : {},
|
|
8065
|
+
transaction,
|
|
8066
|
+
categories
|
|
8067
|
+
});
|
|
8068
|
+
}
|
|
8069
|
+
return { kind: "write", params, entities, writes: specs };
|
|
8070
|
+
}
|
|
8071
|
+
function resolveRegisteredModel(name, label) {
|
|
8072
|
+
for (const cls of MetadataRegistry.getAll().keys()) {
|
|
8073
|
+
if (cls.name === name) {
|
|
8074
|
+
return cls;
|
|
8075
|
+
}
|
|
8076
|
+
}
|
|
8077
|
+
throw new Error(
|
|
8078
|
+
`prepared AOT (${label}): a derived effect targets entity '${name}', which is not a registered model.`
|
|
8079
|
+
);
|
|
8080
|
+
}
|
|
8081
|
+
function compilePreparedPlan(body, planLabel = "prepared") {
|
|
8082
|
+
const { kind, routes } = evaluatePreparedRoutes(body);
|
|
8083
|
+
if (kind === "read") {
|
|
8084
|
+
return compileReadPlan(
|
|
8085
|
+
routes
|
|
8086
|
+
);
|
|
8087
|
+
}
|
|
8088
|
+
return compileWritePlan(
|
|
8089
|
+
routes,
|
|
8090
|
+
planLabel
|
|
8091
|
+
);
|
|
8092
|
+
}
|
|
8093
|
+
function buildPreparedPlanDocument(plans) {
|
|
8094
|
+
const sorted = {};
|
|
8095
|
+
for (const id of Object.keys(plans).sort()) sorted[id] = plans[id];
|
|
8096
|
+
return {
|
|
8097
|
+
formatVersion: PREPARED_FORMAT_VERSION,
|
|
8098
|
+
specVersion: SPEC_VERSION,
|
|
8099
|
+
plans: sorted
|
|
8100
|
+
};
|
|
8101
|
+
}
|
|
8102
|
+
|
|
7554
8103
|
// src/spec/index.ts
|
|
7555
8104
|
function buildBridgeBundle(queries = {}, commands = {}, registry = MetadataRegistry, transactions = {}, contractInputs = {}) {
|
|
7556
8105
|
const bundle = {
|
|
@@ -7564,6 +8113,7 @@ function buildBridgeBundle(queries = {}, commands = {}, registry = MetadataRegis
|
|
|
7564
8113
|
export {
|
|
7565
8114
|
SPEC_VERSION,
|
|
7566
8115
|
MARKER_ROW_ENTITY,
|
|
8116
|
+
buildManifestEntity,
|
|
7567
8117
|
buildManifest,
|
|
7568
8118
|
buildProjection,
|
|
7569
8119
|
compileFilterExpression,
|
|
@@ -7601,6 +8151,9 @@ export {
|
|
|
7601
8151
|
MAX_TRANSACT_COMPOSE_ITEMS,
|
|
7602
8152
|
compileMutationPlan,
|
|
7603
8153
|
executeCommandMethod,
|
|
8154
|
+
LOGICAL_EFFECT_CATEGORIES,
|
|
8155
|
+
executeLogicalWriteOps,
|
|
8156
|
+
executeLogicalParallelWrites,
|
|
7604
8157
|
isPreparedParamRef,
|
|
7605
8158
|
PreparedWriteStatement,
|
|
7606
8159
|
PreparedReadStatement,
|
|
@@ -7646,5 +8199,11 @@ export {
|
|
|
7646
8199
|
assertContractBoundaries,
|
|
7647
8200
|
buildQuerySpec,
|
|
7648
8201
|
buildOperations,
|
|
8202
|
+
PREPARED_FORMAT_VERSION,
|
|
8203
|
+
canonicalJson,
|
|
8204
|
+
entityFingerprint,
|
|
8205
|
+
planFingerprint,
|
|
8206
|
+
compilePreparedPlan,
|
|
8207
|
+
buildPreparedPlanDocument,
|
|
7649
8208
|
buildBridgeBundle
|
|
7650
8209
|
};
|