graphddb 0.7.0 → 0.7.2

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.
@@ -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.0";
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,
@@ -2939,6 +2942,30 @@ function renderTemplateRecord(record, key, params, contextLabel) {
2939
2942
  }
2940
2943
  return out;
2941
2944
  }
2945
+ var EFFECT_FIELD_MAPS = /* @__PURE__ */ new WeakMap();
2946
+ function effectFieldMap(modelClass) {
2947
+ let map = EFFECT_FIELD_MAPS.get(modelClass);
2948
+ if (map === void 0) {
2949
+ map = new Map(MetadataRegistry.get(modelClass).fields.map((f) => [f.propertyName, f]));
2950
+ EFFECT_FIELD_MAPS.set(modelClass, map);
2951
+ }
2952
+ return map;
2953
+ }
2954
+ function serializeEffectParams(modelClasses, values) {
2955
+ const maps = modelClasses.map(effectFieldMap);
2956
+ const oldPrefix = `${OLD_VALUE_NAMESPACE}.`;
2957
+ const out = {};
2958
+ for (const [name, value] of Object.entries(values)) {
2959
+ const fieldName = name.startsWith(oldPrefix) ? name.slice(oldPrefix.length) : name;
2960
+ let fieldMeta;
2961
+ for (const map of maps) {
2962
+ fieldMeta = map.get(fieldName);
2963
+ if (fieldMeta !== void 0) break;
2964
+ }
2965
+ out[name] = fieldMeta !== void 0 ? serializeFieldValue(value, fieldMeta) : value instanceof Date ? value.toISOString() : value;
2966
+ }
2967
+ return out;
2968
+ }
2942
2969
  function renderEdgeWriteItems(edge, key, params, contextLabel, modelBySignature) {
2943
2970
  const adjacency = edge.entity.modelClass;
2944
2971
  return edge.items.map((item) => {
@@ -3284,11 +3311,14 @@ async function executeCommandMethod(contract, methodName, keyOrKeys, params = {}
3284
3311
  );
3285
3312
  if (hasDerivedEffects) {
3286
3313
  const modelBySignature = /* @__PURE__ */ new Map();
3314
+ const fragmentClasses = ops.map((opn) => modelClassOf(opn));
3315
+ const effectKey = serializeEffectParams(fragmentClasses, key);
3316
+ const effectParams = serializeEffectParams(fragmentClasses, params);
3287
3317
  const edgeItems = edgeWrites2.flatMap(
3288
3318
  (edge, i) => renderEdgeWriteItems(
3289
3319
  edge,
3290
- key,
3291
- params,
3320
+ effectKey,
3321
+ effectParams,
3292
3322
  `command method '${methodName}' (edge #${i})`,
3293
3323
  modelBySignature
3294
3324
  )
@@ -3296,41 +3326,41 @@ async function executeCommandMethod(contract, methodName, keyOrKeys, params = {}
3296
3326
  const updateItems = derivedUpdates.map(
3297
3327
  (update, i) => renderDerivedUpdate(
3298
3328
  update,
3299
- key,
3300
- params,
3329
+ effectKey,
3330
+ effectParams,
3301
3331
  `command method '${methodName}' (derive #${i})`,
3302
3332
  modelBySignature
3303
3333
  )
3304
3334
  );
3305
3335
  const guardItems = uniqueGuards.flatMap(
3306
- (guard, i) => renderUniqueGuardItems(guard, key, params, `command method '${methodName}' (unique #${i})`)
3336
+ (guard, i) => renderUniqueGuardItems(guard, effectKey, effectParams, `command method '${methodName}' (unique #${i})`)
3307
3337
  );
3308
3338
  const outboxItems = outboxEvents.flatMap(
3309
- (event, i) => renderOutboxEventItems(event, key, params, `command method '${methodName}' (emit #${i})`)
3339
+ (event, i) => renderOutboxEventItems(event, effectKey, effectParams, `command method '${methodName}' (emit #${i})`)
3310
3340
  );
3311
3341
  const idempotencyItems = idempotencyGuard !== void 0 ? renderIdempotencyGuardItems(
3312
3342
  idempotencyGuard,
3313
- key,
3314
- params,
3343
+ effectKey,
3344
+ effectParams,
3315
3345
  `command method '${methodName}' (idempotency)`
3316
3346
  ) : [];
3317
3347
  const maintainItems = maintainWrites.map(
3318
3348
  (m, i) => renderMaintainWriteItem(
3319
3349
  m,
3320
- key,
3321
- params,
3350
+ effectKey,
3351
+ effectParams,
3322
3352
  `command method '${methodName}' (maintain #${i})`,
3323
3353
  modelBySignature
3324
3354
  )
3325
3355
  );
3326
3356
  const maintainOutboxItems = maintainOutbox.flatMap(
3327
- (mo, i) => renderMaintainOutboxItems(mo, key, params, `command method '${methodName}' (maintain-outbox #${i})`)
3357
+ (mo, i) => renderMaintainOutboxItems(mo, effectKey, effectParams, `command method '${methodName}' (maintain-outbox #${i})`)
3328
3358
  );
3329
3359
  const checks = conditionChecks.map((check, i) => ({
3330
3360
  check: renderConditionCheck(
3331
3361
  check,
3332
- key,
3333
- params,
3362
+ effectKey,
3363
+ effectParams,
3334
3364
  `command method '${methodName}' (requires #${i})`
3335
3365
  )
3336
3366
  }));
@@ -3366,6 +3396,7 @@ function renderCompiledOp(op, ctx) {
3366
3396
  const baseOp = op.condition !== void 0 ? withCondition(fragment.op, op.condition) : fragment.op;
3367
3397
  const modelBySignature = /* @__PURE__ */ new Map();
3368
3398
  const write = renderWrite(baseOp, op.params, op.params, ctx);
3399
+ const effectParams = serializeEffectParams([modelClassOf(fragment.op)], op.params);
3369
3400
  const edgeItems = [];
3370
3401
  const updateItems = [];
3371
3402
  const guardItems = [];
@@ -3375,34 +3406,34 @@ function renderCompiledOp(op, ctx) {
3375
3406
  const maintainOutboxItems = [];
3376
3407
  const checkItems = [];
3377
3408
  for (const edge of fragment.edgeWrites ?? []) {
3378
- edgeItems.push(...renderEdgeWriteItems(edge, op.params, op.params, `${ctx} edge`, modelBySignature));
3409
+ edgeItems.push(...renderEdgeWriteItems(edge, effectParams, effectParams, `${ctx} edge`, modelBySignature));
3379
3410
  }
3380
3411
  for (const u of fragment.derivedUpdates ?? []) {
3381
- updateItems.push(renderDerivedUpdate(u, op.params, op.params, `${ctx} derive`, modelBySignature));
3412
+ updateItems.push(renderDerivedUpdate(u, effectParams, effectParams, `${ctx} derive`, modelBySignature));
3382
3413
  }
3383
3414
  for (const g of fragment.uniqueGuards ?? []) {
3384
- guardItems.push(...renderUniqueGuardItems(g, op.params, op.params, `${ctx} unique`));
3415
+ guardItems.push(...renderUniqueGuardItems(g, effectParams, effectParams, `${ctx} unique`));
3385
3416
  }
3386
3417
  for (const e of fragment.outboxEvents ?? []) {
3387
- outboxItems.push(...renderOutboxEventItems(e, op.params, op.params, `${ctx} emit`));
3418
+ outboxItems.push(...renderOutboxEventItems(e, effectParams, effectParams, `${ctx} emit`));
3388
3419
  }
3389
3420
  if (fragment.idempotencyGuard !== void 0) {
3390
3421
  idempotencyItems.push(
3391
- ...renderIdempotencyGuardItems(fragment.idempotencyGuard, op.params, op.params, `${ctx} idempotency`)
3422
+ ...renderIdempotencyGuardItems(fragment.idempotencyGuard, effectParams, effectParams, `${ctx} idempotency`)
3392
3423
  );
3393
3424
  }
3394
3425
  for (const m of fragment.maintainWrites ?? []) {
3395
3426
  maintainItems.push(
3396
- renderMaintainWriteItem(m, op.params, op.params, `${ctx} maintain`, modelBySignature)
3427
+ renderMaintainWriteItem(m, effectParams, effectParams, `${ctx} maintain`, modelBySignature)
3397
3428
  );
3398
3429
  }
3399
3430
  for (const mo of fragment.maintainOutbox ?? []) {
3400
3431
  maintainOutboxItems.push(
3401
- ...renderMaintainOutboxItems(mo, op.params, op.params, `${ctx} maintain-outbox`)
3432
+ ...renderMaintainOutboxItems(mo, effectParams, effectParams, `${ctx} maintain-outbox`)
3402
3433
  );
3403
3434
  }
3404
3435
  for (const c of fragment.conditionChecks ?? []) {
3405
- checkItems.push({ check: renderConditionCheck(c, op.params, op.params, `${ctx} requires`) });
3436
+ checkItems.push({ check: renderConditionCheck(c, effectParams, effectParams, `${ctx} requires`) });
3406
3437
  }
3407
3438
  const 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;
3408
3439
  return {
@@ -3532,8 +3563,23 @@ async function persistSingleWriteWithHooks(w, runtime, origins, transaction, ret
3532
3563
  });
3533
3564
  }
3534
3565
  async function runEnvelopeW1(op, runtime, transaction, writeCtxs) {
3535
- const kind = op.fragment.op.operation;
3536
- const keyFields = new Set(op.fragment.keyFields);
3566
+ const r = await runLogicalW1(
3567
+ {
3568
+ kind: op.fragment.op.operation,
3569
+ modelClass: op.fragment.op.entity.modelClass,
3570
+ keyFields: op.fragment.keyFields,
3571
+ params: op.params
3572
+ },
3573
+ runtime,
3574
+ transaction,
3575
+ writeCtxs
3576
+ );
3577
+ if (r.rewritten !== void 0) return { op, rewritten: r.rewritten };
3578
+ return { op: { ...op, params: r.params } };
3579
+ }
3580
+ async function runLogicalW1(op, runtime, transaction, writeCtxs) {
3581
+ const kind = op.kind;
3582
+ const keyFields = new Set(op.keyFields);
3537
3583
  const input = {};
3538
3584
  if (kind === "put") {
3539
3585
  input.item = { ...op.params };
@@ -3547,15 +3593,14 @@ async function runEnvelopeW1(op, runtime, transaction, writeCtxs) {
3547
3593
  input.key = key;
3548
3594
  input.changes = changes;
3549
3595
  }
3550
- const modelClass = op.fragment.op.entity.modelClass;
3551
- const ctx = runtime.writeCtx(kind, ctxModelFor(modelClass), input, transaction);
3596
+ const ctx = runtime.writeCtx(kind, ctxModelFor(op.modelClass), input, transaction);
3552
3597
  await runtime.runWriteBefore(ctx);
3553
3598
  writeCtxs.push(ctx);
3554
3599
  if (ctx.kind !== kind) {
3555
- return { op, rewritten: renderRewrittenWrite(modelClass, ctx) };
3600
+ return { params: op.params, rewritten: renderRewrittenWrite(op.modelClass, ctx) };
3556
3601
  }
3557
3602
  const merged = ctx.kind === "put" ? { ...ctx.input.item ?? {} } : { ...ctx.input.key ?? {}, ...ctx.input.changes ?? {} };
3558
- return { op: { ...op, params: merged } };
3603
+ return { params: merged };
3559
3604
  }
3560
3605
  function renderRewrittenWrite(modelClass, ctx) {
3561
3606
  const modelStatic = modelClass.asModel();
@@ -3646,6 +3691,199 @@ async function executeCompiledParallelWrites(ops, options) {
3646
3691
  ]);
3647
3692
  return results;
3648
3693
  }
3694
+ var LOGICAL_EFFECT_CATEGORIES = [
3695
+ "edge",
3696
+ "derive",
3697
+ "unique",
3698
+ "outbox",
3699
+ "idempotency",
3700
+ "maintain",
3701
+ "maintainOutbox"
3702
+ ];
3703
+ function renderLogicalWrite(op, params) {
3704
+ const base = {
3705
+ operation: op.kind,
3706
+ modelClass: op.modelClass,
3707
+ modelStatic: op.modelStatic,
3708
+ key: params,
3709
+ ...op.condition !== void 0 ? { condition: op.condition } : {}
3710
+ };
3711
+ if (op.kind === "put") return { ...base, item: params };
3712
+ if (op.kind === "update") {
3713
+ const keyFields = new Set(op.keyFields);
3714
+ const changes = {};
3715
+ for (const [k, v] of Object.entries(params)) {
3716
+ if (!keyFields.has(k)) changes[k] = v;
3717
+ }
3718
+ return { ...base, changes };
3719
+ }
3720
+ return base;
3721
+ }
3722
+ function renderLogicalOp(op, params) {
3723
+ const write = renderLogicalWrite(op, params);
3724
+ const rendered = op.renderEffects(params);
3725
+ const e = rendered.effects;
3726
+ const checkItems = rendered.checks.map((check) => ({ check }));
3727
+ const modelBySignature = new Map(rendered.modelBySignature);
3728
+ const pick = (c) => [...e[c] ?? []];
3729
+ const edgeItems = pick("edge");
3730
+ const updateItems = pick("derive");
3731
+ const guardItems = pick("unique");
3732
+ const outboxItems = pick("outbox");
3733
+ const idempotencyItems = pick("idempotency");
3734
+ const maintainItems = pick("maintain");
3735
+ const maintainOutboxItems = pick("maintainOutbox");
3736
+ return {
3737
+ write,
3738
+ edgeItems,
3739
+ updateItems,
3740
+ guardItems,
3741
+ outboxItems,
3742
+ idempotencyItems,
3743
+ maintainItems,
3744
+ maintainOutboxItems,
3745
+ checkItems,
3746
+ modelBySignature,
3747
+ 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
3748
+ };
3749
+ }
3750
+ function readBackForLogicalOp(op, params) {
3751
+ const sel = op.result?.select;
3752
+ if (sel === void 0 || Object.keys(sel).length === 0) return void 0;
3753
+ const key = {};
3754
+ for (const f of op.keyFields) {
3755
+ if (f in params) key[f] = params[f];
3756
+ }
3757
+ return executeQuery(op.modelClass, key, sel, {
3758
+ consistentRead: op.result?.options?.consistentRead ?? true,
3759
+ maxDepth: op.result?.options?.maxDepth
3760
+ });
3761
+ }
3762
+ async function executeLogicalWriteOps(ops, options) {
3763
+ const label = options.label;
3764
+ const writeMw = buildWriteRuntime(options.context);
3765
+ const writeCtxs = [];
3766
+ const txId = { id: /* @__PURE__ */ Symbol("graphddb.prepared-aot") };
3767
+ const effectiveParams = [];
3768
+ const rewrites = [];
3769
+ if (writeMw.active) {
3770
+ for (const op of ops) {
3771
+ const r = await runLogicalW1(op, writeMw, txId, writeCtxs);
3772
+ effectiveParams.push(r.params);
3773
+ rewrites.push(r.rewritten ?? null);
3774
+ }
3775
+ } else {
3776
+ for (const op of ops) {
3777
+ effectiveParams.push(op.params);
3778
+ rewrites.push(null);
3779
+ }
3780
+ }
3781
+ const renderedOps = ops.map(
3782
+ (op, i) => rewrites[i] !== null ? rewrittenCompiledOp(rewrites[i]) : renderLogicalOp(op, effectiveParams[i])
3783
+ );
3784
+ const rendered = renderedOps.map((r) => r.write);
3785
+ const modelBySignature = /* @__PURE__ */ new Map();
3786
+ const edgeItems = [];
3787
+ const updateItems = [];
3788
+ const guardItems = [];
3789
+ const outboxItems = [];
3790
+ const idempotencyItems = [];
3791
+ const maintainItems = [];
3792
+ const maintainOutboxItems = [];
3793
+ const checkItems = [];
3794
+ for (const r of renderedOps) {
3795
+ for (const [sig, name] of r.modelBySignature) modelBySignature.set(sig, name);
3796
+ edgeItems.push(...r.edgeItems);
3797
+ updateItems.push(...r.updateItems);
3798
+ guardItems.push(...r.guardItems);
3799
+ outboxItems.push(...r.outboxItems);
3800
+ idempotencyItems.push(...r.idempotencyItems);
3801
+ maintainItems.push(...r.maintainItems);
3802
+ maintainOutboxItems.push(...r.maintainOutboxItems);
3803
+ checkItems.push(...r.checkItems);
3804
+ }
3805
+ const hasDerivedEffects = renderedOps.some((r) => r.hasDerivedEffects);
3806
+ const origins = writeMw.active ? rendered.map((w) => ({ model: ctxModelFor(w.modelClass), kind: w.operation })) : [];
3807
+ if (rendered.length === 1 && !hasDerivedEffects) {
3808
+ if (writeMw.active) {
3809
+ await persistSingleWriteWithHooks(rendered[0], writeMw, origins, txId, options.retry);
3810
+ } else {
3811
+ await executeSingleWrite(rendered[0], options.retry);
3812
+ }
3813
+ } else {
3814
+ await executeReferentialTransaction(
3815
+ rendered,
3816
+ edgeItems,
3817
+ updateItems,
3818
+ guardItems,
3819
+ outboxItems,
3820
+ idempotencyItems,
3821
+ maintainItems,
3822
+ maintainOutboxItems,
3823
+ checkItems,
3824
+ label,
3825
+ modelBySignature,
3826
+ options.retry,
3827
+ writeMw.active ? { runtime: writeMw, origins, transaction: txId } : void 0
3828
+ );
3829
+ }
3830
+ if (writeMw.active) {
3831
+ for (let i = writeCtxs.length - 1; i >= 0; i--) {
3832
+ await writeMw.runWriteAfter(writeCtxs[i], {});
3833
+ }
3834
+ }
3835
+ const readBacks = await Promise.all(
3836
+ ops.map((op, i) => readBackForLogicalOp(op, effectiveParams[i]))
3837
+ );
3838
+ return { readBacks };
3839
+ }
3840
+ async function executeLogicalParallelWrites(ops, options) {
3841
+ const label = options.label;
3842
+ const results = new Array(ops.length);
3843
+ const renderedOps = ops.map((op) => renderLogicalOp(op, op.params));
3844
+ const coalescibleIdx = [];
3845
+ const individualIdx = [];
3846
+ const hooksActive = buildWriteRuntime(options.context).active;
3847
+ renderedOps.forEach((r, i) => {
3848
+ const w = r.write;
3849
+ if (!hooksActive && !r.hasDerivedEffects && w.condition === void 0 && (w.operation === "put" || w.operation === "delete")) {
3850
+ coalescibleIdx.push(i);
3851
+ } else {
3852
+ individualIdx.push(i);
3853
+ }
3854
+ });
3855
+ await Promise.all([
3856
+ (async () => {
3857
+ if (coalescibleIdx.length === 0) return;
3858
+ const writes = coalescibleIdx.map((i) => renderedOps[i].write);
3859
+ try {
3860
+ await executeParallelWrites(writes);
3861
+ await Promise.all(
3862
+ coalescibleIdx.map(async (i) => {
3863
+ const value = await readBackForLogicalOp(ops[i], ops[i].params);
3864
+ results[i] = { ok: true, value };
3865
+ })
3866
+ );
3867
+ } catch (e) {
3868
+ const error = e instanceof Error ? e : new Error(String(e));
3869
+ for (const i of coalescibleIdx) results[i] = { ok: false, error };
3870
+ }
3871
+ })(),
3872
+ ...individualIdx.map(async (i) => {
3873
+ try {
3874
+ const { readBacks } = await executeLogicalWriteOps([ops[i]], {
3875
+ label: `${label} (op #${i})`,
3876
+ ...options.retry !== void 0 ? { retry: options.retry } : {},
3877
+ ...options.context !== void 0 ? { context: options.context } : {}
3878
+ });
3879
+ results[i] = { ok: true, value: readBacks[0] };
3880
+ } catch (e) {
3881
+ results[i] = { ok: false, error: e instanceof Error ? e : new Error(String(e)) };
3882
+ }
3883
+ })
3884
+ ]);
3885
+ return results;
3886
+ }
3649
3887
  function withCondition(op, condition) {
3650
3888
  if (op.condition === void 0) return { ...op, condition };
3651
3889
  const base = op.condition;
@@ -4052,7 +4290,7 @@ function classify(routes) {
4052
4290
  }
4053
4291
  return kind;
4054
4292
  }
4055
- function compileWriteRoute(alias, route) {
4293
+ function analyzeWriteRoute(alias, route) {
4056
4294
  const present = INTENT_KEYS.filter((k) => route[k] !== void 0);
4057
4295
  if (present.length !== 1) {
4058
4296
  throw new Error(
@@ -4073,23 +4311,37 @@ function compileWriteRoute(alias, route) {
4073
4311
  );
4074
4312
  const conditionRec = route.condition !== void 0 ? recordSlots(route.condition, `${alias} condition`) : void 0;
4075
4313
  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
4314
  return {
4084
4315
  alias,
4085
4316
  intent,
4086
- fragment,
4317
+ model,
4318
+ keyFields: keyRec.fields,
4319
+ inputFields: inputRec.fields,
4087
4320
  keySlots: keyRec.slots,
4088
4321
  inputSlots: inputRec.slots,
4089
4322
  ...conditionRec !== void 0 ? { conditionSlots: conditionRec.slots } : {},
4090
4323
  ...route.result !== void 0 ? { result: route.result } : {}
4091
4324
  };
4092
4325
  }
4326
+ function compileWriteRoute(alias, route) {
4327
+ const analyzed = analyzeWriteRoute(alias, route);
4328
+ const fragment = compileWriteFragment({
4329
+ alias,
4330
+ intent: analyzed.intent,
4331
+ model: analyzed.model,
4332
+ keyFieldNames: analyzed.keyFields,
4333
+ inputFieldNames: analyzed.inputFields
4334
+ });
4335
+ return {
4336
+ alias,
4337
+ intent: analyzed.intent,
4338
+ fragment,
4339
+ keySlots: analyzed.keySlots,
4340
+ inputSlots: analyzed.inputSlots,
4341
+ ...analyzed.conditionSlots !== void 0 ? { conditionSlots: analyzed.conditionSlots } : {},
4342
+ ...analyzed.result !== void 0 ? { result: analyzed.result } : {}
4343
+ };
4344
+ }
4093
4345
  function compileReadRoute(alias, route) {
4094
4346
  const isQuery = route.query !== void 0;
4095
4347
  const isList = route.list !== void 0;
@@ -4256,6 +4508,25 @@ function stableJson(value) {
4256
4508
  return v;
4257
4509
  });
4258
4510
  }
4511
+ function evaluatePreparedRoutes(body) {
4512
+ if (typeof body !== "function") {
4513
+ throw new Error(
4514
+ `${PREPARE_LABEL}: expected a body function \`($) => ({ alias: { ... } })\`.`
4515
+ );
4516
+ }
4517
+ const $ = makePreparedInputProxy();
4518
+ const map = body($);
4519
+ if (map === null || typeof map !== "object") {
4520
+ throw new Error(
4521
+ `${PREPARE_LABEL}: the body must return a descriptor map \`{ alias: { ... } }\`.`
4522
+ );
4523
+ }
4524
+ const routes = Object.keys(map).map((alias) => ({
4525
+ alias,
4526
+ route: map[alias]
4527
+ }));
4528
+ return { kind: classify(routes), routes };
4529
+ }
4259
4530
  function prepare(body) {
4260
4531
  if (typeof body !== "function") {
4261
4532
  throw new Error(
@@ -6839,12 +7110,42 @@ function synthesizeMutationTransaction(contractName, methodName, ops, checks, ed
6839
7110
  ...maintainOutboxItems,
6840
7111
  ...checkItems
6841
7112
  ];
7113
+ const categories = [
7114
+ ...base.items.map(() => "base"),
7115
+ ...edgeItems.map(() => "edge"),
7116
+ ...updateItems.map(() => "derive"),
7117
+ ...guardItems.map(() => "unique"),
7118
+ ...outboxItems.map(() => "outbox"),
7119
+ ...idempotencyItems.map(() => "idempotency"),
7120
+ ...maintainItems.map(() => "maintain"),
7121
+ ...maintainOutboxItems.map(() => "maintainOutbox"),
7122
+ ...checkItems.map(() => "check")
7123
+ ];
6842
7124
  if (items.length > MAX_TRANSACT_ITEMS) {
6843
7125
  throw new Error(
6844
7126
  `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
7127
  );
6846
7128
  }
6847
- return { params: sortedParams(params), items, maxItems: items.length };
7129
+ return {
7130
+ spec: { params: sortedParams(params), items, maxItems: items.length },
7131
+ categories
7132
+ };
7133
+ }
7134
+ function serializePreparedWriteFragment(label, fragment) {
7135
+ const { spec, categories } = synthesizeMutationTransaction(
7136
+ label,
7137
+ "plan",
7138
+ [fragment.op],
7139
+ fragment.conditionChecks ?? [],
7140
+ fragment.edgeWrites ?? [],
7141
+ fragment.derivedUpdates ?? [],
7142
+ fragment.uniqueGuards ?? [],
7143
+ fragment.outboxEvents ?? [],
7144
+ fragment.idempotencyGuard,
7145
+ fragment.maintainWrites ?? [],
7146
+ fragment.maintainOutbox ?? []
7147
+ );
7148
+ return { transaction: spec, categories };
6848
7149
  }
6849
7150
  function modeTargetFor(mode, op, commandSpec, contractName, methodName, opName, transactionSpecs) {
6850
7151
  if (mode === "parallel") {
@@ -6967,7 +7268,7 @@ function serializeCommandContract(contractName, contract, commandSpecs, transact
6967
7268
  idempotencyGuard,
6968
7269
  maintainWrites,
6969
7270
  maintainOutbox
6970
- )
7271
+ ).spec
6971
7272
  ) : (
6972
7273
  // #90: writes only.
6973
7274
  composeFragmentTransaction(contractName, methodName, ops)
@@ -7263,12 +7564,12 @@ function buildReadOperations(def) {
7263
7564
  };
7264
7565
  const ops = [root];
7265
7566
  ops.push(...buildRelationOperations(metadata, select, "$"));
7266
- return ops;
7567
+ return injectRefsSourceProjections(ops);
7267
7568
  }
7268
7569
  function projectionFields(select) {
7269
7570
  const out = [];
7270
7571
  for (const [field, value] of Object.entries(select)) {
7271
- if (value === true) out.push(field);
7572
+ if (value === true || isInlineSnapshotSpec(value)) out.push(field);
7272
7573
  }
7273
7574
  return out.sort();
7274
7575
  }
@@ -7284,9 +7585,16 @@ function buildRelationOperations(metadata, select, parentPath) {
7284
7585
  const targetMeta = MetadataRegistry.get(targetClass);
7285
7586
  const childPath = `${parentPath}.${rel.propertyName}`;
7286
7587
  if (rel.type === "refs") {
7287
- throw new Error(
7288
- `Relation '${rel.propertyName}' is a 'refs' relation (issue #197): its read fans an inline parent-row id-list ('${rel.refs?.from}[].${rel.refs?.key}') out into one BatchGet. The static operations spec / Python codegen cannot represent a parent-list key fan-out (operation key conditions bind a single scalar '{result.*}'), so a 'refs' relation is resolvable ONLY through the TS in-process runtime (Model.query). Remove '${rel.propertyName}' from selects that are compiled to operations.json / generated Python, or resolve it with a separate BatchGet in the generated-code consumer.`
7588
+ if (spec.filter !== void 0) {
7589
+ throw new Error(
7590
+ `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.`
7591
+ );
7592
+ }
7593
+ ops.push(
7594
+ buildRefsOperation(rel, targetMeta, childSelect, `${childPath}.items`)
7289
7595
  );
7596
+ ops.push(...buildRelationOperations(targetMeta, childSelect, `${childPath}.items`));
7597
+ continue;
7290
7598
  }
7291
7599
  if (rel.type === "hasMany") {
7292
7600
  const limit = spec.limit ?? rel.options?.limit?.default ?? 20;
@@ -7397,6 +7705,65 @@ function buildBatchGetOperation(rel, targetMeta, select, resultPath) {
7397
7705
  sourceField
7398
7706
  };
7399
7707
  }
7708
+ function buildRefsOperation(rel, targetMeta, select, resultPath) {
7709
+ const refs = rel.refs;
7710
+ if (!refs) {
7711
+ throw new Error(
7712
+ `Relation '${rel.propertyName}' is type 'refs' but carries no refs binding.`
7713
+ );
7714
+ }
7715
+ const { targetField, sourceField } = singleSourceField(rel);
7716
+ const resolved = resolveKey([targetField], targetMeta);
7717
+ if (resolved.type !== "pk" || resolved.partial) {
7718
+ throw new Error(
7719
+ `Relation '${rel.propertyName}' (refs) must resolve to a full primary key (BatchGetItem); got ${resolved.type}${resolved.partial ? " (partial)" : ""}.`
7720
+ );
7721
+ }
7722
+ if (!targetMeta.primaryKey) throw new Error("Primary key not defined");
7723
+ const nameOf = (f) => f === targetField ? sourceField : f;
7724
+ const { pk, sk } = evaluateKey(
7725
+ targetMeta.primaryKey.segmented,
7726
+ "result",
7727
+ /* @__PURE__ */ new Set([targetField]),
7728
+ nameOf
7729
+ );
7730
+ const keyCondition = { PK: pk };
7731
+ if (sk !== void 0) keyCondition.SK = sk;
7732
+ return {
7733
+ type: "BatchGetItem",
7734
+ tableName: TableMapping.resolve(targetMeta.tableName),
7735
+ keyCondition,
7736
+ projection: projectionFields(select),
7737
+ resultPath,
7738
+ sourceField,
7739
+ sourceList: { from: refs.from, key: refs.key }
7740
+ };
7741
+ }
7742
+ function injectRefsSourceProjections(ops) {
7743
+ for (let i = 0; i < ops.length; i++) {
7744
+ const op = ops[i];
7745
+ if (op.sourceList === void 0) continue;
7746
+ const tokens = op.resultPath.split(".");
7747
+ const parentPath = tokens.slice(0, -2).join(".") || "$";
7748
+ const parentIndex = ops.findIndex((p) => p.resultPath === parentPath);
7749
+ if (parentIndex === -1) {
7750
+ throw new Error(
7751
+ `refs fan-out at '${op.resultPath}': no parent operation at '${parentPath}'.`
7752
+ );
7753
+ }
7754
+ const parent = ops[parentIndex];
7755
+ const from2 = op.sourceList.from;
7756
+ if (parent.projection.includes(from2)) {
7757
+ continue;
7758
+ }
7759
+ ops[parentIndex] = {
7760
+ ...parent,
7761
+ projection: [...parent.projection, from2].sort()
7762
+ };
7763
+ ops[i] = { ...op, sourceList: { ...op.sourceList, implicit: true } };
7764
+ }
7765
+ return ops;
7766
+ }
7400
7767
  function filterSpec(filter) {
7401
7768
  return { declarative: filter };
7402
7769
  }
@@ -7551,6 +7918,216 @@ function buildOperations(queries = {}, commands = {}, transactions = {}, contrac
7551
7918
  };
7552
7919
  }
7553
7920
 
7921
+ // src/spec/prepared.ts
7922
+ import { createHash } from "crypto";
7923
+ var PREPARED_FORMAT_VERSION = "1";
7924
+ function canonicalJson(value) {
7925
+ return JSON.stringify(value, (_k, v) => {
7926
+ if (v !== null && typeof v === "object" && !Array.isArray(v)) {
7927
+ const sorted = {};
7928
+ for (const key of Object.keys(v).sort()) {
7929
+ sorted[key] = v[key];
7930
+ }
7931
+ return sorted;
7932
+ }
7933
+ return v;
7934
+ });
7935
+ }
7936
+ function entityFingerprint(metadata) {
7937
+ return createHash("sha256").update(canonicalJson(buildManifestEntity(metadata))).digest("hex");
7938
+ }
7939
+ function planFingerprint(plan2) {
7940
+ return createHash("sha256").update(canonicalJson(plan2)).digest("hex");
7941
+ }
7942
+ function bindOfSlot(slot, where) {
7943
+ if (slot.kind === "param") return { param: slot.name };
7944
+ const v = slot.value;
7945
+ if (v === null || typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
7946
+ return { literal: v };
7947
+ }
7948
+ if (v instanceof Date) return { literalDate: v.toISOString() };
7949
+ if (typeof v === "bigint") return { literalBigInt: v.toString() };
7950
+ throw new Error(
7951
+ `prepared AOT: ${where} carries a non-serializable literal (${typeof v}).`
7952
+ );
7953
+ }
7954
+ function bindMapOf(slots, where) {
7955
+ const out = {};
7956
+ for (const [field, slot] of Object.entries(slots)) {
7957
+ out[field] = bindOfSlot(slot, `${where} field '${field}'`);
7958
+ }
7959
+ return out;
7960
+ }
7961
+ function paramKindForField(metadata, field) {
7962
+ const f = metadata.fields.find((x) => x.propertyName === field);
7963
+ return f?.dynamoType === "N" ? "number" : "string";
7964
+ }
7965
+ function addParam(params, name, kind) {
7966
+ if (!(name in params)) params[name] = { type: kind, required: true };
7967
+ }
7968
+ function readRouteQuerySpec(route, metadata) {
7969
+ const params = {};
7970
+ const key = {};
7971
+ for (const field of Object.keys(route.keySlots)) {
7972
+ const kind = paramKindForField(metadata, field);
7973
+ params[field] = { kind, required: true };
7974
+ key[field] = kind === "number" ? param.number() : param.string();
7975
+ }
7976
+ const def = {
7977
+ __isOperationDefinition: true,
7978
+ entity: { name: route.modelClass.name, modelClass: route.modelClass },
7979
+ operation: route.kind === "query" ? "query" : "list",
7980
+ key,
7981
+ select: route.select,
7982
+ params
7983
+ };
7984
+ return buildQuerySpec(def);
7985
+ }
7986
+ function compileReadPlan(routes) {
7987
+ const specs = [];
7988
+ const params = {};
7989
+ const entities = {};
7990
+ for (const { alias, route } of routes) {
7991
+ const compiled = compileReadRoute(alias, route);
7992
+ const metadata = MetadataRegistry.get(compiled.modelClass);
7993
+ const entityName = compiled.modelClass.name;
7994
+ entities[entityName] = entityFingerprint(metadata);
7995
+ for (const [field, slot] of Object.entries(compiled.keySlots)) {
7996
+ if (slot.kind === "param") {
7997
+ addParam(params, slot.name, paramKindForField(metadata, field));
7998
+ }
7999
+ }
8000
+ const dynamicParam = (slot, kind) => {
8001
+ if (slot?.kind === "param") addParam(params, slot.name, kind);
8002
+ };
8003
+ dynamicParam(compiled.limitSlot, "number");
8004
+ dynamicParam(compiled.afterSlot, "string");
8005
+ dynamicParam(compiled.consistentReadSlot, "string");
8006
+ specs.push({
8007
+ alias,
8008
+ op: compiled.kind,
8009
+ entity: entityName,
8010
+ select: compiled.select,
8011
+ key: bindMapOf(compiled.keySlots, `${alias} key`),
8012
+ ...compiled.maxDepth !== void 0 ? { maxDepth: compiled.maxDepth } : {},
8013
+ ...compiled.order !== void 0 ? { order: compiled.order } : {},
8014
+ ...compiled.filter !== void 0 ? { filter: compiled.filter } : {},
8015
+ ...compiled.consistentReadSlot !== void 0 ? { consistentRead: bindOfSlot(compiled.consistentReadSlot, `${alias} consistentRead`) } : {},
8016
+ ...compiled.limitSlot !== void 0 ? { limit: bindOfSlot(compiled.limitSlot, `${alias} limit`) } : {},
8017
+ ...compiled.afterSlot !== void 0 ? { after: bindOfSlot(compiled.afterSlot, `${alias} after`) } : {},
8018
+ query: readRouteQuerySpec(compiled, metadata)
8019
+ });
8020
+ }
8021
+ return { kind: "read", params, entities, reads: specs };
8022
+ }
8023
+ function compileWritePlan(routes, planLabel) {
8024
+ const specs = [];
8025
+ const params = {};
8026
+ const entities = {};
8027
+ for (const { alias, route } of routes) {
8028
+ const analyzed = analyzeWriteRoute(alias, route);
8029
+ if (analyzed.intent === "create" && analyzed.conditionSlots !== void 0) {
8030
+ throw new Error(
8031
+ `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.`
8032
+ );
8033
+ }
8034
+ const modelClass = resolveModelClass(analyzed.model);
8035
+ const metadata = MetadataRegistry.get(modelClass);
8036
+ const entityName = modelClass.name;
8037
+ entities[entityName] = entityFingerprint(metadata);
8038
+ const fragment = compileWriteFragment({
8039
+ alias,
8040
+ intent: analyzed.intent,
8041
+ model: analyzed.model,
8042
+ keyFieldNames: analyzed.keyFields,
8043
+ inputFieldNames: analyzed.inputFields
8044
+ });
8045
+ const { transaction, categories } = serializePreparedWriteFragment(
8046
+ `${planLabel}.${alias}`,
8047
+ fragment
8048
+ );
8049
+ for (const item of transaction.items) {
8050
+ if (item.literalKey === true || item.entity === MARKER_ROW_ENTITY) continue;
8051
+ if (item.entity in entities) continue;
8052
+ const cls = resolveRegisteredModel(item.entity, `${planLabel}.${alias}`);
8053
+ entities[item.entity] = entityFingerprint(MetadataRegistry.get(cls));
8054
+ }
8055
+ for (const [field, slot] of Object.entries({
8056
+ ...analyzed.inputSlots,
8057
+ ...analyzed.keySlots,
8058
+ ...analyzed.conditionSlots ?? {}
8059
+ })) {
8060
+ if (slot.kind === "param") {
8061
+ addParam(params, slot.name, paramKindForField(metadata, field));
8062
+ }
8063
+ }
8064
+ let readback;
8065
+ const resultSelect = analyzed.result?.select;
8066
+ if (resultSelect !== void 0 && Object.keys(resultSelect).length > 0) {
8067
+ const kf = {};
8068
+ const kp = {};
8069
+ for (const field of fragment.keyFields) {
8070
+ const kind = paramKindForField(metadata, field);
8071
+ kp[field] = { kind, required: true };
8072
+ kf[field] = kind === "number" ? param.number() : param.string();
8073
+ }
8074
+ readback = buildQuerySpec({
8075
+ __isOperationDefinition: true,
8076
+ entity: { name: entityName, modelClass },
8077
+ operation: "query",
8078
+ key: kf,
8079
+ select: resultSelect,
8080
+ params: kp
8081
+ });
8082
+ }
8083
+ specs.push({
8084
+ alias,
8085
+ entity: entityName,
8086
+ intent: analyzed.intent,
8087
+ keyFields: fragment.keyFields,
8088
+ key: bindMapOf(analyzed.keySlots, `${alias} key`),
8089
+ input: bindMapOf(analyzed.inputSlots, `${alias} input`),
8090
+ ...analyzed.conditionSlots !== void 0 ? { condition: bindMapOf(analyzed.conditionSlots, `${alias} condition`) } : {},
8091
+ ...analyzed.result !== void 0 ? { result: analyzed.result } : {},
8092
+ ...readback !== void 0 ? { readback } : {},
8093
+ transaction,
8094
+ categories
8095
+ });
8096
+ }
8097
+ return { kind: "write", params, entities, writes: specs };
8098
+ }
8099
+ function resolveRegisteredModel(name, label) {
8100
+ for (const cls of MetadataRegistry.getAll().keys()) {
8101
+ if (cls.name === name) {
8102
+ return cls;
8103
+ }
8104
+ }
8105
+ throw new Error(
8106
+ `prepared AOT (${label}): a derived effect targets entity '${name}', which is not a registered model.`
8107
+ );
8108
+ }
8109
+ function compilePreparedPlan(body, planLabel = "prepared") {
8110
+ const { kind, routes } = evaluatePreparedRoutes(body);
8111
+ if (kind === "read") {
8112
+ return compileReadPlan(
8113
+ routes
8114
+ );
8115
+ }
8116
+ return compileWritePlan(
8117
+ routes,
8118
+ planLabel
8119
+ );
8120
+ }
8121
+ function buildPreparedPlanDocument(plans) {
8122
+ const sorted = {};
8123
+ for (const id of Object.keys(plans).sort()) sorted[id] = plans[id];
8124
+ return {
8125
+ formatVersion: PREPARED_FORMAT_VERSION,
8126
+ specVersion: SPEC_VERSION,
8127
+ plans: sorted
8128
+ };
8129
+ }
8130
+
7554
8131
  // src/spec/index.ts
7555
8132
  function buildBridgeBundle(queries = {}, commands = {}, registry = MetadataRegistry, transactions = {}, contractInputs = {}) {
7556
8133
  const bundle = {
@@ -7564,6 +8141,7 @@ function buildBridgeBundle(queries = {}, commands = {}, registry = MetadataRegis
7564
8141
  export {
7565
8142
  SPEC_VERSION,
7566
8143
  MARKER_ROW_ENTITY,
8144
+ buildManifestEntity,
7567
8145
  buildManifest,
7568
8146
  buildProjection,
7569
8147
  compileFilterExpression,
@@ -7600,7 +8178,11 @@ export {
7600
8178
  compileSingleFragmentPlan,
7601
8179
  MAX_TRANSACT_COMPOSE_ITEMS,
7602
8180
  compileMutationPlan,
8181
+ serializeEffectParams,
7603
8182
  executeCommandMethod,
8183
+ LOGICAL_EFFECT_CATEGORIES,
8184
+ executeLogicalWriteOps,
8185
+ executeLogicalParallelWrites,
7604
8186
  isPreparedParamRef,
7605
8187
  PreparedWriteStatement,
7606
8188
  PreparedReadStatement,
@@ -7646,5 +8228,11 @@ export {
7646
8228
  assertContractBoundaries,
7647
8229
  buildQuerySpec,
7648
8230
  buildOperations,
8231
+ PREPARED_FORMAT_VERSION,
8232
+ canonicalJson,
8233
+ entityFingerprint,
8234
+ planFingerprint,
8235
+ compilePreparedPlan,
8236
+ buildPreparedPlanDocument,
7649
8237
  buildBridgeBundle
7650
8238
  };