graphddb 0.6.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-N5NQM3SO.js → chunk-C5Q2NMRW.js} +1072 -25
- package/dist/cli.js +336 -12
- package/dist/{from-change-CFzBy7aU.d.ts → from-change-CWiXBcgi.d.ts} +1 -1
- package/dist/{index-CtJbTrfB.d.ts → index-CvS9ATKB.d.ts} +289 -104
- package/dist/index.d.ts +85 -7
- package/dist/index.js +269 -1
- package/dist/linter/index.d.ts +4 -4
- package/dist/{maintenance-view-adapter-CFeasCKo.d.ts → maintenance-view-adapter-NBTZbE8-.d.ts} +2539 -2242
- package/dist/{registry-DbqmFyab.d.ts → registry-BndKbZbg.d.ts} +1 -1
- package/dist/{relation-depth-0TiWr5OW.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 +216 -0
- package/dist/transform/index.js +982 -0
- package/docs/cqrs-contract.md +1 -1
- package/docs/design-patterns.md +9 -7
- package/docs/prepared-statements.md +232 -0
- package/docs/python-bridge.md +24 -4
- package/package.json +5 -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;
|
|
@@ -3760,17 +3970,15 @@ function normalizeWriteOp(alias, d) {
|
|
|
3760
3970
|
}
|
|
3761
3971
|
return [{ ...base, key: d.key }];
|
|
3762
3972
|
}
|
|
3763
|
-
function
|
|
3764
|
-
const keyFieldNames2 =
|
|
3765
|
-
const inputFieldNames = Object.keys(op.input);
|
|
3766
|
-
const model = op.model;
|
|
3973
|
+
function compileWriteFragment(input) {
|
|
3974
|
+
const { alias, intent, model, keyFieldNames: keyFieldNames2, inputFieldNames } = input;
|
|
3767
3975
|
const body = ($) => {
|
|
3768
3976
|
const descriptor = {
|
|
3769
|
-
[
|
|
3977
|
+
[intent]: (() => model),
|
|
3770
3978
|
key: mapToRefs($, keyFieldNames2),
|
|
3771
3979
|
...inputFieldNames.length > 0 ? { input: mapToRefs($, inputFieldNames) } : {}
|
|
3772
3980
|
};
|
|
3773
|
-
const map = { [
|
|
3981
|
+
const map = { [alias]: descriptor };
|
|
3774
3982
|
return map;
|
|
3775
3983
|
};
|
|
3776
3984
|
const plan2 = mutation(body);
|
|
@@ -3778,7 +3986,16 @@ function compileWriteOp(op) {
|
|
|
3778
3986
|
throw new Error("DDBModel.mutate: internal error building the mutation plan.");
|
|
3779
3987
|
}
|
|
3780
3988
|
const compiled = compileMutationPlan(plan2);
|
|
3781
|
-
|
|
3989
|
+
return compiled.fragments[0];
|
|
3990
|
+
}
|
|
3991
|
+
function compileWriteOp(op) {
|
|
3992
|
+
const fragment = compileWriteFragment({
|
|
3993
|
+
alias: op.alias,
|
|
3994
|
+
intent: op.intent,
|
|
3995
|
+
model: op.model,
|
|
3996
|
+
keyFieldNames: Object.keys(op.key),
|
|
3997
|
+
inputFieldNames: Object.keys(op.input)
|
|
3998
|
+
});
|
|
3782
3999
|
const params = { ...op.input, ...op.key };
|
|
3783
4000
|
return {
|
|
3784
4001
|
fragment,
|
|
@@ -3847,6 +4064,478 @@ function resolveModelStatic(ref, alias) {
|
|
|
3847
4064
|
return ref;
|
|
3848
4065
|
}
|
|
3849
4066
|
|
|
4067
|
+
// src/runtime/prepared.ts
|
|
4068
|
+
var PREPARE_LABEL = "DDBModel.prepare";
|
|
4069
|
+
var PARAM_REF_BRAND = /* @__PURE__ */ Symbol("graphddb:preparedParamRef");
|
|
4070
|
+
function isPreparedParamRef(value) {
|
|
4071
|
+
return typeof value === "object" && value !== null && value[PARAM_REF_BRAND] === true;
|
|
4072
|
+
}
|
|
4073
|
+
function makeParamRef(name) {
|
|
4074
|
+
const origin = `the placeholder '${name}' (\`$.${name}\`)`;
|
|
4075
|
+
const target = /* @__PURE__ */ Object.create(null);
|
|
4076
|
+
const proxy = new Proxy(target, {
|
|
4077
|
+
get(_t, prop) {
|
|
4078
|
+
if (prop === PARAM_REF_BRAND) return true;
|
|
4079
|
+
if (prop === "name") return name;
|
|
4080
|
+
if (typeof prop === "symbol") return void 0;
|
|
4081
|
+
throw new Error(
|
|
4082
|
+
`${PREPARE_LABEL}: unsupported access '${String(prop)}' on ${origin}. A \`key\` / \`input\` / option value only supports a direct \`$.name\` param reference or a concrete static literal \u2014 never a transform, a nested access, or a coercion (that would capture a per-call runtime value).`
|
|
4083
|
+
);
|
|
4084
|
+
},
|
|
4085
|
+
set() {
|
|
4086
|
+
throw new Error(`${PREPARE_LABEL}: cannot assign on ${origin}.`);
|
|
4087
|
+
}
|
|
4088
|
+
});
|
|
4089
|
+
return proxy;
|
|
4090
|
+
}
|
|
4091
|
+
function makePreparedInputProxy() {
|
|
4092
|
+
const cache = /* @__PURE__ */ new Map();
|
|
4093
|
+
return new Proxy(/* @__PURE__ */ Object.create(null), {
|
|
4094
|
+
get(_t, prop) {
|
|
4095
|
+
if (typeof prop === "symbol") return void 0;
|
|
4096
|
+
const name = prop;
|
|
4097
|
+
let ref = cache.get(name);
|
|
4098
|
+
if (!ref) {
|
|
4099
|
+
ref = makeParamRef(name);
|
|
4100
|
+
cache.set(name, ref);
|
|
4101
|
+
}
|
|
4102
|
+
return ref;
|
|
4103
|
+
},
|
|
4104
|
+
set() {
|
|
4105
|
+
throw new Error(`${PREPARE_LABEL}: cannot assign to the \`$\` placeholder proxy.`);
|
|
4106
|
+
}
|
|
4107
|
+
});
|
|
4108
|
+
}
|
|
4109
|
+
function assertNoRuntimeCapture(value, where) {
|
|
4110
|
+
if (isPreparedParamRef(value)) return;
|
|
4111
|
+
if (value === null) return;
|
|
4112
|
+
const t = typeof value;
|
|
4113
|
+
if (t === "string" || t === "number" || t === "boolean" || t === "bigint") return;
|
|
4114
|
+
if (value instanceof Date) return;
|
|
4115
|
+
if (t === "undefined") {
|
|
4116
|
+
throw new Error(
|
|
4117
|
+
`${PREPARE_LABEL}: ${where} is undefined. Bind a \`$.name\` param reference or a concrete static literal \u2014 or omit the field from the template entirely.`
|
|
4118
|
+
);
|
|
4119
|
+
}
|
|
4120
|
+
if (t === "function") {
|
|
4121
|
+
throw new Error(
|
|
4122
|
+
`${PREPARE_LABEL}: ${where} is a function. A prepared body may only bind a \`$.name\` param reference or a static literal here; a function is a captured per-call value (no runtime capture \u2014 see the prepared-statement contract).`
|
|
4123
|
+
);
|
|
4124
|
+
}
|
|
4125
|
+
if (t === "symbol") {
|
|
4126
|
+
throw new Error(`${PREPARE_LABEL}: ${where} is a symbol, which is not a bindable value.`);
|
|
4127
|
+
}
|
|
4128
|
+
throw new Error(
|
|
4129
|
+
`${PREPARE_LABEL}: ${where} is a non-literal object. A prepared body may only bind a \`$.name\` param reference or a static literal here \u2014 an object value is a captured per-call runtime value.`
|
|
4130
|
+
);
|
|
4131
|
+
}
|
|
4132
|
+
function recordSlots(binding, where) {
|
|
4133
|
+
const fields = Object.keys(binding);
|
|
4134
|
+
const slots = {};
|
|
4135
|
+
for (const f of fields) {
|
|
4136
|
+
const v = binding[f];
|
|
4137
|
+
if (isPreparedParamRef(v)) {
|
|
4138
|
+
slots[f] = { kind: "param", name: v.name };
|
|
4139
|
+
} else {
|
|
4140
|
+
assertNoRuntimeCapture(v, `${where} field '${f}'`);
|
|
4141
|
+
slots[f] = { kind: "literal", value: v };
|
|
4142
|
+
}
|
|
4143
|
+
}
|
|
4144
|
+
return { fields, slots };
|
|
4145
|
+
}
|
|
4146
|
+
function bindSlots(slots, params) {
|
|
4147
|
+
const out = {};
|
|
4148
|
+
for (const [field, slot] of Object.entries(slots)) {
|
|
4149
|
+
out[field] = slot.kind === "param" ? params[slot.name] : slot.value;
|
|
4150
|
+
}
|
|
4151
|
+
return out;
|
|
4152
|
+
}
|
|
4153
|
+
function bindOptionalSlot(slot, params) {
|
|
4154
|
+
if (slot === void 0) return void 0;
|
|
4155
|
+
return slot.kind === "param" ? params[slot.name] : slot.value;
|
|
4156
|
+
}
|
|
4157
|
+
function recordOptionSlot(value, where) {
|
|
4158
|
+
if (value === void 0) return void 0;
|
|
4159
|
+
if (isPreparedParamRef(value)) return { kind: "param", name: value.name };
|
|
4160
|
+
assertNoRuntimeCapture(value, where);
|
|
4161
|
+
return { kind: "literal", value };
|
|
4162
|
+
}
|
|
4163
|
+
var PreparedWriteStatement = class {
|
|
4164
|
+
/** @internal */
|
|
4165
|
+
constructor(ops) {
|
|
4166
|
+
this.ops = ops;
|
|
4167
|
+
}
|
|
4168
|
+
ops;
|
|
4169
|
+
async execute(params = {}, options = {}) {
|
|
4170
|
+
const compiled = this.ops.map((op) => ({
|
|
4171
|
+
fragment: op.fragment,
|
|
4172
|
+
params: { ...bindSlots(op.inputSlots, params), ...bindSlots(op.keySlots, params) },
|
|
4173
|
+
...op.conditionSlots !== void 0 ? { condition: bindSlots(op.conditionSlots, params) } : {},
|
|
4174
|
+
...op.result !== void 0 ? { result: op.result } : {}
|
|
4175
|
+
}));
|
|
4176
|
+
const mode = options.mode ?? "transaction";
|
|
4177
|
+
const runOpts = {
|
|
4178
|
+
label: PREPARE_LABEL,
|
|
4179
|
+
...options.retry !== void 0 ? { retry: options.retry } : {},
|
|
4180
|
+
...options.context !== void 0 ? { context: options.context } : {}
|
|
4181
|
+
};
|
|
4182
|
+
if (mode === "transaction") {
|
|
4183
|
+
const { readBacks } = await executeCompiledWriteOp(compiled, runOpts);
|
|
4184
|
+
const out2 = {};
|
|
4185
|
+
this.ops.forEach((op, i) => {
|
|
4186
|
+
if (op.result !== void 0) out2[op.alias] = readBacks[i];
|
|
4187
|
+
});
|
|
4188
|
+
return out2;
|
|
4189
|
+
}
|
|
4190
|
+
const settled = await executeCompiledParallelWrites(compiled, runOpts);
|
|
4191
|
+
const out = {};
|
|
4192
|
+
this.ops.forEach((op, i) => {
|
|
4193
|
+
const res = settled[i];
|
|
4194
|
+
out[op.alias] = res.ok ? { ok: true, value: op.result !== void 0 ? res.value : void 0 } : { ok: false, error: res.error };
|
|
4195
|
+
});
|
|
4196
|
+
return out;
|
|
4197
|
+
}
|
|
4198
|
+
};
|
|
4199
|
+
var PreparedReadStatement = class {
|
|
4200
|
+
/** @internal */
|
|
4201
|
+
constructor(routes) {
|
|
4202
|
+
this.routes = routes;
|
|
4203
|
+
}
|
|
4204
|
+
routes;
|
|
4205
|
+
async execute(params = {}, options = {}) {
|
|
4206
|
+
const results = await Promise.all(
|
|
4207
|
+
this.routes.map((route) => this.runRoute(route, params, options))
|
|
4208
|
+
);
|
|
4209
|
+
const out = {};
|
|
4210
|
+
this.routes.forEach((route, i) => {
|
|
4211
|
+
out[route.alias] = results[i];
|
|
4212
|
+
});
|
|
4213
|
+
return out;
|
|
4214
|
+
}
|
|
4215
|
+
runRoute(route, params, options) {
|
|
4216
|
+
const key = bindSlots(route.keySlots, params);
|
|
4217
|
+
const consistentRead = bindOptionalSlot(route.consistentReadSlot, params);
|
|
4218
|
+
if (route.kind === "query") {
|
|
4219
|
+
return executeQuery(route.modelClass, key, route.select, {
|
|
4220
|
+
consistentRead,
|
|
4221
|
+
maxDepth: route.maxDepth,
|
|
4222
|
+
...options.retry !== void 0 ? { retry: options.retry } : {},
|
|
4223
|
+
...options.context !== void 0 ? { context: options.context } : {}
|
|
4224
|
+
});
|
|
4225
|
+
}
|
|
4226
|
+
const limit = bindOptionalSlot(route.limitSlot, params);
|
|
4227
|
+
const after = bindOptionalSlot(route.afterSlot, params);
|
|
4228
|
+
return executeList(route.modelClass, key, route.select, {
|
|
4229
|
+
limit,
|
|
4230
|
+
after,
|
|
4231
|
+
order: route.order,
|
|
4232
|
+
filter: route.filter,
|
|
4233
|
+
...options.retry !== void 0 ? { retry: options.retry } : {},
|
|
4234
|
+
...options.context !== void 0 ? { context: options.context } : {}
|
|
4235
|
+
});
|
|
4236
|
+
}
|
|
4237
|
+
};
|
|
4238
|
+
var READ_INTENT_KEYS = ["query", "list"];
|
|
4239
|
+
function classify(routes) {
|
|
4240
|
+
let kind;
|
|
4241
|
+
for (const { alias, route } of routes) {
|
|
4242
|
+
const rec = route;
|
|
4243
|
+
const isRead = READ_INTENT_KEYS.some((k) => rec[k] !== void 0);
|
|
4244
|
+
const isWrite = INTENT_KEYS.some((k) => rec[k] !== void 0);
|
|
4245
|
+
if (isRead === isWrite) {
|
|
4246
|
+
throw new Error(
|
|
4247
|
+
`${PREPARE_LABEL}: the descriptor for alias '${alias}' must carry exactly one of a read intent (\`query\` / \`list\`) OR a write intent (\`create\` / \`update\` / \`remove\`). It declared ${isRead && isWrite ? "both" : "neither"}.`
|
|
4248
|
+
);
|
|
4249
|
+
}
|
|
4250
|
+
const routeKind = isRead ? "read" : "write";
|
|
4251
|
+
if (kind === void 0) kind = routeKind;
|
|
4252
|
+
else if (kind !== routeKind) {
|
|
4253
|
+
throw new Error(
|
|
4254
|
+
`${PREPARE_LABEL}: a prepared body must be all-read or all-write; alias '${alias}' is a ${routeKind} route but a previous alias was a ${kind} route. Split them into two prepared statements.`
|
|
4255
|
+
);
|
|
4256
|
+
}
|
|
4257
|
+
}
|
|
4258
|
+
if (kind === void 0) {
|
|
4259
|
+
throw new Error(
|
|
4260
|
+
`${PREPARE_LABEL}: the prepared body declared no routes. Declare at least one \`{ alias: { query|list|create|update|remove: () => Model, key, ... } }\`.`
|
|
4261
|
+
);
|
|
4262
|
+
}
|
|
4263
|
+
return kind;
|
|
4264
|
+
}
|
|
4265
|
+
function analyzeWriteRoute(alias, route) {
|
|
4266
|
+
const present = INTENT_KEYS.filter((k) => route[k] !== void 0);
|
|
4267
|
+
if (present.length !== 1) {
|
|
4268
|
+
throw new Error(
|
|
4269
|
+
`${PREPARE_LABEL}: the write descriptor for alias '${alias}' must carry exactly one intent (\`create\` / \`update\` / \`remove\`), but declared ${present.length === 0 ? "none" : present.join(", ")}.`
|
|
4270
|
+
);
|
|
4271
|
+
}
|
|
4272
|
+
const intent = present[0];
|
|
4273
|
+
if (route.key === null || typeof route.key !== "object") {
|
|
4274
|
+
throw new Error(
|
|
4275
|
+
`${PREPARE_LABEL}: the '${intent}' descriptor for alias '${alias}' must declare a \`key\` object binding the target's primary-key fields.`
|
|
4276
|
+
);
|
|
4277
|
+
}
|
|
4278
|
+
const model = resolveModelStatic(route[intent], alias);
|
|
4279
|
+
const keyRec = recordSlots(route.key, `${alias} key`);
|
|
4280
|
+
const inputRec = recordSlots(
|
|
4281
|
+
route.input ?? {},
|
|
4282
|
+
`${alias} input`
|
|
4283
|
+
);
|
|
4284
|
+
const conditionRec = route.condition !== void 0 ? recordSlots(route.condition, `${alias} condition`) : void 0;
|
|
4285
|
+
if (route.result !== void 0) assertStaticTemplate(route.result, `${alias} result`);
|
|
4286
|
+
return {
|
|
4287
|
+
alias,
|
|
4288
|
+
intent,
|
|
4289
|
+
model,
|
|
4290
|
+
keyFields: keyRec.fields,
|
|
4291
|
+
inputFields: inputRec.fields,
|
|
4292
|
+
keySlots: keyRec.slots,
|
|
4293
|
+
inputSlots: inputRec.slots,
|
|
4294
|
+
...conditionRec !== void 0 ? { conditionSlots: conditionRec.slots } : {},
|
|
4295
|
+
...route.result !== void 0 ? { result: route.result } : {}
|
|
4296
|
+
};
|
|
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
|
+
}
|
|
4317
|
+
function compileReadRoute(alias, route) {
|
|
4318
|
+
const isQuery = route.query !== void 0;
|
|
4319
|
+
const isList = route.list !== void 0;
|
|
4320
|
+
if (isQuery === isList) {
|
|
4321
|
+
throw new Error(
|
|
4322
|
+
`${PREPARE_LABEL}: the read descriptor for alias '${alias}' must carry exactly one of \`query\` or \`list\`. It declared ${isQuery && isList ? "both" : "neither"}.`
|
|
4323
|
+
);
|
|
4324
|
+
}
|
|
4325
|
+
if (route.key === null || typeof route.key !== "object" || route.select === void 0) {
|
|
4326
|
+
throw new Error(
|
|
4327
|
+
`${PREPARE_LABEL}: the '${isQuery ? "query" : "list"}' descriptor for alias '${alias}' must declare both \`key\` and \`select\`.`
|
|
4328
|
+
);
|
|
4329
|
+
}
|
|
4330
|
+
const model = resolveModelStatic(isQuery ? route.query : route.list, alias);
|
|
4331
|
+
const modelClass = resolveModelClass(model);
|
|
4332
|
+
const keyRec = recordSlots(route.key, `${alias} key`);
|
|
4333
|
+
assertStaticTemplate(route.select, `${alias} select`);
|
|
4334
|
+
const opt = route.options ?? {};
|
|
4335
|
+
if (isQuery) {
|
|
4336
|
+
for (const unsupported of ["limit", "after", "order", "filter"]) {
|
|
4337
|
+
if (opt[unsupported] !== void 0) {
|
|
4338
|
+
throw new Error(
|
|
4339
|
+
`${PREPARE_LABEL}: the 'query' descriptor for alias '${alias}' declares \`options.${unsupported}\`, which a single-entity query does not support (a query supports \`consistentRead\` / \`maxDepth\`). Use a \`list\` route for pagination / ordering / filtering.`
|
|
4340
|
+
);
|
|
4341
|
+
}
|
|
4342
|
+
}
|
|
4343
|
+
} else if (opt.consistentRead !== void 0) {
|
|
4344
|
+
throw new Error(
|
|
4345
|
+
`${PREPARE_LABEL}: the 'list' descriptor for alias '${alias}' declares \`options.consistentRead\`, which \`Model.list\` does not support \u2014 it would be silently ignored. Remove it (a list always reads eventually consistent).`
|
|
4346
|
+
);
|
|
4347
|
+
}
|
|
4348
|
+
if (opt.filter !== void 0) {
|
|
4349
|
+
assertStaticTemplate(opt.filter, `${alias} options.filter`);
|
|
4350
|
+
}
|
|
4351
|
+
return {
|
|
4352
|
+
alias,
|
|
4353
|
+
kind: isQuery ? "query" : "list",
|
|
4354
|
+
modelClass,
|
|
4355
|
+
select: route.select,
|
|
4356
|
+
keySlots: keyRec.slots,
|
|
4357
|
+
...typeof opt.maxDepth === "number" ? { maxDepth: opt.maxDepth } : {},
|
|
4358
|
+
...opt.order !== void 0 ? { order: opt.order } : {},
|
|
4359
|
+
...opt.filter !== void 0 ? { filter: opt.filter } : {},
|
|
4360
|
+
...(() => {
|
|
4361
|
+
const s = recordOptionSlot(opt.consistentRead, `${alias} options.consistentRead`);
|
|
4362
|
+
return s !== void 0 ? { consistentReadSlot: s } : {};
|
|
4363
|
+
})(),
|
|
4364
|
+
...(() => {
|
|
4365
|
+
const s = recordOptionSlot(opt.limit, `${alias} options.limit`);
|
|
4366
|
+
return s !== void 0 ? { limitSlot: s } : {};
|
|
4367
|
+
})(),
|
|
4368
|
+
...(() => {
|
|
4369
|
+
const s = recordOptionSlot(opt.after, `${alias} options.after`);
|
|
4370
|
+
return s !== void 0 ? { afterSlot: s } : {};
|
|
4371
|
+
})()
|
|
4372
|
+
};
|
|
4373
|
+
}
|
|
4374
|
+
function assertStaticTemplate(value, where) {
|
|
4375
|
+
if (isPreparedParamRef(value)) {
|
|
4376
|
+
throw new Error(
|
|
4377
|
+
`${PREPARE_LABEL}: ${where} is a \`$\` param reference. This position is a static projection / template \u2014 only \`key\` (and the dynamic pagination options) may bind params.`
|
|
4378
|
+
);
|
|
4379
|
+
}
|
|
4380
|
+
if (typeof value === "function") {
|
|
4381
|
+
throw new Error(
|
|
4382
|
+
`${PREPARE_LABEL}: ${where} is a function \u2014 a captured per-call value (no runtime capture). This position must be a static, params-independent template.`
|
|
4383
|
+
);
|
|
4384
|
+
}
|
|
4385
|
+
if (value !== null && typeof value === "object") {
|
|
4386
|
+
if (Array.isArray(value)) {
|
|
4387
|
+
value.forEach((v, i) => assertStaticTemplate(v, `${where}[${i}]`));
|
|
4388
|
+
} else {
|
|
4389
|
+
for (const [k, v] of Object.entries(value)) {
|
|
4390
|
+
assertStaticTemplate(v, `${where}.${k}`);
|
|
4391
|
+
}
|
|
4392
|
+
}
|
|
4393
|
+
}
|
|
4394
|
+
}
|
|
4395
|
+
var PREPARE_CACHE_MAX = 256;
|
|
4396
|
+
var BoundedLru = class {
|
|
4397
|
+
constructor(max) {
|
|
4398
|
+
this.max = max;
|
|
4399
|
+
}
|
|
4400
|
+
max;
|
|
4401
|
+
map = /* @__PURE__ */ new Map();
|
|
4402
|
+
get(key) {
|
|
4403
|
+
const v = this.map.get(key);
|
|
4404
|
+
if (v !== void 0) {
|
|
4405
|
+
this.map.delete(key);
|
|
4406
|
+
this.map.set(key, v);
|
|
4407
|
+
}
|
|
4408
|
+
return v;
|
|
4409
|
+
}
|
|
4410
|
+
set(key, value) {
|
|
4411
|
+
if (this.map.has(key)) this.map.delete(key);
|
|
4412
|
+
this.map.set(key, value);
|
|
4413
|
+
if (this.map.size > this.max) {
|
|
4414
|
+
const oldest = this.map.keys().next().value;
|
|
4415
|
+
if (oldest !== void 0) this.map.delete(oldest);
|
|
4416
|
+
}
|
|
4417
|
+
}
|
|
4418
|
+
get size() {
|
|
4419
|
+
return this.map.size;
|
|
4420
|
+
}
|
|
4421
|
+
clear() {
|
|
4422
|
+
this.map.clear();
|
|
4423
|
+
}
|
|
4424
|
+
};
|
|
4425
|
+
var PREPARE_CACHE = new BoundedLru(PREPARE_CACHE_MAX);
|
|
4426
|
+
function structureKey(routes) {
|
|
4427
|
+
const parts = routes.map(({ alias, route }) => {
|
|
4428
|
+
const rec = route;
|
|
4429
|
+
const intent = [...INTENT_KEYS, ...READ_INTENT_KEYS].find((k) => rec[k] !== void 0) ?? "?";
|
|
4430
|
+
const modelRef = rec[intent];
|
|
4431
|
+
const modelId = modelIdentity(modelRef, alias);
|
|
4432
|
+
const key = describeBinding(route.key);
|
|
4433
|
+
const rw = route;
|
|
4434
|
+
const rr = route;
|
|
4435
|
+
const extra = intent === "query" || intent === "list" ? `select=${stableJson(rr.select)};opt=${describeReadOptions(rr.options)}` : `input=${describeBinding(rw.input)};cond=${describeBinding(rw.condition)};result=${stableJson(rw.result)}`;
|
|
4436
|
+
return `${alias}|${intent}|${modelId}|key=${key}|${extra}`;
|
|
4437
|
+
});
|
|
4438
|
+
return parts.join("||");
|
|
4439
|
+
}
|
|
4440
|
+
var MODEL_IDENTITY_IDS = /* @__PURE__ */ new WeakMap();
|
|
4441
|
+
var nextModelIdentityId = 1;
|
|
4442
|
+
function modelIdentity(ref, alias) {
|
|
4443
|
+
try {
|
|
4444
|
+
const model = resolveModelStatic(ref, alias);
|
|
4445
|
+
const cls = resolveModelClass(model);
|
|
4446
|
+
let id = MODEL_IDENTITY_IDS.get(cls);
|
|
4447
|
+
if (id === void 0) {
|
|
4448
|
+
id = nextModelIdentityId++;
|
|
4449
|
+
MODEL_IDENTITY_IDS.set(cls, id);
|
|
4450
|
+
}
|
|
4451
|
+
return `model#${id}`;
|
|
4452
|
+
} catch {
|
|
4453
|
+
return "unresolved";
|
|
4454
|
+
}
|
|
4455
|
+
}
|
|
4456
|
+
function describeBinding(binding) {
|
|
4457
|
+
if (binding === void 0) return "\u2205";
|
|
4458
|
+
const keys = Object.keys(binding).sort();
|
|
4459
|
+
return keys.map((k) => {
|
|
4460
|
+
const v = binding[k];
|
|
4461
|
+
return isPreparedParamRef(v) ? `${k}=$(${v.name})` : `${k}=L(${stableJson(v)})`;
|
|
4462
|
+
}).join(",");
|
|
4463
|
+
}
|
|
4464
|
+
function describeReadOptions(options) {
|
|
4465
|
+
if (options === void 0) return "\u2205";
|
|
4466
|
+
const o = options;
|
|
4467
|
+
const slot = (v) => isPreparedParamRef(v) ? `$(${v.name})` : v === void 0 ? "\u2205" : `L(${stableJson(v)})`;
|
|
4468
|
+
return `cr=${slot(o.consistentRead)};limit=${slot(o.limit)};after=${slot(o.after)};depth=${o.maxDepth ?? "\u2205"};order=${o.order ?? "\u2205"};filter=${stableJson(o.filter)}`;
|
|
4469
|
+
}
|
|
4470
|
+
function stableJson(value) {
|
|
4471
|
+
return JSON.stringify(value, (_k, v) => {
|
|
4472
|
+
if (isPreparedParamRef(v)) return `$${v.name}`;
|
|
4473
|
+
if (v !== null && typeof v === "object" && !Array.isArray(v)) {
|
|
4474
|
+
const sorted = {};
|
|
4475
|
+
for (const k of Object.keys(v).sort()) {
|
|
4476
|
+
sorted[k] = v[k];
|
|
4477
|
+
}
|
|
4478
|
+
return sorted;
|
|
4479
|
+
}
|
|
4480
|
+
return v;
|
|
4481
|
+
});
|
|
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
|
+
}
|
|
4502
|
+
function prepare(body) {
|
|
4503
|
+
if (typeof body !== "function") {
|
|
4504
|
+
throw new Error(
|
|
4505
|
+
`${PREPARE_LABEL}: expected a body function \`($) => ({ alias: { ... } })\`.`
|
|
4506
|
+
);
|
|
4507
|
+
}
|
|
4508
|
+
const $ = makePreparedInputProxy();
|
|
4509
|
+
const map = body($);
|
|
4510
|
+
if (map === null || typeof map !== "object") {
|
|
4511
|
+
throw new Error(
|
|
4512
|
+
`${PREPARE_LABEL}: the body must return a descriptor map \`{ alias: { ... } }\`.`
|
|
4513
|
+
);
|
|
4514
|
+
}
|
|
4515
|
+
const routes = Object.keys(map).map((alias) => ({
|
|
4516
|
+
alias,
|
|
4517
|
+
route: map[alias]
|
|
4518
|
+
}));
|
|
4519
|
+
const cacheKey = structureKey(routes);
|
|
4520
|
+
const cached = PREPARE_CACHE.get(cacheKey);
|
|
4521
|
+
if (cached !== void 0) return cached;
|
|
4522
|
+
const kind = classify(routes);
|
|
4523
|
+
let handle;
|
|
4524
|
+
if (kind === "write") {
|
|
4525
|
+
const ops = routes.map(
|
|
4526
|
+
({ alias, route }) => compileWriteRoute(alias, route)
|
|
4527
|
+
);
|
|
4528
|
+
handle = new PreparedWriteStatement(ops);
|
|
4529
|
+
} else {
|
|
4530
|
+
const compiledRoutes = routes.map(
|
|
4531
|
+
({ alias, route }) => compileReadRoute(alias, route)
|
|
4532
|
+
);
|
|
4533
|
+
handle = new PreparedReadStatement(compiledRoutes);
|
|
4534
|
+
}
|
|
4535
|
+
PREPARE_CACHE.set(cacheKey, handle);
|
|
4536
|
+
return handle;
|
|
4537
|
+
}
|
|
4538
|
+
|
|
3850
4539
|
// src/model/DDBModel.ts
|
|
3851
4540
|
var DDBModel = class {
|
|
3852
4541
|
/**
|
|
@@ -3921,6 +4610,43 @@ var DDBModel = class {
|
|
|
3921
4610
|
static mutate(envelope, options) {
|
|
3922
4611
|
return executeWriteEnvelope(envelope, options);
|
|
3923
4612
|
}
|
|
4613
|
+
/**
|
|
4614
|
+
* **Prepared statement** (issue #205 / design #203) — the read/write-unified
|
|
4615
|
+
* middle tier between the ad-hoc `DDBModel.mutate` / `DDBModel.query` (per-call
|
|
4616
|
+
* recompiled) and the public CQRS contract (`publicCommandModel` /
|
|
4617
|
+
* `publicQueryModel`, precompiled but contract-welded).
|
|
4618
|
+
*
|
|
4619
|
+
* `prepare($ => ({...}))` **compiles the declarative route body once** and returns
|
|
4620
|
+
* a handle whose `.execute(params)` binds the per-call values and runs through the
|
|
4621
|
+
* SAME execution cores those two surfaces use — so a write's derived maintainers /
|
|
4622
|
+
* atomic transaction semantics are identical to `DDBModel.mutate`, and a read's
|
|
4623
|
+
* projection / relation resolution / pagination are identical to `Model.query` /
|
|
4624
|
+
* `list`. A body is **all-read or all-write**:
|
|
4625
|
+
*
|
|
4626
|
+
* ```ts
|
|
4627
|
+
* const createPost = DDBModel.prepare(($) => ({
|
|
4628
|
+
* post: { create: () => Post, key: { threadId: $.threadId, postId: $.postId },
|
|
4629
|
+
* input: { body: $.body } },
|
|
4630
|
+
* }));
|
|
4631
|
+
* await createPost.execute({ threadId, postId, body });
|
|
4632
|
+
*
|
|
4633
|
+
* const userById = DDBModel.prepare(($) => ({
|
|
4634
|
+
* user: { query: () => User, key: { userId: $.userId },
|
|
4635
|
+
* select: { userId: true, name: true } },
|
|
4636
|
+
* }));
|
|
4637
|
+
* await userById.execute({ userId });
|
|
4638
|
+
* ```
|
|
4639
|
+
*
|
|
4640
|
+
* The body may reference only `$` placeholders (per-call values) and module-static
|
|
4641
|
+
* model refs — a captured per-call runtime value is a **loud reject** (the runtime
|
|
4642
|
+
* half of the no-runtime-capture rule; the static build lint is phase 2 / #206). A
|
|
4643
|
+
* handle hoisted to module scope recompiles nothing; an inline `prepare(fn)` hits a
|
|
4644
|
+
* bounded structural cache after the first compile (placement-independent safety
|
|
4645
|
+
* net).
|
|
4646
|
+
*/
|
|
4647
|
+
static prepare(body) {
|
|
4648
|
+
return prepare(body);
|
|
4649
|
+
}
|
|
3924
4650
|
/**
|
|
3925
4651
|
* Parse a single CDC {@link ChangeEvent} into the `[oldRecord, newRecord]` tuple
|
|
3926
4652
|
* for THIS model (issue #153 — the pure `(event) => [old, new]` typed mapper).
|
|
@@ -4309,12 +5035,12 @@ function wholeKeysSentinel() {
|
|
|
4309
5035
|
function isContractKeyFieldRef(value) {
|
|
4310
5036
|
return typeof value === "object" && value !== null && value[KEY_FIELD_REF_BRAND] === true;
|
|
4311
5037
|
}
|
|
4312
|
-
var
|
|
5038
|
+
var PARAM_REF_BRAND2 = /* @__PURE__ */ Symbol("graphddb:contractParamRef");
|
|
4313
5039
|
function isContractParamRef(value) {
|
|
4314
|
-
return typeof value === "object" && value !== null && value[
|
|
5040
|
+
return typeof value === "object" && value !== null && value[PARAM_REF_BRAND2] === true;
|
|
4315
5041
|
}
|
|
4316
5042
|
function mintContractParamRef(field) {
|
|
4317
|
-
return { [
|
|
5043
|
+
return { [PARAM_REF_BRAND2]: true, field, token: `{${field}}` };
|
|
4318
5044
|
}
|
|
4319
5045
|
var FROM_REF_BRAND = /* @__PURE__ */ Symbol("graphddb:contractFromRef");
|
|
4320
5046
|
var COMPOSE_NODE_BRAND = /* @__PURE__ */ Symbol("graphddb:contractComposeNode");
|
|
@@ -4571,13 +5297,13 @@ function normalizeReturnSelection(select) {
|
|
|
4571
5297
|
}
|
|
4572
5298
|
return Object.keys(out).length > 0 ? out : void 0;
|
|
4573
5299
|
}
|
|
4574
|
-
var
|
|
5300
|
+
var READ_INTENT_KEYS2 = ["query", "list"];
|
|
4575
5301
|
var WRITE_INTENT_KEYS = ["create", "update", "remove"];
|
|
4576
5302
|
function isPublicComposeDescriptor(value) {
|
|
4577
5303
|
return typeof value === "object" && value !== null && isCommandPlan(value.command);
|
|
4578
5304
|
}
|
|
4579
5305
|
function isPublicReadDescriptor(value) {
|
|
4580
|
-
return typeof value === "object" && value !== null &&
|
|
5306
|
+
return typeof value === "object" && value !== null && READ_INTENT_KEYS2.some((k) => value[k] !== void 0);
|
|
4581
5307
|
}
|
|
4582
5308
|
function isPublicWriteDescriptor(value) {
|
|
4583
5309
|
return typeof value === "object" && value !== null && WRITE_INTENT_KEYS.some((k) => value[k] !== void 0);
|
|
@@ -6356,12 +7082,42 @@ function synthesizeMutationTransaction(contractName, methodName, ops, checks, ed
|
|
|
6356
7082
|
...maintainOutboxItems,
|
|
6357
7083
|
...checkItems
|
|
6358
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
|
+
];
|
|
6359
7096
|
if (items.length > MAX_TRANSACT_ITEMS) {
|
|
6360
7097
|
throw new Error(
|
|
6361
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.`
|
|
6362
7099
|
);
|
|
6363
7100
|
}
|
|
6364
|
-
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 };
|
|
6365
7121
|
}
|
|
6366
7122
|
function modeTargetFor(mode, op, commandSpec, contractName, methodName, opName, transactionSpecs) {
|
|
6367
7123
|
if (mode === "parallel") {
|
|
@@ -6484,7 +7240,7 @@ function serializeCommandContract(contractName, contract, commandSpecs, transact
|
|
|
6484
7240
|
idempotencyGuard,
|
|
6485
7241
|
maintainWrites,
|
|
6486
7242
|
maintainOutbox
|
|
6487
|
-
)
|
|
7243
|
+
).spec
|
|
6488
7244
|
) : (
|
|
6489
7245
|
// #90: writes only.
|
|
6490
7246
|
composeFragmentTransaction(contractName, methodName, ops)
|
|
@@ -6780,12 +7536,12 @@ function buildReadOperations(def) {
|
|
|
6780
7536
|
};
|
|
6781
7537
|
const ops = [root];
|
|
6782
7538
|
ops.push(...buildRelationOperations(metadata, select, "$"));
|
|
6783
|
-
return ops;
|
|
7539
|
+
return injectRefsSourceProjections(ops);
|
|
6784
7540
|
}
|
|
6785
7541
|
function projectionFields(select) {
|
|
6786
7542
|
const out = [];
|
|
6787
7543
|
for (const [field, value] of Object.entries(select)) {
|
|
6788
|
-
if (value === true) out.push(field);
|
|
7544
|
+
if (value === true || isInlineSnapshotSpec(value)) out.push(field);
|
|
6789
7545
|
}
|
|
6790
7546
|
return out.sort();
|
|
6791
7547
|
}
|
|
@@ -6801,9 +7557,16 @@ function buildRelationOperations(metadata, select, parentPath) {
|
|
|
6801
7557
|
const targetMeta = MetadataRegistry.get(targetClass);
|
|
6802
7558
|
const childPath = `${parentPath}.${rel.propertyName}`;
|
|
6803
7559
|
if (rel.type === "refs") {
|
|
6804
|
-
|
|
6805
|
-
|
|
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`)
|
|
6806
7567
|
);
|
|
7568
|
+
ops.push(...buildRelationOperations(targetMeta, childSelect, `${childPath}.items`));
|
|
7569
|
+
continue;
|
|
6807
7570
|
}
|
|
6808
7571
|
if (rel.type === "hasMany") {
|
|
6809
7572
|
const limit = spec.limit ?? rel.options?.limit?.default ?? 20;
|
|
@@ -6914,6 +7677,65 @@ function buildBatchGetOperation(rel, targetMeta, select, resultPath) {
|
|
|
6914
7677
|
sourceField
|
|
6915
7678
|
};
|
|
6916
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
|
+
}
|
|
6917
7739
|
function filterSpec(filter) {
|
|
6918
7740
|
return { declarative: filter };
|
|
6919
7741
|
}
|
|
@@ -7068,6 +7890,216 @@ function buildOperations(queries = {}, commands = {}, transactions = {}, contrac
|
|
|
7068
7890
|
};
|
|
7069
7891
|
}
|
|
7070
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
|
+
|
|
7071
8103
|
// src/spec/index.ts
|
|
7072
8104
|
function buildBridgeBundle(queries = {}, commands = {}, registry = MetadataRegistry, transactions = {}, contractInputs = {}) {
|
|
7073
8105
|
const bundle = {
|
|
@@ -7081,6 +8113,7 @@ function buildBridgeBundle(queries = {}, commands = {}, registry = MetadataRegis
|
|
|
7081
8113
|
export {
|
|
7082
8114
|
SPEC_VERSION,
|
|
7083
8115
|
MARKER_ROW_ENTITY,
|
|
8116
|
+
buildManifestEntity,
|
|
7084
8117
|
buildManifest,
|
|
7085
8118
|
buildProjection,
|
|
7086
8119
|
compileFilterExpression,
|
|
@@ -7118,6 +8151,14 @@ export {
|
|
|
7118
8151
|
MAX_TRANSACT_COMPOSE_ITEMS,
|
|
7119
8152
|
compileMutationPlan,
|
|
7120
8153
|
executeCommandMethod,
|
|
8154
|
+
LOGICAL_EFFECT_CATEGORIES,
|
|
8155
|
+
executeLogicalWriteOps,
|
|
8156
|
+
executeLogicalParallelWrites,
|
|
8157
|
+
isPreparedParamRef,
|
|
8158
|
+
PreparedWriteStatement,
|
|
8159
|
+
PreparedReadStatement,
|
|
8160
|
+
PREPARE_CACHE_MAX,
|
|
8161
|
+
prepare,
|
|
7121
8162
|
DDBModel,
|
|
7122
8163
|
isMutationInputRef,
|
|
7123
8164
|
isMutationFragment,
|
|
@@ -7158,5 +8199,11 @@ export {
|
|
|
7158
8199
|
assertContractBoundaries,
|
|
7159
8200
|
buildQuerySpec,
|
|
7160
8201
|
buildOperations,
|
|
8202
|
+
PREPARED_FORMAT_VERSION,
|
|
8203
|
+
canonicalJson,
|
|
8204
|
+
entityFingerprint,
|
|
8205
|
+
planFingerprint,
|
|
8206
|
+
compilePreparedPlan,
|
|
8207
|
+
buildPreparedPlanDocument,
|
|
7161
8208
|
buildBridgeBundle
|
|
7162
8209
|
};
|