graphddb 0.7.10 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +6 -6
  2. package/dist/cdc/index.d.ts +389 -5
  3. package/dist/cdc/index.js +4 -4
  4. package/dist/{chunk-AD6ZQTTE.js → chunk-GS4C5VGO.js} +2 -6
  5. package/dist/{chunk-DFUKGU2Q.js → chunk-HNY2EJPV.js} +216 -229
  6. package/dist/{chunk-EOJDN3SA.js → chunk-I4LEJ4TF.js} +3744 -6144
  7. package/dist/{chunk-3ZU2VW3L.js → chunk-L2NEDS7U.js} +582 -781
  8. package/dist/chunk-L4QRCHRQ.js +278 -0
  9. package/dist/chunk-LGHSZIEE.js +187 -0
  10. package/dist/chunk-N4NWYNGZ.js +1987 -0
  11. package/dist/{chunk-PDUVTYC5.js → chunk-XTWXMOHD.js} +0 -1
  12. package/dist/cli.js +63 -254
  13. package/dist/index.d.ts +23 -1550
  14. package/dist/index.js +94 -1850
  15. package/dist/internal/index.d.ts +84 -0
  16. package/dist/internal/index.js +701 -0
  17. package/dist/{maintenance-view-adapter-BAZ9uBGe.d.ts → key-DR7_lpyk.d.ts} +504 -1910
  18. package/dist/linter/index.d.ts +39 -7
  19. package/dist/linter/index.js +22 -4
  20. package/dist/{registry-LWE54Sdc.d.ts → linter-C-vypgut.d.ts} +22 -22
  21. package/dist/prepared-artifact-BpPgkXEo.d.ts +281 -0
  22. package/dist/spec/index.d.ts +506 -5
  23. package/dist/spec/index.js +22 -18
  24. package/dist/testing/index.d.ts +2 -3
  25. package/dist/testing/index.js +4 -4
  26. package/dist/transform/index.d.ts +1 -1
  27. package/dist/transform/index.js +4 -4
  28. package/dist/{types-BQLzTEqh.d.ts → types-2PMXEn5x.d.ts} +8 -10
  29. package/dist/types-BXLzIcQD.d.ts +450 -0
  30. package/docs/cdc-projection.md +5 -5
  31. package/docs/class-hydration.md +1 -1
  32. package/docs/cqrs-contract.md +28 -20
  33. package/docs/design-patterns.md +5 -5
  34. package/docs/docs-generation.md +6 -6
  35. package/docs/middleware.md +15 -15
  36. package/docs/mutation-command-derivation.md +52 -42
  37. package/docs/prepared-statements.md +14 -14
  38. package/docs/python-bridge.md +96 -65
  39. package/docs/spec.md +153 -124
  40. package/docs/testing.md +9 -8
  41. package/package.json +14 -4
  42. package/dist/chunk-3UD3XIF2.js +0 -860
  43. package/dist/chunk-MMVHOUM4.js +0 -24
  44. package/dist/from-change-Ty95KA8C.d.ts +0 -327
  45. package/dist/index-Dc7d8mWI.d.ts +0 -1089
  46. package/dist/relation-depth-BRS513Tq.d.ts +0 -36
package/dist/index.d.ts CHANGED
@@ -1,14 +1,8 @@
1
- export { a as LintResult, L as LintRule, b as Linter, M as MetadataRegistry } from './registry-LWE54Sdc.js';
2
- import { ab as SelectableOf, ac as PrimaryKeyOf, ad as RequestContext, ae as Middleware, af as ReadRequestKind, ag as CtxModel, ah as ReadParams, ai as ReadRequestCtx, D as DynamoDBOperation, aj as Item, n as Executor, ak as RetryPolicy, al as RetryOverride, am as KeyDefinition, an as GsiDefinition, ao as ModelKind, ap as FieldOptions, aq as DynamoType, ar as ProjectionTransform, as as MaintainEvent, at as MembershipPredicate, au as MaintainConsistency, av as MaintainUpdateMode, aw as MembershipPredicateOp, ax as RelationOptions, ay as AggregateOptions, az as AggregateValue, aA as SelectBuilderSpec, aB as RawCondition, m as EntityMetadata, aC as ExecutionPlan, aD as FieldMetadata, aE as ResolvedKey, o as ReadExecOptions, p as ExecutorResult, q as BatchGetExecInput, P as PutInput, W as WriteExecOptions, r as WriteResult, s as UpdateInput, t as DeleteInput, u as BatchWriteExecItem, v as BatchExecOptions, T as TransactWriteExecItem, M as ModelStatic, w as DDBModel, aF as Slot, aG as PreparedWriteExecOptions, aH as CommandReturn, aI as ParallelOpResult, K as PreparedBody, aJ as PreparedStatement, aK as RelationMetadata, aL as MaintainEffect, V as ViewDefinition, A as ParamDescriptor, I as DefinitionMap, aM as OperationDefinition, aN as WriteDefinitionOptions, aO as PartialQueryKeyOf, aP as StrictSelectSpec, aQ as ReadDefinitionOptions, aR as EntityInput, aS as UniqueQueryKeyOf } from './maintenance-view-adapter-BAZ9uBGe.js';
3
- export { aT as AggregateMetadata, J as AnyOperationDefinition, aU as BatchDeleteRequest, aV as BatchGetOptions, aW as BatchGetRequest, aX as BatchGetResult, aY as BatchPutRequest, B as BatchResult, aZ as BatchWriteRequest, a_ as CONTRACT_RANGE_FANOUT_CONCURRENCY, C as CdcEmulatorOptions, a as CdcMode, a$ as CdcModelRegistry, b0 as CdcSubscribeHandlers, b1 as Change, b as ChangeBatch, c as ChangeEvent, d as ChangeEventName, e as ChangeHandler, f as ClockMode, b2 as CollectionEffect, b3 as CollectionOptions, b4 as Column, b5 as ColumnMap, b6 as CommandInputShape, b7 as CommandMethod, z as CommandMethodSpec, y as CommandModelContract, b8 as CommandPlan, b9 as CommandResultKind, ba as CommandSelectShape, L as CompiledFragment, g as ConcurrentRecomputeRef, bb as CondSlot, bc as ConditionCheckInput, H as ConditionInput, bd as Connection, be as ContractCallSignature, bf as ContractCommandParams, bg as ContractComposeNode, bh as ContractFromRef, bi as ContractItem, bj as ContractKeyFieldRef, bk as ContractKeyInput, bl as ContractKeyRef, bm as ContractMethodOp, bn as ContractParamRef, bo as ContractQueryParams, bp as CounterAggregate, bq as CounterEffect, br as CtxBase, bs as DEFAULT_MAX_ATTEMPTS, bt as DEFAULT_RETRY_POLICY, bu as DeleteOptions, bv as DeriveEffect, X as DerivedEdgeWrite, a1 as DerivedUpdate, bw as DescriptorBinding, bx as ENTITY_WRITES_MARKER, by as EdgeEffect, bz as EffectPath, bA as EmbeddedMetadata, bB as EmitEffect, G as EntityRef, bC as EntityWritesDefinition, bD as EntityWritesShape, E as EventLog, bE as ExecutableCommandContract, bF as ExecutableQueryContract, F as FaultSpec, bG as FilterInput, bH as FragmentCondition, bI as FragmentConditionOperatorObject, bJ as FragmentInput, bK as GsiDefinitionMarker, bL as GsiOptions, bM as IdempotencyEffect, bN as InProcessWriteDescriptor, bO as InlineSnapshotSpec, bP as InputArity, bQ as KeyDefinitionMarker, bR as KeySegment, bS as KeySlot, bT as KeyStructure, bU as KeyedResult, bV as LIFECYCLE_CONTRACT_MARKER, bW as LifecycleContract, bX as LifecycleEffects, bY as MaintainItem, bZ as MaintainTrigger, b_ as MaintenanceGraph, b$ as MembershipEffect, c0 as ModelRef, c1 as MutateMode, c2 as MutateOptions, c3 as MutateParallelResult, c4 as MutateTransactionResult, c5 as MutationBody, c6 as MutationDescriptorMap, c7 as MutationFragment, c8 as MutationInputProxy, c9 as MutationInputRef, ca as MutationIntent, cb as OperationKind, cc as PREPARE_CACHE_MAX, cd as ParamStructure, ce as PersistCtx, cf as PersistOrigin, cg as PlannedCommandMethod, ch as PreparedInputProxy, ci as PreparedParamRef, cj as PreparedReadExecOptions, ck as PreparedReadRoute, cl as PreparedReadStatement, cm as PreparedWriteRoute, cn as PreparedWriteStatement, co as ProjectionMap, cp as ProjectionTransformOp, cq as PutOptions, cr as QueryEnvelopeResult, cs as QueryKeyOf, ct as QueryMethod, x as QueryMethodSpec, Q as QueryModelContract, cu as QueryResult, cv as ReadEnvelope, cw as ReadOpCtx, cx as ReadOpKind, cy as ReadRouteDescriptor, cz as ReadRouteOptions, cA as ReadRouteResult, cB as RecordedCompose, cC as RelationBuilder, cD as RelationConsistency, cE as RelationLimitOptions, cF as RelationPattern, cG as RelationProjection, cH as RelationReadOptions, cI as RelationSelect, cJ as RelationSpec, cK as RelationUpdateMode, cL as RelationWriteOptions, R as ReplayOptions, cM as RequiresEffect, cN as Resolution, cO as RetryInfo, cP as RetryOperationKind, cQ as SegmentSpec, cR as SegmentedKey, cS as SelectBuilder, cT as SelectOf, S as ShardId, cU as SnapshotEffect, h as StartingPosition, i as StreamViewType, j as SubscribeHandler, k as SubscribeHandlers, cV as TransactionContext, cW as UniqueEffect, cX as Updatable, cY as UpdateOptions, cZ as ViewSourceSlice, c_ as WriteCtx, c$ as WriteDescriptor, d0 as WriteEnvelope, d1 as WriteInput, d2 as WriteKind, d3 as WriteLifecyclePhase, d4 as WriteMiddleware, d5 as WriteRecorder, d6 as WriteResultProjection, d7 as attachModelClass, d8 as buildDeleteInput, d9 as buildMaintenanceGraph, da as buildPutInput, l as buildSubscribeHandler, db as buildUpdateInput, dc as collectViewDefinitions, a5 as compileFragment, a6 as compileMutationPlan, a7 as compileSingleFragmentPlan, dd as cond, de as contractOfMethodSpec, df as definePlan, dg as entityWrites, dh as executeBatchGet, di as executeBatchWrite, dj as executeCommandMethod, dk as executeDelete, dl as executeKeyedBatchGet, dm as executePut, dn as executeQueryMethod, dp as executeRangeFanout, dq as executeTransaction, dr as executeUpdate, ds as from, dt as getEntityWrites, du as gsi, dv as identity, dw as isColumn, dx as isCommandModelContract, dy as isCommandPlan, dz as isContractComposeNode, dA as isContractFromRef, dB as isContractKeyFieldRef, dC as isContractKeyRef, dD as isContractParamRef, dE as isEntityWritesDefinition, dF as isKeySegment, dG as isLifecycleContract, dH as isMaintainTrigger, dI as isMutationFragment, dJ as isMutationInputRef, dK as isPlannedCommandMethod, dL as isPreparedParamRef, dM as isQueryModelContract, dN as isRetryableError, dO as isRetryableTransactionCancellation, dP as k, dQ as key, dR as lifecyclePhaseForIntent, dS as maintainTrigger, dT as mintContractKeyFieldRef, dU as mintContractParamRef, dV as mutation, dW as prepare, dX as preview, dY as publicCommandModel, dZ as publicQueryModel, d_ as query, a9 as resolveLifecycle, d$ as wholeKeysSentinel } from './maintenance-view-adapter-BAZ9uBGe.js';
4
- import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
5
- import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
6
- import { q as ExecutionPlanSpec, T as TransactionSpec, M as Manifest, Y as TransactionItemSpec, c as Param } from './types-BQLzTEqh.js';
7
- export { B as BridgeBundle, f as CommandContractMethodSpec, g as CommandResolutionTarget, a as CommandSpec, h as ComposeSpec, d as ConditionSpec, b as ContextSpec, j as ContractCardinality, k as ContractCommandResult, l as ContractInputArity, m as ContractKeySpec, n as ContractKind, o as ContractResolution, C as ContractSpec, p as EXPR_VERSION, u as ExpressionNode, x as ExpressionOperator, E as ExpressionSpec, F as FilterSpec, a1 as LiteralParam, D as ManifestEntity, G as ManifestField, H as ManifestFieldType, I as ManifestGsi, J as ManifestKey, K as ManifestRelation, L as ManifestTable, a2 as NumberParam, N as OperationSpec, O as OperationsDocument, a0 as ParamKind, P as ParamSpec, R as QueryContractMethodSpec, Q as QuerySpec, U as RangeConditionSpec, V as ReadOperationType, S as SPEC_VERSION, W as SPEC_VERSION_SCP, e as SpecVersion, a3 as StringParam, Z as TransactionItemType, _ as WhenSpec, $ as WriteOperationType, a4 as isParam, a5 as param } from './types-BQLzTEqh.js';
8
- export { C as CdcEmulator, M as MAINT_OUTBOX_PK_PREFIX, a as MaintenanceDrain, b as MaintenanceDrainOptions, c as createCdcEmulator, d as createMaintenanceDrain, e as createMaintenanceDrainHandler, p as parseChange } from './from-change-Ty95KA8C.js';
9
- import { P as PreparedWriteOpSpec, a as PreparedPlanDocument } from './index-Dc7d8mWI.js';
10
- export { A as AnyModelContract, B as BuiltContracts, C as ContextOwnership, b as ContextOwnershipMap, c as ContractBoundaryViolation, d as ContractInputs, e as ContractMap, f as ContractN1Violation, g as PreparedBindSpec, h as PreparedPlanSpec, i as PreparedReadRouteSpec, S as SCP_LOWERED_MARKER, j as ScpParamValues, T as TransactionDefinition, k as TransactionParamShape, l as TransactionRef, m as TxConditionCheckOptions, n as TxForEachInstruction, o as TxForEachOptions, p as TxInstruction, q as TxRecorder, r as TxWriteInstruction, s as TxWriteOptions, W as WhenComparison, t as assertBundleSerializable, u as assertContractBoundaries, v as assertContractN1Safe, w as assertJsonSerializable, x as assertSupportedCondition, y as buildBridgeBundle, z as buildContexts, D as buildContracts, E as buildManifest, F as buildOperations, G as buildQuerySpec, H as buildTransactionSpec, I as buildTransactions, J as canonicalizeExpressionSpec, K as collectContractBoundaryViolations, L as collectContractN1Violations, M as defineScpTransaction, N as defineTransaction, O as defineTransactions, Q as isTransactionRef, R as operationsContainScpNodes, U as operationsSpecVersion, V as when } from './index-Dc7d8mWI.js';
11
- export { c as createDefaultLinter, g as gsiAmbiguityRule, m as missingGsiRule, n as noScanRule, q as queryBoundaryRule, r as relationDepthRule, a as requireLimitRule } from './relation-depth-BRS513Tq.js';
1
+ import { ak as SelectableOf, al as PrimaryKeyOf, e as ProjectionTransform, f as MaintainEvent, j as MembershipPredicate, h as MaintainConsistency, i as MaintainUpdateMode, am as MembershipPredicateOp, an as publishQuery, ao as publishCommand, D as DDBModel, ap as RetryPolicy, aq as Middleware } from './key-DR7_lpyk.js';
2
+ export { ar as CdcModelRegistry, as as CdcSubscribeHandlers, at as Change, au as Column, av as ColumnMap, aw as CondSlot, ax as CtxBase, ay as CtxModel, az as FilterInput, aA as GsiDefinitionMarker, aB as GsiOptions, aC as InlineSnapshotSpec, aD as Item, aE as KeyDefinitionMarker, aF as KeySegment, aG as KeySlot, aH as KeyStructure, M as ModelStatic, aI as MutateAuthoringOptions, aJ as ParamDescriptor, aK as ParamStructure, aL as PartialQueryKeyOf, aM as PersistCtx, aN as PersistOrigin, aO as QueryKeyOf, aP as QueryResult, aQ as RawCondition, aR as ReadOpCtx, aS as ReadOpKind, aT as ReadParams, aU as ReadRequestCtx, aV as ReadRequestKind, aW as RelationBuilder, aX as RelationSelect, aY as RelationSpec, aZ as RequestContext, a_ as SegmentSpec, d as SegmentedKey, a$ as SelectBuilder, b0 as SelectOf, b1 as UniqueQueryKeyOf, b2 as Updatable, b3 as WriteCtx, b4 as WriteInput, b5 as WriteKind, b6 as WriteMiddleware, b7 as cond, b8 as entityWrites, b9 as getEntityWrites, ba as gsi, bb as identity, bc as k, bd as key, be as mutate, bf as preview, bg as when } from './key-DR7_lpyk.js';
3
+ import * as _aws_sdk_client_dynamodb from '@aws-sdk/client-dynamodb';
4
+ import { M as ModelKind, F as FieldOptions, D as DynamoType, R as RelationOptions, A as AggregateOptions, a as AggregateValue } from './types-BXLzIcQD.js';
5
+ export { $ as LiteralParam, a0 as NumberParam, a1 as Param, a2 as ParamKind, a3 as StringParam, a4 as param } from './types-2PMXEn5x.js';
12
6
 
13
7
  /**
14
8
  * Query options with conditional `consistentRead` availability.
@@ -19,7 +13,7 @@ export { c as createDefaultLinter, g as gsiAmbiguityRule, m as missingGsiRule, n
19
13
  * @typeParam Key - The query key type used in this call
20
14
  * @typeParam ModelClass - The model class constructor (`typeof UserModel`)
21
15
  */
22
- type QueryOptions$1<Key, ModelClass> = [Key] extends [
16
+ type QueryOptions<Key, ModelClass> = [Key] extends [
23
17
  PrimaryKeyOf<ModelClass>
24
18
  ] ? {
25
19
  consistentRead?: boolean;
@@ -39,403 +33,6 @@ type ListOptions<T, S extends SelectableOf<T> = SelectableOf<T>> = {
39
33
  order?: 'ASC' | 'DESC';
40
34
  };
41
35
 
42
- /**
43
- * Read-middleware runtime (issue #138). Drives the registered {@link Middleware}
44
- * list through the read hook points R1–R5 with the ordering docs/middleware.md
45
- * specifies:
46
- *
47
- * - `before*` (R1 / R2) run **first-registered-first** (FIFO);
48
- * - `afterFetch` (R3 / R4) and `onError` (R5) run **last-registered-first**
49
- * (LIFO) — onion nesting without a `next()` callback.
50
- *
51
- * All hooks are async-first: each is always `await`ed, so a sync hook (returns
52
- * `void` / a value) and an async hook (returns a `Promise`) compose uniformly.
53
- *
54
- * A {@link MiddlewareRuntime} is constructed ONCE per read at the request entry
55
- * (R1) from the snapshot of registered middleware + the per-call `context`, then
56
- * threaded — unchanged — through the root read and every relation fan-out op
57
- * (the same way `retry` is threaded, see `RelationTraversalOptions`). Threading
58
- * an immutable runtime (not re-reading the registry per op) keeps a read's hook
59
- * set stable even if middleware is (un)registered mid-flight, and is
60
- * concurrency-safe under parallel fan-out (#31).
61
- *
62
- * @see docs/middleware.md
63
- */
64
-
65
- /**
66
- * The per-read middleware runtime. Immutable wrt the middleware list and the
67
- * request `context`; threaded by reference into every fan-out op. A read with no
68
- * registered middleware is represented by an EMPTY runtime whose `active` is
69
- * `false`, so the hot read path can skip all hook plumbing with one check.
70
- */
71
- declare class MiddlewareRuntime {
72
- /** The frozen, registration-ordered middleware snapshot for this read. */
73
- private readonly chain;
74
- /** The host-injected per-call context (`{}` when the read passed none). */
75
- readonly context: RequestContext;
76
- constructor(chain: readonly Middleware[], context: RequestContext);
77
- /** `true` when at least one middleware is registered for this read. */
78
- get active(): boolean;
79
- /**
80
- * Build the request-level context (R1 / R4 / R5 share it). Pure — no hooks run
81
- * — so a caller can create it up front and still reference it from R5 even if
82
- * R1 throws mid-chain.
83
- */
84
- requestCtx(kind: ReadRequestKind, model: CtxModel, params: ReadParams): ReadRequestCtx;
85
- /**
86
- * R1 — request entry, before key-resolution / plan. Builds the
87
- * {@link ReadRequestCtx} then runs every `read.before` FIFO; a hook may mutate
88
- * `ctx.params` and may `throw` to cancel the read. Returns the (possibly
89
- * hook-mutated) ctx so the caller can re-read `params` and reuse the SAME ctx
90
- * for R4 / R5 (shared `state`).
91
- */
92
- runRequestBefore(kind: ReadRequestKind, model: CtxModel, params: ReadParams): Promise<ReadRequestCtx>;
93
- /**
94
- * R4 — final hydrated, relation-merged result. Runs every `read.afterFetch`
95
- * LIFO, threading each hook's return value into the next (onion), and returns
96
- * the final (possibly replaced) result. `ctx` is the SAME object R1 produced,
97
- * so a `before`/`afterFetch` pair shares `ctx.state`.
98
- */
99
- runRequestAfter<T>(ctx: ReadRequestCtx, result: T): Promise<T>;
100
- /**
101
- * R5 (request-level) — runs every `read.onError` LIFO. A hook may **recover**
102
- * by RETURNING a non-`undefined` value: it becomes the read's resolved result
103
- * in place of throwing, and the FIRST such hook (in LIFO order) wins and
104
- * short-circuits the rest of the chain. If every hook declines (returns
105
- * `undefined`/`void`), the original error rethrows — preserving the
106
- * "failed read still rejects" default. (Proposal R5 + appendix A: onError may
107
- * "rethrow / recover"; hooks are unrestricted.)
108
- *
109
- * The recovered value is returned as `T` (the read's result type); its shape
110
- * is the host's responsibility — a hook recovering a `query` should return an
111
- * item / `null`, one recovering a `list` a `{ items, cursor }`.
112
- */
113
- runRequestError<T>(ctx: ReadRequestCtx, err: unknown): Promise<T>;
114
- /**
115
- * Drive ONE physical op (root read or any fan-out fetch) through R2 → send →
116
- * R3, with R5 (op-level) on failure:
117
- *
118
- * 1. R2 (`read.op.before`, FIFO) — may mutate `ctx.operation`, may `throw`;
119
- * 2. the caller's `send` runs against the (possibly mutated) operation;
120
- * 3. R3 (`read.op.afterFetch`, LIFO) — transforms the raw items (onion);
121
- * 4. on any throw from `send` (or R2 / R3), R5 (`read.op.onError`, LIFO) runs:
122
- * a hook may **recover** by RETURNING an `Item[]` (those become this op's
123
- * items and the pipeline continues); the FIRST hook that returns items wins
124
- * and short-circuits the chain. If none recovers, the original error
125
- * rethrows.
126
- *
127
- * Returns the (possibly R3-replaced, or R5-recovered) items. The `send` closure
128
- * receives the final operation so a caller that built its operation from
129
- * `ctx.operation` before this call still observes an R2 mutation (the caller
130
- * MUST send `ctx.operation`, which this method passes through).
131
- */
132
- runOp(operation: DynamoDBOperation, relationPath: readonly string[], model: CtxModel, send: (operation: DynamoDBOperation) => Promise<Item[]>): Promise<Item[]>;
133
- }
134
-
135
- /**
136
- * The ONE shared `TransactWriteItems` commit orchestration (issue #97).
137
- *
138
- * Every TS path that renders a transaction's resolved items into a single atomic
139
- * `TransactWriteItems` — the in-process command runtime's single-key derived-effect
140
- * composition ({@link import('./command-runtime.js')}'s `executeReferentialTransaction`)
141
- * and its array-of-keys / #90 multi-fragment writes path (`executeTransactWrites`), the
142
- * declarative spec executor ({@link import('../operations/declarative-transaction.js')}'s
143
- * `executeDeclarativeTransaction`), and the public imperative
144
- * {@link import('../operations/transaction.js').TransactionContext} (`executeTransaction`)
145
- * — performs the SAME four steps, in this order:
146
- *
147
- * 1. **collapse** same-physical-key collisions, op-aware ({@link collapseSameKey}, the
148
- * #93 rule shared with Python `_collapse_same_key_items`);
149
- * 2. enforce the DynamoDB **≤25** hard limit ({@link MAX_TRANSACT_ITEMS}) — a transaction
150
- * is atomic and CANNOT be split, so a >25 set is rejected, never chunked;
151
- * 3. **commit** atomically via {@link Executor.transactWrite};
152
- * 4. post-commit **capture** — feed each committed item into the in-process
153
- * change-capture seam (issue #72 / #94), gated on
154
- * {@link ChangeCaptureRegistry.hasSubscribers} for zero overhead.
155
- *
156
- * The root cause behind #93 and #94 was that these four steps were DUPLICATED across the
157
- * four callers, so a fix (the #93 `Put`+`Update` reject; the #94 capture) landed on some
158
- * paths and was missed on others (the path-takeoff bug recurred twice). This module is the
159
- * SINGLE place that orchestration lives. Each caller's job is reduced to building its own
160
- * items (per-caller, because item shapes and model-labeling differ) and handing them — plus
161
- * an optional model-label map and an optional limit-error customizer — to
162
- * {@link commitTransaction}. No caller carries its own collapse / ≤25 / commit / capture.
163
- *
164
- * Capture runs ONLY after a successful commit: a rolled-back (cancelled) transaction throws
165
- * out of {@link Executor.transactWrite} before reaching the capture step and therefore emits
166
- * ZERO events — the same post-commit semantics every single-op write path already has.
167
- * Production via real DynamoDB Streams is unaffected (Streams capture at the table layer
168
- * regardless of code path). Python is already single-path: every transaction write routes
169
- * through `GraphDDBRuntime.execute_transaction` → `TransactionExpander.expand` (collapse +
170
- * ≤25) → boto3 `transact_write_items`, and captures via real Streams, so it mirrors this
171
- * one TS path branch-for-branch.
172
- */
173
-
174
- /**
175
- * The DynamoDB `TransactWriteItems` item cap (25). A transaction is atomic, so a set larger
176
- * than this CANNOT be split across requests without breaking atomicity — every transaction
177
- * path rejects an over-limit set rather than chunking it ({@link commitTransaction}). This is
178
- * the SINGLE definition of the limit; the public {@link
179
- * import('../operations/transaction.js')} re-exports it for the imperative
180
- * `TransactionContext` API surface.
181
- */
182
- declare const MAX_TRANSACT_ITEMS = 25;
183
-
184
- declare class ClientManager {
185
- private static client;
186
- private static documentClient;
187
- /**
188
- * The active I/O executor (issue #76). `null` ⇒ use the lazily-constructed
189
- * default {@link DynamoExecutor}. Tests / the memory adapter replace it via
190
- * {@link setExecutor}. Constructed lazily (and imported lazily) so importing
191
- * GraphDDB for planning / linting / codegen never pulls in the AWS SDK.
192
- */
193
- private static executor;
194
- private static defaultExecutor;
195
- /**
196
- * The globally-configured single-op retry policy (issue #111). `null` ⇒ the
197
- * always-on built-in {@link DEFAULT_RETRY_POLICY} applies (retry is enabled out
198
- * of the box). Set via {@link DDBModel.setRetryPolicy} / {@link setRetryPolicy};
199
- * read at call time by the default {@link RetryingExecutor}.
200
- */
201
- private static retryPolicy;
202
- /**
203
- * The host-runtime read-middleware registry (issue #50 / #138) — a process-wide
204
- * ordered list of {@link Middleware}, the same global-config + injectable-seam
205
- * pattern as {@link retryPolicy} / {@link executor}. Registered via
206
- * {@link DDBModel.use} / {@link use}, snapshotted per read, and NEVER serialized
207
- * (it never touches the planner / spec / `operations.json`, so the bridge #48 is
208
- * unaffected). {@link reset} clears it.
209
- */
210
- private static middleware;
211
- static setClient(client: DynamoDBClient): void;
212
- static getClient(): DynamoDBClient;
213
- static getDocumentClient(): Promise<DynamoDBDocumentClient>;
214
- /**
215
- * The active I/O executor (issue #76). All read/write I/O flows through this
216
- * seam. Defaults to a {@link DynamoExecutor} (current real-DynamoDB
217
- * behavior); tests inject a `MemoryExecutor` via {@link setExecutor}.
218
- *
219
- * The default is constructed lazily and imported lazily so importing
220
- * GraphDDB for planning / linting / codegen does not eagerly load the
221
- * executor module graph.
222
- */
223
- static getExecutor(): Executor;
224
- /**
225
- * Inject a custom executor (test adapter / memory engine). It is used AS-IS —
226
- * not wrapped in a {@link RetryingExecutor} — so the in-memory adapter (which
227
- * never throttles) bypasses the retry layer entirely (issue #111).
228
- */
229
- static setExecutor(executor: Executor): void;
230
- /** Restore the default {@link RetryingExecutor}-wrapped {@link DynamoExecutor}. */
231
- static resetExecutor(): void;
232
- /**
233
- * Configure the global single-op retry policy (issue #111). Mirrors
234
- * {@link setClient} / {@link setTableMapping}: a process-wide setting the default
235
- * {@link RetryingExecutor} reads. `null` restores the always-on built-in default.
236
- * A per-call `retry?:` override on an operation option bag takes precedence.
237
- */
238
- static setRetryPolicy(policy: RetryPolicy | null): void;
239
- /** The globally-configured retry policy, or `null` for the built-in default. */
240
- static getRetryPolicy(): RetryPolicy | null;
241
- /**
242
- * Register a read {@link Middleware} (issue #50 / #138). Appended last, so its
243
- * `before*` hooks run last (FIFO) and its `afterFetch` / `onError` hooks run
244
- * first (LIFO). Mirrors {@link setRetryPolicy}; backs {@link DDBModel.use}.
245
- */
246
- static use(mw: Middleware): void;
247
- /** Remove every registered read middleware (teardown / tests). Backs {@link DDBModel.clearMiddleware}. */
248
- static clearMiddleware(): void;
249
- /**
250
- * A point-in-time, registration-ordered snapshot of the registered read
251
- * middleware. A read takes this once at entry (R1) and threads it down, so the
252
- * read's hook set stays stable even if middleware is registered / cleared
253
- * mid-flight.
254
- */
255
- static getMiddleware(): readonly Middleware[];
256
- /** @internal — for testing only */
257
- static reset(): void;
258
- }
259
-
260
- /**
261
- * Maximum number of keys allowed in a single DynamoDB BatchGetItem request.
262
- */
263
- declare const BATCH_GET_MAX_KEYS = 100;
264
- /**
265
- * Maximum number of items allowed in a single DynamoDB BatchWriteItem request.
266
- */
267
- declare const BATCH_WRITE_MAX_ITEMS = 25;
268
-
269
- /**
270
- * The single source of truth for **relation execution staging** (issue #70),
271
- * shared by the static SSoT generator (`src/spec/operations.ts`,
272
- * {@link buildQuerySpec}) and the TS host runtime
273
- * (`src/relation/traversal.ts`, {@link resolveRelations}). It is the TS analogue
274
- * of the Python runtime's `_relation_stages` / `_plan_concurrency`
275
- * (`python/graphddb_runtime/runtime.py`).
276
- *
277
- * ## Why one shared module (anti-drift)
278
- *
279
- * #70a records an {@link ExecutionPlanSpec} (`groups` + `concurrency`) in the
280
- * SSoT; #70b made the Python runtime **honor** it; #70c makes the TS host runtime
281
- * honor it too. For the two runtimes to make **identical** plan-declared
282
- * decisions, TS must consume *the same plan facts* the generator serialized — not
283
- * re-derive parallelism by an independent "parallelize every sibling" walk. The
284
- * host runtime has no generated JSON to load (it works off live decorator
285
- * metadata + a `select` tree), so it derives the plan from that live tree using
286
- * **this exact function** — the same one the generator calls to produce the
287
- * serialized `executionPlan`. Generator and runtime therefore cannot drift: they
288
- * are one codepath. {@link deriveExecutionPlan} is the seam.
289
- *
290
- * ## How the runtime obtains the plan
291
- *
292
- * The generator produces `operations[]` (root + relation ops, each with a
293
- * `resultPath`) and runs {@link deriveExecutionPlan} over their result paths. The
294
- * host runtime walks the live `select` tree with {@link relationResultPaths},
295
- * which emits the **identical** ordered `resultPath` list
296
- * (`buildRelationOperations` is refactored to use it), and runs the **same**
297
- * {@link deriveExecutionPlan} over `['$', ...those]`. The resulting
298
- * {@link ExecutionPlanSpec} is threaded through the recursive relation traversal:
299
- * at each parent level the traversal honors the plan's grouping (which child ops
300
- * are an independent, concurrency-eligible stage) and the declared `concurrency`
301
- * bound — reading them from the plan, never re-deciding them.
302
- */
303
-
304
- /**
305
- * A **resolved** relation execution plan as the host runtime consumes it: the
306
- * serialized {@link ExecutionPlanSpec} (`groups` over operation indices +
307
- * `concurrency`) together with the `resultPath` each operation index produces, so
308
- * the recursive traversal can locate "the operations at this parent level" and
309
- * read their declared stage from the plan.
310
- *
311
- * Built once at a read's entry point ({@link buildRelationExecutionPlan}) and
312
- * threaded, unchanged, through every recursion level. A read whose `select` has
313
- * **no** relations (or where the plan would be a single op) carries no plan; the
314
- * traversal then has nothing to stage.
315
- */
316
- interface RelationExecutionPlan {
317
- /** The serialized staging facts (issue #70a) the traversal honors. */
318
- readonly plan: ExecutionPlanSpec;
319
- /** Operation index → the `resultPath` it writes (index 0 = root `$`). */
320
- readonly resultPaths: readonly string[];
321
- }
322
-
323
- interface QueryOptions {
324
- consistentRead?: boolean;
325
- maxDepth?: number;
326
- /**
327
- * Per-call throttle / transient-error retry override (issue #111), applied to the
328
- * parent read AND to every relation-fan-out send under it. A {@link RetryPolicy}
329
- * replaces the global policy for this call; `false` disables retry. Omit to use
330
- * the configured (or built-in default) policy.
331
- */
332
- retry?: RetryOverride;
333
- /**
334
- * When `true`, the returned item carries its resolved base-table key as a
335
- * non-enumerable Symbol property so it can be passed back to `update()`
336
- * directly — even for a partial `select` or a GSI-based query. Host-side
337
- * runtime behaviour only; not part of the serialized query.
338
- */
339
- updatable?: boolean;
340
- /**
341
- * Opt-in class / domain-object hydration (issue #53). When provided, the
342
- * factory is applied as the **final after-fetch step** to the fully-built
343
- * plain object (after projection, relation resolution, and any `updatable`
344
- * hidden-key attachment) and its return value is what the read resolves to —
345
- * the return type propagates from the factory (see `ModelStatic.query`).
346
- *
347
- * Semantics:
348
- * - applied to a **present** item only; a `null` read stays `null` and the
349
- * factory is NOT invoked, so the factory need not be null-tolerant;
350
- * - a throw from the factory propagates verbatim as the read's rejection
351
- * (the fetch already succeeded; the failure is purely in caller code);
352
- * - synchronous by design — an async transform is a caller-side `await` on
353
- * the plain result, intentionally out of scope here.
354
- *
355
- * Host-side runtime behaviour ONLY: like `updatable` / `consistentRead`, the
356
- * factory is a host-language closure that is NEVER serialized into the SSoT /
357
- * `operations.json`, so it does not impede the multi-language bridge (#48).
358
- * "What to fetch" stays declarative; "what object to load it onto" is host-side.
359
- */
360
- hydrate?: (raw: Record<string, unknown> | null) => unknown;
361
- /**
362
- * Host-injected per-call read context (issue #50 / #138) — exposed to every
363
- * read hook (R1–R5) as `ctx.context`, and threaded UNCHANGED to every relation
364
- * fan-out op so a hook (e.g. a tenant-scope R2 filter) applies across the root
365
- * read AND the fan-out fetches. A host-language value, NEVER serialized into the
366
- * SSoT / `operations.json` (the bridge #48 is unaffected). Absent ⇒ `{}`.
367
- */
368
- context?: RequestContext;
369
- }
370
- declare function executeQuery(modelClass: Function, key: Record<string, unknown>, selectSpec: Record<string, unknown>, options?: QueryOptions): Promise<Record<string, unknown> | null>;
371
-
372
- interface ListInput {
373
- limit?: number;
374
- after?: string;
375
- order?: 'ASC' | 'DESC';
376
- /** Declarative server-side DynamoDB FilterExpression input. */
377
- filter?: Record<string, unknown>;
378
- /**
379
- * When `true`, each returned item carries its resolved base-table key as a
380
- * non-enumerable Symbol property so it can be passed back to `update()`
381
- * directly. Host-side runtime behaviour only; not part of the serialized
382
- * query.
383
- */
384
- updatable?: boolean;
385
- /**
386
- * Per-call throttle / transient-error retry override (issue #111). A
387
- * {@link RetryPolicy} replaces the global policy for this read; `false` disables
388
- * retry. Omit to use the configured (or built-in default) policy.
389
- */
390
- retry?: RetryOverride;
391
- /**
392
- * Host-injected per-call read context (issue #50 / #138) — exposed to every
393
- * read hook as `ctx.context`. Used only when this `list` is the read ENTRY
394
- * point (the public `DDBModel.list` / a `list` envelope route): a fresh
395
- * {@link MiddlewareRuntime} is built from it. Ignored when a relation fan-out
396
- * threads an already-built {@link mw} (the runtime then carries the root read's
397
- * context). Absent ⇒ `{}`.
398
- */
399
- context?: RequestContext;
400
- /**
401
- * @internal The per-read middleware runtime, threaded UNCHANGED from a relation
402
- * fan-out (issue #138) so the hasMany Query's op-level hooks (R2/R3/R5) fire
403
- * with the root read's context + the fan-out's {@link relationPath}. When absent
404
- * at the entry point, one is built from {@link context}.
405
- */
406
- mw?: MiddlewareRuntime;
407
- /**
408
- * @internal The relation path to THIS list read — `[]` (or absent) for a
409
- * top-level `list`; the fan-out leg (e.g. `['orders']`) for a hasMany Query.
410
- * Populates the op-level ctx's `relationPath` (issue #138).
411
- */
412
- relationPath?: readonly string[];
413
- }
414
- /**
415
- * Public list result returned by `DDBModel.list()`. Contains exactly the
416
- * `items` and `cursor` keys — no `rawItems` leak.
417
- */
418
- interface ListResult {
419
- items: Record<string, unknown>[];
420
- cursor: string | null;
421
- }
422
- /**
423
- * Public list execution backing `DDBModel.list()`. Delegates to
424
- * {@link executeListInternal} and constructs a fresh result object exposing
425
- * only `{ items, cursor }`, so internal-only fields such as `rawItems` never
426
- * cross the public API boundary (no reliance on `as` type narrowing).
427
- */
428
- declare function executeList(modelClass: Function, key: Record<string, unknown>, selectSpec: Record<string, unknown>, options?: ListInput): Promise<ListResult>;
429
-
430
- declare function derivePrefix(customPrefix: string | undefined, className: string | undefined): string;
431
-
432
- /**
433
- * Validate that no two access patterns (PK + GSIs) have overlapping
434
- * input field sets. A subset relationship means a single query input
435
- * could match multiple access patterns, creating ambiguity.
436
- */
437
- declare function validateGsiAmbiguity(primaryKey: KeyDefinition | null, gsiDefinitions: GsiDefinition[]): void;
438
-
439
36
  interface ModelOptions {
440
37
  table: string;
441
38
  prefix?: string;
@@ -736,8 +333,8 @@ type ProjectionValue = SourceRef | ProjectionTransform;
736
333
  * multi-source view (different sources filling DIFFERENT fields) is fine.
737
334
  */
738
335
 
739
- type AnyModelClass$1 = new (...args: any[]) => any;
740
- type SourceFactory = () => AnyModelClass$1;
336
+ type AnyModelClass = new (...args: any[]) => any;
337
+ type SourceFactory = () => AnyModelClass;
741
338
  /**
742
339
  * A map keyed by `keyof Self` (the typed view fields) with value type `V`. When
743
340
  * `Self` is left at its `unknown` default (the un-annotated decorator), it widens to
@@ -1068,1145 +665,21 @@ declare function max(field: string): AggregateValue;
1068
665
  */
1069
666
  declare function cdcProjected(): (_target: Function, _context: ClassDecoratorContext) => void;
1070
667
 
1071
- declare class TableMapping {
1072
- private static mapping;
1073
- static set(mapping: Record<string, string>): void;
1074
- static resolve(declaredName: string): string;
1075
- /** @internal for testing only */
1076
- static reset(): void;
1077
- }
1078
-
1079
- /** Type guard: is this select-position value a builder rather than a plain spec? */
1080
- declare function isSelectBuilder(value: unknown): value is SelectBuilderSpec;
1081
-
1082
- interface UpdateExpressionResult {
1083
- expression: string;
1084
- names: Record<string, string>;
1085
- values: Record<string, unknown>;
1086
- }
1087
- declare function buildUpdateExpression(changes: Record<string, unknown>, embeddedFieldNames: Set<string>): UpdateExpressionResult;
1088
-
1089
- interface ConditionExpressionResult {
1090
- expression: string;
1091
- names: Record<string, string>;
1092
- values: Record<string, unknown>;
1093
- }
1094
-
1095
- declare function buildConditionExpression(condition: Record<string, unknown> | RawCondition): ConditionExpressionResult;
1096
-
1097
- /**
1098
- * Compiled DynamoDB `FilterExpression`. Names are `#`-aliased column
1099
- * placeholders (collision-free, refactor-safe) and values are `:`-aliased
1100
- * parameters — there is no literal interpolation, so the compiler is injection
1101
- * free. This result is attached to a Query/Scan operation independently of the
1102
- * KeyConditionExpression and ProjectionExpression.
1103
- */
1104
- interface FilterExpressionResult {
1105
- filterExpression: string;
1106
- expressionAttributeNames: Record<string, string>;
1107
- expressionAttributeValues: Record<string, unknown>;
1108
- }
1109
- /**
1110
- * Compile a declarative {@link FilterInput} object into a DynamoDB
1111
- * `FilterExpression` with parameterized values and aliased column names.
1112
- *
1113
- * Multiple top-level field keys are joined with implicit `AND`. Returns
1114
- * `undefined` for an empty / no-op filter so callers can skip attaching it.
1115
- */
1116
- declare function compileFilterExpression(filter: Record<string, unknown> | undefined, metadata: EntityMetadata): FilterExpressionResult | undefined;
1117
- /**
1118
- * Evaluate a {@link FilterInput} against an already-hydrated item, client-side.
1119
- *
1120
- * Used for belongsTo / hasOne relations, which resolve via `BatchGetItem` and
1121
- * therefore cannot push a server-side `FilterExpression`. Operates on the
1122
- * fields present on the item (compared with hydrated TS values, e.g. `Date`).
1123
- */
1124
- declare function evaluateFilter(item: Record<string, unknown>, filter: Record<string, unknown>): boolean;
1125
-
1126
- /**
1127
- * Declarative transaction executor (issue #46, Python-bridge Phase 4).
1128
- *
1129
- * Executes a serializable {@link TransactionSpec} against DynamoDB by expanding
1130
- * its templated items with concrete params and an optional array of `forEach`
1131
- * elements, deriving each row's key (and GSI) attributes from the manifest, and
1132
- * issuing **one** `TransactWriteItems` (≤25). This is the bridge-side, fully
1133
- * declarative counterpart to the ergonomic `executeTransaction` (the latter
1134
- * stays as the TS-only imperative API). Both the TS path here and the Python
1135
- * runtime expand the spec by the **same** rules, which the conformance harness
1136
- * verifies end-to-end.
1137
- *
1138
- * Template forms (mirroring the planner): `{param}` resolves from the caller
1139
- * params; `{item.<field>}` resolves from the current `forEach` element. A leaf
1140
- * with no placeholder is a literal.
1141
- */
1142
-
1143
- /** A `{ Put | Update | Delete }` entry as accepted by `TransactWriteCommand`. */
1144
- type TransactItem = Record<string, unknown>;
1145
- /**
1146
- * Expand a {@link TransactionSpec} into the concrete `TransactItems` list for a
1147
- * given set of params. Each item with a `forEach` binding is expanded once per
1148
- * element of the named array param (skipping elements whose `when` fails); a
1149
- * plain item is emitted once (skipping it when its `when` fails). After expansion
1150
- * the items are collapsed op-aware ({@link collapseSameKeyItems}) by the SINGLE shared
1151
- * {@link import('../runtime/same-key-collapse.js').collapseSameKey} rule the in-process
1152
- * runtime and the Python `TransactionExpander` apply identically: a swap pair that resolved
1153
- * to one physical key becomes a net no-op, a self-row Put/Delete de-dups, same-key counter
1154
- * ADDs merge, and a `Put`+`Update` (an aliased self-target counter, #93) — or any other
1155
- * unsupported same-key combination — is REJECTED loudly rather than silently dropped, so the
1156
- * same SSoT cannot diverge. The ≤25 limit is checked here too (mirroring Python's
1157
- * `TransactionExpander.expand`); {@link commitTransaction} re-checks it as the runtime guard.
1158
- */
1159
- declare function expandTransaction(spec: TransactionSpec, manifest: Manifest, params: Record<string, unknown>): TransactItem[];
1160
- /**
1161
- * Execute a declarative transaction spec with concrete params against DynamoDB (issue #46).
1162
- *
1163
- * Routes through the SINGLE shared {@link commitTransaction} orchestration (issue #97): the
1164
- * expanded items are collapsed op-aware, capped at ≤25 (a transaction is atomic — never
1165
- * split), committed atomically, and fed POST-COMMIT into the in-process change-capture seam
1166
- * (issue #72 / #94) — so a declarative transaction now emits ChangeEvents just like every
1167
- * other transaction path (the original #97 goal), with each modeled row labelled by its
1168
- * entity and the `literalKey` marker rows captured unlabelled for the emulator's table-level
1169
- * fallback. A no-op (zero expanded items) issues no call; a rolled-back transaction throws
1170
- * before the capture step and emits ZERO events.
1171
- */
1172
- declare function executeDeclarativeTransaction(spec: TransactionSpec, manifest: Manifest, params: Record<string, unknown>): Promise<void>;
1173
-
1174
- interface ExplainInput {
1175
- select?: Record<string, unknown>;
1176
- limit?: number;
1177
- after?: string;
1178
- order?: 'ASC' | 'DESC';
1179
- consistentRead?: boolean;
1180
- filter?: Record<string, unknown>;
1181
- }
1182
- declare function executeExplain(modelClass: Function, key: Record<string, unknown>, options?: ExplainInput): ExecutionPlan;
1183
-
1184
- declare function serializeFieldValue(value: unknown, fieldMeta: FieldMetadata): unknown;
1185
-
1186
- declare function resolveKey(queryFields: string[], metadata: EntityMetadata): ResolvedKey;
1187
-
1188
- interface ProjectionResult {
1189
- projectionExpression: string;
1190
- expressionAttributeNames: Record<string, string>;
1191
- }
1192
- /**
1193
- * Builds ProjectionExpression and ExpressionAttributeNames from a select object.
1194
- *
1195
- * Rules:
1196
- * - scalar (`true`) → field name added to projection
1197
- * - inline embeddedSnapshot read (`{ inline: true }`, issue #196) → the owner-row
1198
- * physical attribute is added to the projection like a scalar (the maintained
1199
- * copy is read straight off the row, NOT traversed to child partitions)
1200
- * - embedded (object without `select`/`inline`) → nested path e.g. `#p0.#p1`
1201
- * - relation (object with `select` property) → excluded from projection
1202
- * - additionalFields → added to projection (for implicit key resolution)
1203
- */
1204
- declare function buildProjection(select: Record<string, unknown>, additionalFields?: string[]): ProjectionResult | undefined;
1205
-
1206
- interface PlanInput {
1207
- key: Record<string, unknown>;
1208
- select: Record<string, unknown>;
1209
- limit?: number;
1210
- after?: Record<string, unknown>;
1211
- order?: 'ASC' | 'DESC';
1212
- consistentRead?: boolean;
1213
- additionalProjectionFields?: string[];
1214
- /** Declarative server-side filter (compiled to a FilterExpression). */
1215
- filter?: Record<string, unknown>;
1216
- /**
1217
- * When `true`, the base-table key attributes (`PK`/`SK`) are added to the
1218
- * ProjectionExpression so the hydrator can carry a resolved key on the result
1219
- * (issue #54). Required for partial-`select` and GSI-based reads, where the
1220
- * key would otherwise not be returned.
1221
- */
1222
- updatable?: boolean;
1223
- }
1224
- declare function plan(metadata: EntityMetadata, input: PlanInput): ExecutionPlan;
1225
-
1226
- /**
1227
- * Execute a structured read operation through the active I/O seam (issue #76).
1228
- *
1229
- * This is the read entry point the planner-driven paths (`query` / `list`) call.
1230
- * It delegates to `ClientManager.getExecutor()`, which is the default
1231
- * {@link import('./retrying-executor.js').RetryingExecutor}-wrapped
1232
- * {@link import('./dynamo-executor.js').DynamoExecutor} unless a test has injected
1233
- * a `MemoryExecutor` via `ClientManager.setExecutor()`. The optional
1234
- * {@link ReadExecOptions} carries a per-call retry override (issue #111).
1235
- */
1236
- declare function execute(operation: DynamoDBOperation, options?: ReadExecOptions): Promise<ExecutorResult>;
1237
-
1238
- /**
1239
- * The default {@link Executor}: a thin, behavior-preserving move of GraphDDB's
1240
- * original DynamoDB I/O behind the unified seam (issue #76). Each method builds
1241
- * exactly the AWS SDK command the corresponding operation built before the
1242
- * refactor and sends it through `ClientManager.getDocumentClient()`, so the
1243
- * command shapes (and therefore every existing mock-based / integration test)
1244
- * are unchanged.
1245
- *
1246
- * The optional per-call options (`ReadExecOptions` / `BatchExecOptions`) carry a
1247
- * retry override (issue #111) that is meaningful ONLY to {@link RetryingExecutor};
1248
- * here they are accepted to satisfy the {@link Executor} interface and otherwise
1249
- * ignored (this executor performs no app-level error retry of its own).
1250
- */
1251
- declare class DynamoExecutor implements Executor {
1252
- execute(operation: DynamoDBOperation, _options?: ReadExecOptions): Promise<ExecutorResult>;
1253
- batchGet(input: BatchGetExecInput, _options?: ReadExecOptions): Promise<ExecutorResult>;
1254
- put(input: PutInput, options?: WriteExecOptions): Promise<WriteResult>;
1255
- update(input: UpdateInput, options?: WriteExecOptions): Promise<WriteResult>;
1256
- delete(input: DeleteInput, options?: WriteExecOptions): Promise<WriteResult>;
1257
- batchWrite(tableName: string, items: BatchWriteExecItem[], _options?: BatchExecOptions): Promise<void>;
1258
- transactWrite(items: TransactWriteExecItem[], _options?: BatchExecOptions): Promise<void>;
1259
- }
1260
-
1261
- /**
1262
- * {@link RetryingExecutor} — the single-op throttle / transient-error retry layer
1263
- * (issue #111). It DECORATES an inner {@link Executor} (the default
1264
- * {@link DynamoExecutor}), wrapping every single-op send — `execute` (Get/Query),
1265
- * `batchGet` (relation fan-out + planner reads), `put` / `update` / `delete`, and
1266
- * `transactWrite` — in {@link runWithRetry} so a throttle / transient error is
1267
- * retried with exponential backoff + jitter under a bounded attempt cap.
1268
- *
1269
- * This is the (B) ERROR-retry layer; the (A) partial-batch retry
1270
- * (`UnprocessedKeys` / `UnprocessedItems`) lives inside `batchGet` / `batchWrite`
1271
- * and is a SEPARATE concern (see `retry-policy.ts` header). The two share the
1272
- * backoff function but never merge:
1273
- * - `batchGet` here gets an OUTER error-retry wrap (the inner BatchGet still does
1274
- * its own UnprocessedKeys retry; the outer wrap catches a hard throttle REJECT
1275
- * of the whole send).
1276
- * - `batchWrite` is the in-flight bulk path that already owns UnprocessedItems
1277
- * retry per chunk; it is passed through UNWRAPPED here to avoid double-counting
1278
- * a throttle as both an unprocessed-item retry and an error retry. (Its chunks
1279
- * already back off on the shared ramp.)
1280
- *
1281
- * The policy precedence per call: an explicit per-call override (`retry:` on the
1282
- * option bag) wins; otherwise the globally-configured policy
1283
- * (`DDBModel.setRetryPolicy`); otherwise the always-on {@link DEFAULT_RETRY_POLICY}.
1284
- * `retry: false`, or a resolved `maxAttempts <= 1`, disables retry for that call.
1285
- */
1286
-
1287
- declare class RetryingExecutor implements Executor {
1288
- private readonly inner;
1289
- private readonly getGlobalPolicy;
1290
- /**
1291
- * @param inner the wrapped executor (the real {@link DynamoExecutor}).
1292
- * @param getGlobalPolicy reads the globally-configured policy (or `null` for the
1293
- * built-in default) at CALL time, so a `DDBModel.setRetryPolicy(...)` after the
1294
- * executor is constructed still takes effect.
1295
- */
1296
- constructor(inner: Executor, getGlobalPolicy: () => RetryPolicy | null);
1297
- execute(operation: DynamoDBOperation, options?: ReadExecOptions): Promise<ExecutorResult>;
1298
- batchGet(input: BatchGetExecInput, options?: ReadExecOptions): Promise<ExecutorResult>;
1299
- put(input: PutInput, options?: WriteExecOptions): Promise<WriteResult>;
1300
- update(input: UpdateInput, options?: WriteExecOptions): Promise<WriteResult>;
1301
- delete(input: DeleteInput, options?: WriteExecOptions): Promise<WriteResult>;
1302
- batchWrite(tableName: string, items: BatchWriteExecItem[], options?: BatchExecOptions): Promise<void>;
1303
- transactWrite(items: TransactWriteExecItem[], options?: BatchExecOptions): Promise<void>;
1304
- }
1305
-
1306
- /**
1307
- * Transforms raw DynamoDB items into partial Entity objects.
1308
- *
1309
- * - Applies type conversions (ISO 8601 → Date for `format: 'datetime'`)
1310
- * - Reconstructs embedded objects from Map attributes
1311
- * - Returns only the fields named in `select`; internal `PK`/`SK`/GSI key
1312
- * attributes are never copied into the result (not actively stripped)
1313
- *
1314
- * When `updatable` is `true`, each result additionally carries its already
1315
- * resolved base-table key (`{ PK, SK }`) as a non-enumerable Symbol property
1316
- * (see {@link attachHiddenKey}) so it can be passed straight back to `update()`
1317
- * even for a partial `select` or a GSI-based read. This is invisible to
1318
- * `JSON.stringify` / `Object.keys` / spread.
1319
- */
1320
- declare function hydrate(rawItems: Record<string, unknown>[], select: Record<string, unknown>, metadata: EntityMetadata, updatable?: boolean): Record<string, unknown>[];
1321
-
1322
- /**
1323
- * Encodes a DynamoDB LastEvaluatedKey into a base64url cursor string.
1324
- */
1325
- declare function encodeCursor(lastEvaluatedKey: Record<string, unknown>): string;
1326
- /**
1327
- * Decodes a base64url cursor string back into a DynamoDB ExclusiveStartKey.
1328
- */
1329
- declare function decodeCursor(cursor: string): Record<string, unknown>;
1330
-
1331
- /**
1332
- * Per-key cursor envelope for batched `range` (`list`) contract methods (issue
1333
- * #62, CQRS single-service runtime; spec `docs/cqrs-contract.md`,
1334
- * "Pagination under batch + list").
1335
- *
1336
- * A `range` contract method paginates **per key**: each key owns its own
1337
- * connection and therefore its own pagination position. The proposal requires
1338
- * that "the cursor envelope must carry the key it belongs to" so a caller can
1339
- * never accidentally resume one key's pagination against another key.
1340
- *
1341
- * This module wraps the **inner** page cursor (the base64url-encoded DynamoDB
1342
- * `LastEvaluatedKey` produced by {@link encodeCursor}) together with a **stable
1343
- * identity of the owning key** into a single opaque envelope. The envelope is
1344
- * itself base64url-encoded so it round-trips through a URL / JSON body exactly
1345
- * like the inner cursor it supersedes — callers treat it as the same opaque
1346
- * string and pass it back as `params.after`.
1347
- *
1348
- * ## Why even the single-key range form is wrapped
1349
- *
1350
- * The proposal frames per-key cursors under "batch + list", but a single `range`
1351
- * method (`inputArity: 'single'`) is the only externally reachable range form
1352
- * for a single contract (see {@link executeQueryMethod} — a `range` method
1353
- * rejects an array by construction). Wrapping the single-key form too keeps the
1354
- * cursor shape **uniform**: a `range` connection always carries a key-bound
1355
- * envelope, so cursor handling does not branch on input arity and the runtime
1356
- * can always validate that a supplied `after` belongs to the key being read.
1357
- *
1358
- * ## Key identity
1359
- *
1360
- * The owning key is serialized with {@link serializeContractKey} — a canonical,
1361
- * field-sorted JSON form that is **byte-identical across TS and Python** (the
1362
- * parity foundation conformance #65 will lock). The envelope stores this string,
1363
- * not the raw key object, so two structurally-equal keys (field order aside)
1364
- * resume the same pagination.
1365
- */
1366
- /**
1367
- * The decoded contents of a per-key cursor envelope: the canonical key identity
1368
- * it belongs to ({@link serializeContractKey}) and the inner page cursor (the
1369
- * base64url DynamoDB `LastEvaluatedKey`).
1370
- */
1371
- interface PerKeyCursorEnvelope {
1372
- /** The canonical serialized identity of the key this cursor paginates. */
1373
- readonly key: string;
1374
- /** The inner page cursor (base64url-encoded `LastEvaluatedKey`). */
1375
- readonly inner: string;
1376
- }
1377
- /**
1378
- * Canonical, cross-runtime-stable string identity of a contract key. Object
1379
- * fields are sorted by name so `{ a, b }` and `{ b, a }` serialize identically;
1380
- * values are emitted as compact JSON, matching the Python runtime's
1381
- * `serialize_contract_key` (sorted items, compact separators). This is the
1382
- * identity used to key a batch result map and to bind a per-key cursor.
1383
- *
1384
- * **#253 adapter (behavior-contracts)**: this is the shared COMMON
1385
- * `canonicalValue` primitive (runtime-boundary.md §2.1 →
1386
- * canonical-serialization.md §2: key identity = top-level key sort + compact
1387
- * JSON), the same delegation the Python / Rust / Go runtimes adopted in #255.
1388
- * graphddb's former field-sorted `JSON.stringify` is deleted; plain JS key
1389
- * values cross the boundary through {@link toSharedValue} (integral `number` →
1390
- * shared int, `Date` → ISO string, `undefined` fields dropped — the exact
1391
- * `JSON.stringify` semantics the old implementation had). Byte identity of the
1392
- * output is locked by the frozen #253 conformance golden.
1393
- *
1394
- * @param key A plain contract-key object (e.g. `{ categoryId: 'tech' }`).
1395
- * @returns A deterministic JSON string, e.g. `{"categoryId":"tech"}`.
1396
- */
1397
- declare function serializeContractKey(key: Record<string, unknown>): string;
1398
- /**
1399
- * Build a per-key cursor envelope from the owning key and an inner page cursor,
1400
- * encoded as a single opaque base64url string. Returns `null` when `inner` is
1401
- * `null` (the key has no further pages — a terminal connection has a `null`
1402
- * cursor, never an envelope wrapping nothing).
1403
- *
1404
- * @param key The contract key this page belongs to.
1405
- * @param inner The inner page cursor, or `null` when the connection is exhausted.
1406
- */
1407
- declare function encodePerKeyCursor(key: Record<string, unknown>, inner: string | null): string | null;
1408
- /**
1409
- * Decode a per-key cursor envelope produced by {@link encodePerKeyCursor}, and
1410
- * **verify** it belongs to `expectedKey`. A cursor minted for one key fed back
1411
- * for another is a caller error (it would silently resume the wrong key's
1412
- * pagination), so it is rejected rather than honored.
1413
- *
1414
- * @param cursor The opaque envelope string (a `params.after` value).
1415
- * @param expectedKey The key currently being read; the envelope must match it.
1416
- * @returns The inner page cursor to hand to the underlying `list`.
1417
- * @throws if the envelope is malformed, or its key identity does not match
1418
- * `expectedKey`.
1419
- */
1420
- declare function decodePerKeyCursor(cursor: string, expectedKey: Record<string, unknown>): string;
1421
-
1422
- /**
1423
- * Static prepared-plan LOADER (issue #208) — the runtime half of the AOT
1424
- * pipeline. `graphddb transform prepared --aot` compiles every verified
1425
- * `DDBModel.prepare` body at BUILD time into a {@link PreparedPlanDocument}
1426
- * (`src/spec/prepared.ts`) and rewrites the call site to
1427
- *
1428
- * ```ts
1429
- * (__gddbPrepared1 ??= loadPreparedPlan(__gddbPreparedPlans, '<id>', ($) => ({…})))
1430
- * ```
1431
- *
1432
- * This module loads that artifact: it re-binds each plan's entity NAMES against
1433
- * the live {@link MetadataRegistry}, verifies the plan is not stale, and returns
1434
- * a handle whose `execute` runs the very same execution cores the runtime
1435
- * `prepare` uses — with **zero plan construction / compilation per op, including
1436
- * the first call**:
1437
- *
1438
- * - a **read** plan binds into the SAME {@link PreparedReadStatement} the
1439
- * runtime `prepare` returns (identical semantics by construction — reads never
1440
- * had an expensive compile; what `prepare` computes once, the artifact
1441
- * carries);
1442
- * - a **write** plan's mutation compile (lifecycle resolution + derived-effect
1443
- * derivation — the expensive half) happened at BUILD time; `execute` renders
1444
- * the serialized templates and runs {@link executeLogicalWriteOps} /
1445
- * {@link executeLogicalParallelWrites} — the same W1–W5 / fast-path / atomic
1446
- * commit / read-back pipeline as the compiled core, so effects are identical.
1447
- *
1448
- * ## Stale plans are a LOUD reject
1449
- *
1450
- * Each plan records a fingerprint of every entity's canonical manifest entry at
1451
- * build time. On first use the loader recomputes the fingerprints from the live
1452
- * registry: a mismatch (the model declarations — key/GSI templates, fields,
1453
- * relations, table mapping — changed since the artifact was generated) throws
1454
- * with a regeneration hint. A stale static plan NEVER executes. Version-skewed
1455
- * artifacts (format / spec IR version) reject the same way.
1456
- *
1457
- * ## Laziness
1458
- *
1459
- * Binding is deferred to the FIRST `execute` (not the `loadPreparedPlan` call):
1460
- * a module-scope `const p = loadPreparedPlan(…)` must not touch the registry at
1461
- * module-init time (the target models may be declared later in the same module —
1462
- * the same TDZ hazard the #206 lazy slot solves). Binding is memoized per
1463
- * `(document, planId)`; it performs registry lookups + fingerprint hashing only,
1464
- * never plan compilation.
1465
- */
1466
-
1467
- interface BoundWriteOp {
1468
- readonly alias: string;
1469
- readonly spec: PreparedWriteOpSpec;
1470
- readonly modelClass: Function;
1471
- readonly modelStatic: ModelStatic<DDBModel>;
1472
- readonly keySlots: Readonly<Record<string, Slot>>;
1473
- readonly inputSlots: Readonly<Record<string, Slot>>;
1474
- readonly conditionSlots?: Readonly<Record<string, Slot>>;
1475
- readonly manifest: Manifest;
1476
- }
1477
- /** A prepared WRITE handle bound from a static plan (#208). `execute(params)`
1478
- * mirrors {@link import('./prepared.js').PreparedWriteStatement.execute}. */
1479
- declare class AotPreparedWriteStatement {
1480
- private readonly ops;
1481
- /** @internal */
1482
- constructor(ops: readonly BoundWriteOp[]);
1483
- execute(params?: Record<string, unknown>, options?: PreparedWriteExecOptions): Promise<Record<string, CommandReturn | undefined> | Record<string, ParallelOpResult | ParallelOpResult[]>>;
1484
- }
1485
- /**
1486
- * Load one statically-compiled prepared plan (issue #208). The rewritten call
1487
- * site passes the artifact document, the stable plan id, and the ORIGINAL
1488
- * prepare body — the body is retained in source as the plan's compile source
1489
- * (drift regeneration + type inference + the untransformed-build fallback) and
1490
- * is NEVER evaluated here: execution consumes the static artifact only.
1491
- *
1492
- * Per-op plan construction / compilation is ZERO, including the first call —
1493
- * the first `execute` performs registry binding + stale-fingerprint
1494
- * verification only (memoized per `(document, planId)`), then every call binds
1495
- * params into the frozen plan and runs the shared execution cores.
1496
- */
1497
- declare function loadPreparedPlan(doc: PreparedPlanDocument, planId: string, body?: PreparedBody): PreparedStatement;
1498
-
1499
- declare function detectRelationFields(select: Record<string, unknown>, metadata: EntityMetadata): RelationMetadata[];
1500
- declare function getImplicitKeyFields(select: Record<string, unknown>, metadata: EntityMetadata): string[];
1501
-
1502
- interface RelationTraversalOptions {
1503
- maxDepth?: number;
1504
- /**
1505
- * When `true`, each resolved single-result relation (`belongsTo` / `hasOne`)
1506
- * and each `hasMany` item carries its resolved base-table key as a
1507
- * non-enumerable Symbol property so it can be passed back to `update()`.
1508
- */
1509
- updatable?: boolean;
1510
- /**
1511
- * Per-call throttle / transient-error retry override (issue #111), threaded from
1512
- * the read's entry point into every relation-fan-out send (the `BatchGet` for
1513
- * `belongsTo` / `hasOne`, the `Query` for `hasMany`). A {@link RetryPolicy}
1514
- * replaces the global policy; `false` disables retry. Threaded unchanged through
1515
- * every recursion level.
1516
- */
1517
- retry?: RetryOverride;
1518
- /**
1519
- * The **declared execution plan** (issue #70c) the traversal HONORS to decide
1520
- * which sibling relations are independent (a concurrency-eligible stage) and the
1521
- * in-flight bound. It is built once at the read's entry point from the live
1522
- * `select` tree via {@link buildRelationExecutionPlan} — the *same*
1523
- * {@link deriveExecutionPlan} the SSoT generator runs — and threaded, unchanged,
1524
- * through every recursion level. The traversal reads its grouping/bound from
1525
- * this plan; it never re-decides "parallelize every sibling".
1526
- *
1527
- * Absent → {@link resolveRelations} builds it from `(select, metadata)` at the
1528
- * top level (the normal host-runtime path) and threads it down. A direct caller
1529
- * (or a test proving the plan is honored) may inject a plan with a different
1530
- * grouping / bound and observe the traversal execute accordingly.
1531
- */
1532
- plan?: RelationExecutionPlan;
1533
- /**
1534
- * The per-read middleware runtime (issue #50 / #138), threaded UNCHANGED from
1535
- * the read's entry point into every relation fan-out fetch — exactly like
1536
- * `retry`. Each fan-out send is wrapped so the op-level hooks R2 (before send) /
1537
- * R3 (raw items) / R5 (op error) fire for that physical op too, with
1538
- * `ctx.relationPath` identifying the fan-out leg (`['orders']`,
1539
- * `['orders','product']`, …) and `ctx.model` the relation's target model.
1540
- * Absent → {@link NO_MIDDLEWARE} (no hooks).
1541
- */
1542
- mw?: MiddlewareRuntime;
1543
- /**
1544
- * The relation path from the root read to the CURRENT traversal level — `[]`
1545
- * at the root, extended by each relation's property name as the traversal
1546
- * recurses. Used only to populate `ctx.relationPath` for the op-level hooks
1547
- * (R2/R3/R5). Threaded alongside {@link mw}.
1548
- */
1549
- relationPath?: readonly string[];
1550
- }
1551
- /**
1552
- * Validates that the select depth does not exceed maxDepth.
1553
- * Throws a runtime error if depth is exceeded.
1554
- */
1555
- declare function validateDepth(select: Record<string, unknown>, metadata: EntityMetadata, maxDepth: number, currentDepth?: number): void;
1556
- /**
1557
- * Resolves all relation fields for a single parent item, HONORING the declared
1558
- * execution plan (issue #70c). Returns the merged result with relations attached.
1559
- *
1560
- * The grouping of sibling relations into concurrency-eligible stages and the
1561
- * in-flight bound come from the serialized {@link RelationExecutionPlan} — the
1562
- * *same* plan the SSoT generator derives (`src/relation/execution-plan.ts`), so
1563
- * the TS host runtime and the Python runtime make identical, plan-declared
1564
- * parallelism decisions. The plan is built once from the live `select` tree at the
1565
- * read's entry (when `options.plan` is absent) and threaded, unchanged, through
1566
- * every recursion level; `currentPath` tracks which parent level this call
1567
- * resolves so each relation's declared stage is looked up by its `resultPath`.
1568
- *
1569
- * For a faithfully-built plan the direct children of one parent path share a
1570
- * stage (the generator's level-by-level layering), so they run as one concurrent
1571
- * group under the declared bound — byte-equivalent to the pre-#70c "parallelize
1572
- * siblings" fan-out. A *declared* plan with different grouping / bound changes the
1573
- * schedule accordingly: the plan is honored, never re-derived.
1574
- *
1575
- * @param currentPath The parent result path this call resolves relations under
1576
- * (`$` at the root; `$.<prop>.items` / `$.<prop>` for nested levels). Used to
1577
- * locate each relation's declared stage in the plan.
1578
- */
1579
- declare function resolveRelations(parentItem: Record<string, unknown>, rawParentItem: Record<string, unknown>, select: Record<string, unknown>, metadata: EntityMetadata, options: RelationTraversalOptions, currentDepth?: number, currentPath?: string): Promise<Record<string, unknown>>;
1580
-
1581
- /**
1582
- * Edge write-effects: derive the adjacency `Put` / `Delete` transaction items
1583
- * that materialize a relation edge on DynamoDB (issue #82, Epic #80).
1584
- *
1585
- * ## What an "edge" is here, and what "writing" it means
1586
- *
1587
- * The read side (`src/relation/traversal.ts`, `src/planner/relation-planner.ts`)
1588
- * resolves a `@hasMany` / `@belongsTo` / `@hasOne` relation by reading an
1589
- * **adjacency entity** — a base-table row whose key is built from the foreign-key
1590
- * fields the relation binds on (the `keyBinding`). In the canonical
1591
- * user-permissions model a `GroupMembershipModel` row IS the edge between a
1592
- * `User` and a `Group`: `Group.members` (`@hasMany`, `{ groupId: 'groupId' }`)
1593
- * reads it on the base PK (`GROUP#{groupId}` / `USER#{userId}`), and
1594
- * `User.groups` (`@hasMany`, `{ userId: 'userId' }`) reads the **same row** on
1595
- * the inverse GSI (`USER#{userId}` / `GROUP#{groupId}`). One physical row, two
1596
- * read directions: the inverse GSI is auto-maintained by DynamoDB from the base
1597
- * row's attributes, so there is nothing extra to write for the second direction.
1598
- *
1599
- * Therefore the **write side of an edge is one base-table row**:
1600
- *
1601
- * - **onCreate** — `Put` that row. Its key (PK/SK) is derived from the adjacency
1602
- * entity's own {@link SegmentedKey}, so the row the writer emits is *exactly*
1603
- * the row the existing read traversal queries: the round-trip is correct **by
1604
- * construction**, not by coincidence.
1605
- * - **onDelete** — `Delete` that same row, keyed by the same PK/SK.
1606
- * - **onUpdateKeyChange** — when a foreign key the edge keys on changes (e.g.
1607
- * `post.userId` moves a Post to a different owner), the edge **moves**: the old
1608
- * row must be `Delete`d and the new row `Put`. Delete/update need the **old**
1609
- * key value, which the single-row create form never carries — see
1610
- * {@link OLD_VALUE_NAMESPACE}.
1611
- *
1612
- * ## Declaration is additive
1613
- *
1614
- * An edge's write side is declared on the adjacency entity (the entity whose row
1615
- * *is* the edge) via {@link edgeWrites}; it references, by `() => TargetModel` +
1616
- * relation property name, the read-side relation(s) the row satisfies. The
1617
- * declaration is **purely additive** — it adds a static `writes` member and
1618
- * touches no field/relation/key metadata — so existing models and generated
1619
- * fixtures stay byte-identical, and the read traversal is unaffected.
1620
- *
1621
- * ## Output is the existing SSoT
1622
- *
1623
- * Derivation produces {@link TransactionItemSpec} items — the *same*
1624
- * JSON-serializable shape `defineTransaction` emits (`src/spec/transaction.ts`)
1625
- * — so a derived edge `Put` / `Delete` slots into a `TransactWriteItems` with no
1626
- * new execution substrate. Wiring these into the mutation compiler / transaction
1627
- * assembly is a later issue (#85); this module is the derivation primitive only.
1628
- */
1629
-
1630
- /**
1631
- * The template namespace for an edge's **old** foreign-key value, used by the
1632
- * `onUpdateKeyChange` Delete(old) item. Where a create/put keys on `{field}`
1633
- * (the post-write value, from the mutation input / written entity), the
1634
- * delete-of-old keys on `{old.field}`. This makes the source of each value
1635
- * unambiguous in the serialized spec and lets a single transaction carry **both**
1636
- * the old key (to delete the stale edge) and the new key (to put the moved edge)
1637
- * without collision.
1638
- *
1639
- * How the old value is obtained is the caller's (the #85 compiler's) concern and
1640
- * is intentionally out of scope here: it is threaded in as a normal transaction
1641
- * param named `old.<field>`, exactly as the existing transaction runtime resolves
1642
- * any `{param}` placeholder (`src/operations/declarative-transaction.ts` /
1643
- * `python/graphddb_runtime/transactions.py`). The proposal's recommended source
1644
- * is the pre-mutation entity image (`$.entity.*` — a read of the row before the
1645
- * write, or the DynamoDB old image); this module only fixes the *token shape* so
1646
- * the two key images never clash.
1647
- */
1648
- declare const OLD_VALUE_NAMESPACE: "old";
1649
- /** The lifecycle phase an edge write effect is derived for. */
1650
- type EdgeLifecycle = 'onCreate' | 'onDelete' | 'onUpdateKeyChange';
1651
- /**
1652
- * One declared edge write effect on an adjacency entity: the read-side relation
1653
- * (on a target model) that this entity's row materializes. The declaration is
1654
- * lifecycle-agnostic — the *same* edge is created on create, deleted on delete,
1655
- * and moved on a foreign-key change; {@link deriveEdgeWriteItems} fans it out
1656
- * into the three lifecycle forms.
1657
- *
1658
- * ## `inverse` — dual-edge synchronization (Epic #118 pattern 9, issue #133)
1659
- *
1660
- * The plain edge above is **one physical row** that serves *both* read directions:
1661
- * the base PK reads one direction and an inverse GSI (auto-maintained by DynamoDB)
1662
- * reads the other, so a bidirectional relation costs a single write. The RFC §1
1663
- * pattern-9 gap is the case where the inverse direction is NOT served by that GSI
1664
- * but by a **second physical row** on its own partition — the genuine *dual edge*
1665
- * (forward + inverse rows). {@link inverse}, when present, names a SECOND adjacency
1666
- * model + relation property whose row is the inverse edge; {@link
1667
- * deriveEdgeWriteItemsFor} then maintains BOTH rows from this single (one-direction)
1668
- * declaration — the create `Put`s both, the delete `Delete`s both, a foreign-key
1669
- * change `Delete`s+`Put`s both — so the two directions can never drift. Omit
1670
- * `inverse` for the historical single-row-plus-GSI bidirectional edge.
1671
- */
1672
- interface EdgeWriteDeclaration {
1673
- /** Resolves the target model whose relation this edge feeds (e.g. `GroupModel`). */
1674
- readonly targetFactory: () => new (...args: unknown[]) => unknown;
1675
- /** The relation property on the target that reads this edge (e.g. `members`). */
1676
- readonly relationProperty: string;
1677
- /**
1678
- * The INVERSE edge to keep in sync (Epic #118 pattern 9 / issue #133, dual-edge).
1679
- * Names the second adjacency model whose own row materializes the reverse-direction
1680
- * relation. When present, every lifecycle derivation maintains the inverse row
1681
- * alongside the forward row in the SAME transaction, so a one-direction declaration
1682
- * keeps a two-row bidirectional edge consistent. Absent = the historical single-row
1683
- * (base + inverse GSI) bidirectional edge.
1684
- */
1685
- readonly inverse?: {
1686
- /** Resolves the inverse adjacency model whose row IS the inverse edge. */
1687
- readonly adjacencyFactory: () => new (...args: unknown[]) => unknown;
1688
- /** Resolves the target model whose relation the inverse row feeds. */
1689
- readonly targetFactory: () => new (...args: unknown[]) => unknown;
1690
- /** The relation property on the inverse target that reads the inverse row. */
1691
- readonly relationProperty: string;
668
+ declare const graphddb: {
669
+ readonly publishQuery: typeof publishQuery;
670
+ readonly publishCommand: typeof publishCommand;
671
+ readonly query: typeof DDBModel.query;
672
+ readonly mutate: typeof DDBModel.mutate;
673
+ readonly prepare: typeof DDBModel.prepare;
674
+ readonly subscribe: typeof DDBModel.subscribe;
675
+ readonly config: {
676
+ readonly client: (client: _aws_sdk_client_dynamodb.DynamoDBClient) => void;
677
+ readonly getClient: () => _aws_sdk_client_dynamodb.DynamoDBClient;
678
+ readonly tables: (mapping: Record<string, string>) => void;
679
+ readonly retry: (policy: RetryPolicy | null) => void;
680
+ readonly use: (mw: Middleware) => void;
681
+ readonly clearMiddleware: () => void;
1692
682
  };
1693
- }
1694
- /**
1695
- * Marker carried by a model's static `writes` member ({@link edgeWrites}), so it
1696
- * can be recognized and read without depending on its declaration order. JSON is
1697
- * never emitted from this object directly — it is the build-time declaration that
1698
- * {@link deriveEdgeWriteItems} consumes.
1699
- */
1700
- declare const EDGE_WRITES_MARKER: unique symbol;
1701
- /** The static `writes` declaration produced by {@link edgeWrites}. */
1702
- interface EdgeWritesDefinition {
1703
- readonly [EDGE_WRITES_MARKER]: true;
1704
- /** The declared edge effects, in declaration order. */
1705
- readonly edges: readonly EdgeWriteDeclaration[];
1706
- }
1707
- /** Runtime guard: is `value` an {@link EdgeWritesDefinition}? */
1708
- declare function isEdgeWritesDefinition(value: unknown): value is EdgeWritesDefinition;
1709
- /**
1710
- * The recorder handed to the {@link edgeWrites} builder. `putEdge` / `deleteEdge`
1711
- * / `updateKeyChangeEdge` all declare the **same** edge (the adjacency row that
1712
- * materializes a target relation); they read identically at the declaration site
1713
- * to the three lifecycle effects the derivation emits, while keeping a single
1714
- * declarative source of truth. Each returns an {@link EdgeWriteDeclaration};
1715
- * collect them into the static `writes` member.
1716
- */
1717
- interface EdgeWriteRecorder {
1718
- /**
1719
- * Declare that creating this entity's row creates the edge read by
1720
- * `target`'s `relationProperty` relation (→ adjacency `Put`).
1721
- */
1722
- putEdge(target: () => new (...args: unknown[]) => unknown, relationProperty: string): EdgeWriteDeclaration;
1723
- /**
1724
- * Declare that deleting this entity's row deletes the edge read by
1725
- * `target`'s `relationProperty` relation (→ adjacency `Delete`).
1726
- */
1727
- deleteEdge(target: () => new (...args: unknown[]) => unknown, relationProperty: string): EdgeWriteDeclaration;
1728
- /**
1729
- * Declare that a foreign-key change moves the edge read by `target`'s
1730
- * `relationProperty` relation (→ adjacency `Delete`(old) + `Put`(new)).
1731
- */
1732
- updateKeyChangeEdge(target: () => new (...args: unknown[]) => unknown, relationProperty: string): EdgeWriteDeclaration;
1733
- /**
1734
- * Declare a **dual edge** (Epic #118 pattern 9 / issue #133): a bidirectional edge
1735
- * maintained as TWO physical rows — a forward row on THIS adjacency entity (read by
1736
- * `forward.target.relationProperty`) and an inverse row on a SECOND adjacency model
1737
- * (read by `inverse.target.relationProperty`). Both rows are written / deleted /
1738
- * moved together from this single declaration, so the two directions stay in sync
1739
- * without an inverse GSI. Use this when the reverse direction lives on its own
1740
- * partition (a second row) rather than a GSI of the forward row.
1741
- *
1742
- * @example
1743
- * ```ts
1744
- * // On the forward adjacency entity (FollowEdge: FOLLOWER#a / FOLLOWING#b):
1745
- * static readonly writes = edgeWrites((w) => [
1746
- * w.dualEdge(
1747
- * { target: () => UserModel, relationProperty: 'following' },
1748
- * { adjacency: () => FollowedByEdgeModel, target: () => UserModel, relationProperty: 'followers' },
1749
- * ),
1750
- * ]);
1751
- * ```
1752
- */
1753
- dualEdge(forward: {
1754
- target: () => new (...args: unknown[]) => unknown;
1755
- relationProperty: string;
1756
- }, inverse: {
1757
- adjacency: () => new (...args: unknown[]) => unknown;
1758
- target: () => new (...args: unknown[]) => unknown;
1759
- relationProperty: string;
1760
- }): EdgeWriteDeclaration;
1761
- }
1762
- /**
1763
- * Declare an adjacency entity's edge write side (issue #82), as the model's
1764
- * reusable save contract. The builder receives an {@link EdgeWriteRecorder} and
1765
- * returns the declared edges; the result is stored on the model as
1766
- * `static readonly writes = edgeWrites((w) => [...])`.
1767
- *
1768
- * The declaration is lifecycle-agnostic — `w.putEdge` / `w.deleteEdge` /
1769
- * `w.updateKeyChangeEdge` all name the same edge; {@link deriveEdgeWriteItems}
1770
- * derives the per-lifecycle item set. Using all three at the declaration site is
1771
- * a readability convenience (the author writes `putEdge` under the create
1772
- * contract, `deleteEdge` under the remove contract); they produce identical
1773
- * declarations and the derivation is what distinguishes the lifecycle.
1774
- *
1775
- * @example
1776
- * ```ts
1777
- * // On the GroupMembership adjacency entity: its row is the User<->Group edge.
1778
- * static readonly writes = edgeWrites((w) => [
1779
- * w.putEdge(() => GroupModel, 'members'),
1780
- * ]);
1781
- * ```
1782
- */
1783
- declare function edgeWrites(builder: (w: EdgeWriteRecorder) => readonly EdgeWriteDeclaration[]): EdgeWritesDefinition;
1784
- /**
1785
- * Derive the lifecycle-distinguished adjacency `Put` / `Delete`
1786
- * {@link TransactionItemSpec} set for **one** declared edge.
1787
- *
1788
- * - `onCreate` → a single `Put` of the adjacency row (item = the edge key fields
1789
- * as `{field}` templates).
1790
- * - `onDelete` → a single `Delete` keyed by the row's PK/SK.
1791
- * - `onUpdateKeyChange` → a `Delete` of the **old** row (keyed in the
1792
- * `{old.<field>}` namespace) followed by a `Put` of the **new** row — the edge
1793
- * moves atomically. The two items are emitted in delete-then-put order so a key
1794
- * that is unchanged for one segment but changed for another never races the new
1795
- * row against the old delete within a transaction.
1796
- *
1797
- * The target relation is round-trip-verified ({@link assertRelationRoundTrips})
1798
- * before any item is produced, so a mis-declared edge fails at build time rather
1799
- * than writing an unreadable row.
1800
- */
1801
- declare function deriveEdgeWriteItemsFor(adjacencyClass: new (...args: unknown[]) => unknown, decl: EdgeWriteDeclaration, lifecycle: EdgeLifecycle): TransactionItemSpec[];
1802
- /**
1803
- * Derive the adjacency item set for **every** edge declared on an entity's
1804
- * `writes` member, for one lifecycle phase, preserving declaration order. This is
1805
- * the entry point a compiler (#85) calls to gather an entity's edge effects when
1806
- * assembling a `TransactWriteItems`.
1807
- *
1808
- * @param adjacencyClass The adjacency model class whose row materializes the
1809
- * edges (carries the `writes` declaration and the key).
1810
- * @param writes The entity's {@link EdgeWritesDefinition} (its static
1811
- * `writes`).
1812
- * @param lifecycle Which lifecycle form to derive (`onCreate` / `onDelete` /
1813
- * `onUpdateKeyChange`).
1814
- */
1815
- declare function deriveEdgeWriteItems(adjacencyClass: new (...args: unknown[]) => unknown, writes: EdgeWritesDefinition, lifecycle: EdgeLifecycle): TransactionItemSpec[];
1816
- /**
1817
- * Read the {@link EdgeWritesDefinition} declared on a model class as its static
1818
- * `writes` member, or `undefined` when the model declares no edge write side.
1819
- * Mirrors how {@link MetadataRegistry} discovers a model's static `keys` — it
1820
- * scans the class's own static members for the marker — so the declaration needs
1821
- * no fixed property name beyond the conventional `writes`.
1822
- */
1823
- declare function getEdgeWrites(modelClass: new (...args: unknown[]) => unknown): EdgeWritesDefinition | undefined;
1824
- /**
1825
- * Convenience over {@link deriveEdgeWriteItems} that reads the model's declared
1826
- * `writes` member itself (via {@link getEdgeWrites}). Throws when the model
1827
- * declares no edge write side, so a caller asking for edges of an undeclared
1828
- * model gets an actionable error rather than an empty set.
1829
- */
1830
- declare function deriveModelEdgeWriteItems(modelClass: new (...args: unknown[]) => unknown, lifecycle: EdgeLifecycle): TransactionItemSpec[];
1831
-
1832
- /**
1833
- * Maintenance **repair / rebuild planner** (Epic #118 Phase 2, issue #131).
1834
- *
1835
- * ## What this is
1836
- *
1837
- * The async drain ({@link import('../cdc/maintenance-drain.js')}, #130) keeps a
1838
- * maintained owner row (a snapshot / collection / counter) in sync INCREMENTALLY —
1839
- * it applies one source event at a time. Over a long-lived table the materialized
1840
- * value can nevertheless **drift** from the source of truth: a dropped / dead-lettered
1841
- * stream event, a maintainer added AFTER source rows already existed (initial
1842
- * un-built state), a bug in an earlier maintainer version, or an out-of-band write.
1843
- *
1844
- * The rebuild planner is the repair for that drift. For a given owner row × maintainer
1845
- * it **re-derives the owner-row value from scratch by scanning every source row that
1846
- * belongs to that owner**, using the SAME projection / aggregation / ordering rules the
1847
- * drain uses (shared via `maintenance-projection.ts` and this module), then:
1848
- *
1849
- * - **{@link detectDrift}** — compares the re-derived value against the value currently
1850
- * stored on the owner row and reports whether (and how) they differ; or
1851
- * - **{@link rebuild}** — writes the re-derived ABSOLUTE value onto the owner row, so the
1852
- * materialized data converges to exactly what the source says.
1853
- *
1854
- * After a rebuild the owner row equals what an incremental drain would have converged to
1855
- * had it never missed an event — the AC: "rebuild で materialized が source と一致する".
1856
- *
1857
- * ## Granularity: per owner row × maintainer
1858
- *
1859
- * Drift is detected and repaired at the **owner-row × maintainer** grain — the natural
1860
- * unit, because one owner row's materialized value is a pure function of the set of
1861
- * source rows bound to it. The caller names the owner rows to repair (their key inputs);
1862
- * the planner discovers the source rows for each by querying the source on the maintenance
1863
- * key binding (the same binding the live `hasMany` / `@aggregate` read uses, inverted:
1864
- * the owner row supplies the binding value, the source partition is scanned for matches).
1865
- *
1866
- * ## count is re-derived as an ABSOLUTE SET, not an ADD
1867
- *
1868
- * The maintained counter applies a signed `ADD` per event (#141 / #130). A rebuild
1869
- * instead **counts the source rows and SETs the absolute value** — it does not trust the
1870
- * accumulated counter, it recomputes it. This is the whole point of repair (an ADD that
1871
- * has drifted cannot be corrected by another ADD). The `max(field)` counter is re-derived
1872
- * as the maximum over the scanned source rows and SET unconditionally (the rebuild is the
1873
- * authority, so it does not use the drain's advance-only conditional guard).
1874
- *
1875
- * ## Execution form (production positioning)
1876
- *
1877
- * Like the drain applier, this is a **TS-side planner/executor** intended for dev/test and
1878
- * **operational repair tooling** — run on-demand (a "repair this thread's summary now"
1879
- * admin action) or as a periodic batch (a scheduled reconciliation sweep over a set of
1880
- * owner rows). It is NOT injected into the SSoT spec and emits no generated artifact: a
1881
- * rebuild is a host-side reconciliation against live DynamoDB, not part of a model's
1882
- * compiled write contract — so there is no Python mirror (the Python runtime executes the
1883
- * generated contract; rebuild is not in it). It reuses the maintenance graph (#124) to
1884
- * resolve which maintainers exist and the shared re-derivation rules so a rebuilt value is
1885
- * byte-consistent with a drained one.
1886
- */
1887
-
1888
- /** A model class (or `ModelStatic`) the rebuild planner operates over. */
1889
- type ModelLike = Function | ModelStatic<DDBModel>;
1890
- /** Options for {@link createMaintenanceRebuilder}. */
1891
- interface MaintenanceRebuildOptions {
1892
- /**
1893
- * The model classes whose maintainers this rebuilder can repair. The rebuilder
1894
- * builds a scoped {@link MaintenanceGraph} over them to resolve each
1895
- * owner-row × maintainer back to its declared effect. Defaults to the GLOBAL
1896
- * registry when omitted.
1897
- */
1898
- readonly models?: readonly ModelLike[];
1899
- /**
1900
- * The {@link ViewDefinition}s (`defineView`) whose maintainers this rebuilder can
1901
- * repair (issue #132). The view model classes are added to the scoped registry so a
1902
- * view row can be re-derived from its source rows, exactly like a relation owner row.
1903
- */
1904
- readonly views?: readonly ViewDefinition[];
1905
- }
1906
- /**
1907
- * The drift verdict for one owner row × maintainer: whether the currently-stored
1908
- * materialized value differs from the value re-derived from the source rows, and both
1909
- * values (for diagnostics / a repair preview). `expected` is the re-derived authority;
1910
- * `actual` is what the owner row currently holds (`undefined` when never built).
1911
- */
1912
- interface DriftReport {
1913
- /** The relation / aggregate property the maintained value lives under. */
1914
- readonly relationProperty: string;
1915
- /** The owner entity (logical name) the row belongs to. */
1916
- readonly ownerEntity: string;
1917
- /** The owner row's resolved key (`{ PK, SK }`). */
1918
- readonly ownerKey: Record<string, unknown>;
1919
- /** The maintained attribute (collection field / snapshot attrs / counter attribute). */
1920
- readonly kind: MaintainEffect['kind'];
1921
- /** True when `actual` differs from `expected` (drift present). */
1922
- readonly drifted: boolean;
1923
- /** The value re-derived from the source rows (the authority). */
1924
- readonly expected: unknown;
1925
- /** The value currently stored on the owner row (`undefined` when unbuilt). */
1926
- readonly actual: unknown;
1927
- }
1928
- /** The outcome of a {@link MaintenanceRebuilder.rebuild} over one owner row × maintainer. */
1929
- interface RebuildResult extends DriftReport {
1930
- /** True when the owner row was written (drift was present and repaired). */
1931
- readonly repaired: boolean;
1932
- }
1933
- /**
1934
- * A maintenance **repair / rebuild planner** (#131). Construct via
1935
- * {@link createMaintenanceRebuilder}, then call {@link detectDrift} to compare or
1936
- * {@link rebuild} to repair one owner row × maintainer (resolved by the owner model +
1937
- * the relation/aggregate property + the owner key inputs).
1938
- */
1939
- declare class MaintenanceRebuilder {
1940
- private readonly graph;
1941
- constructor(opts?: MaintenanceRebuildOptions);
1942
- /**
1943
- * Re-derive the materialized value for one owner row × maintainer from the source
1944
- * rows and report whether it differs from what the owner row currently stores. Does
1945
- * NOT write anything (a read-only drift probe).
1946
- *
1947
- * @param owner The owner model class declaring the maintainer.
1948
- * @param relationProperty The relation / `@aggregate` property the value lives under.
1949
- * @param ownerKeyInput The owner row's key input (e.g. `{ threadId: 't1' }`).
1950
- */
1951
- detectDrift(owner: ModelLike, relationProperty: string, ownerKeyInput: Record<string, unknown>): Promise<DriftReport>;
1952
- /**
1953
- * Re-derive the materialized value for one owner row × maintainer from the source
1954
- * rows and WRITE the absolute re-derived value onto the owner row when it drifts (or
1955
- * always, when `force`). Returns the drift verdict plus whether a repair write ran.
1956
- *
1957
- * @param options.force Write even when no drift is detected (default: write only on drift).
1958
- */
1959
- rebuild(owner: ModelLike, relationProperty: string, ownerKeyInput: Record<string, unknown>, options?: {
1960
- force?: boolean;
1961
- }): Promise<RebuildResult>;
1962
- /** Resolve an owner model + property to its {@link MaintainItem} + owner-key builder. */
1963
- private resolve;
1964
- /**
1965
- * Scan every source row bound to this owner row and re-derive the maintained value:
1966
- *
1967
- * - **collection** — project each source row, order + trim by the SAME read-side
1968
- * rules (`orderBy` / `orderDir` / `maxItems`) the drain uses, return the list;
1969
- * - **snapshot** — project the single bound source row (or `undefined` when none);
1970
- * - **counter `count`** — the COUNT of source rows (absolute, not an ADD);
1971
- * - **counter `max`** — the maximum source value of the tracked field.
1972
- */
1973
- private recompute;
1974
- /**
1975
- * The source rows bound to one owner row. The maintenance key binding is
1976
- * `{ ownerField: '$.entity.<sourceField>' }` (the destination owner-key field ← the
1977
- * source row's field). INVERTED for a rebuild: the owner row supplies the value, and
1978
- * the source partition is queried for rows whose `<sourceField>` matches — exactly the
1979
- * set the live `hasMany` / `@aggregate` read aggregates. Queries the source via the
1980
- * same {@link plan} the read planner uses (a partition Query), paginating fully.
1981
- */
1982
- private scanSourceRows;
1983
- /** Re-derive a bounded collection: project, then order + trim by the read-side rules. */
1984
- private recomputeCollection;
1985
- /**
1986
- * Re-derive a single-row snapshot. A snapshot mirrors ONE source row (a `belongsTo` /
1987
- * `hasOne`, normally 1:1). In the unusual case where MORE THAN ONE source row binds, the
1988
- * live single-value read takes the FIRST row in the source's natural query order
1989
- * (`ScanIndexForward`, default ASC = the smallest sort-key leaf), so re-derive the same
1990
- * row — {@link pickFirst} — for a verdict consistent with the live read. Returns
1991
- * `undefined` when no source row is bound (the snapshot should be absent).
1992
- */
1993
- private recomputeSnapshot;
1994
- /** The source entity metadata a maintain effect projects from (its relation target). */
1995
- private sourceMetaOf;
1996
- /** Re-derive a counter: the absolute COUNT of source rows, or the MAX of the tracked field. */
1997
- private recomputeCounter;
1998
- /** Read the owner row (consistent) for the drift comparison / actual value. */
1999
- private readOwnerRow;
2000
- /** The value currently stored on the owner row for this maintainer. */
2001
- private actualValue;
2002
- /** Write the re-derived ABSOLUTE value onto the owner row (the repair). */
2003
- private writeValue;
2004
- }
2005
- /** Create a {@link MaintenanceRebuilder} (issue #131). */
2006
- declare function createMaintenanceRebuilder(opts?: MaintenanceRebuildOptions): MaintenanceRebuilder;
2007
-
2008
- /**
2009
- * Type-level machinery for the parameterized definition DSL (issue #41).
2010
- *
2011
- * Two jobs:
2012
- *
2013
- * 1. {@link Parameterize} — relaxes a real key / changes / item input type so
2014
- * each scalar leaf additionally accepts a {@link Param} placeholder of the
2015
- * matching value type, while **field names and value types stay checked**
2016
- * against the underlying model (so the existing key boundary / `EntityInput`
2017
- * typing keeps biting at definition time).
2018
- *
2019
- * 2. {@link CollectParams} — derives the `name → ParamDescriptor` map from a
2020
- * supplied parameterized structure, preserving each placeholder's value type
2021
- * (`string` / `number` / literal union) in the descriptor.
2022
- */
2023
-
2024
- /** Scalar leaf types that may be replaced by a {@link Param} placeholder. */
2025
- type Replaceable = string | number;
2026
- /** `null` / `undefined` — peeled off an optional leaf before the scalar test. */
2027
- type Nullish = null | undefined;
2028
- /**
2029
- * Relaxes an input type `K` so each scalar leaf additionally accepts a
2030
- * {@link Param} standing in for that leaf's value type. Non-scalar leaves and
2031
- * field names are left exactly as declared, so excess-property / wrong-type /
2032
- * wrong-name errors from the underlying key (or `EntityInput`) type still fire.
2033
- *
2034
- * A leaf of type `V` becomes `V | Param<V>`; a `string` literal field that is a
2035
- * fixed discriminator (no param) is still assignable as its literal.
2036
- *
2037
- * The scalar test is written with a **tuple wrap** (`[…] extends […]`) so it does
2038
- * **not** distribute over a literal-union leaf. A field typed `'a' | 'b'`
2039
- * therefore yields the whole-union placeholder `Param<'a' | 'b'>` (what
2040
- * `param.literal('a','b')` produces) rather than the distributed
2041
- * `Param<'a'> | Param<'b'>`, which rejected the multi-value literal param (#246).
2042
- * Because `Param` is covariant in its value type, a single-member placeholder
2043
- * (`param.literal('a')` → `Param<'a'>`) still fits, while a placeholder carrying a
2044
- * value **outside** the union (`Param<'a' | 'x'>`), a wider-typed placeholder
2045
- * (`Param<string>` / `Param<number>` on a literal field), or a plain literal not
2046
- * in the union stays a type error.
2047
- *
2048
- * `null` / `undefined` are peeled off first (via `Exclude`/`Extract`) and carried
2049
- * through verbatim, so an **optional** leaf (`Partial<EntityInput>` in
2050
- * `defineUpdate` changes) keeps accepting its placeholder — the tuple wrap alone
2051
- * would otherwise send `string | undefined` down the object arm and drop the
2052
- * placeholder.
2053
- */
2054
- type Parameterize<K> = [Exclude<K, Nullish>] extends [Replaceable] ? [Exclude<K, Nullish>] extends [never] ? K : Exclude<K, Nullish> | Param<Exclude<K, Nullish>> | Extract<K, Nullish> : K extends object ? {
2055
- [P in keyof K]: Parameterize<K[P]>;
2056
- } : K;
2057
- /**
2058
- * Call-site excess-property guard for a parameterized structure (the key /
2059
- * item / changes passed to a `define*` entry point).
2060
- *
2061
- * The entry points infer the structure as a bare `const` type parameter
2062
- * (`const K extends Parameterize<Real>`). For a bare type parameter TypeScript
2063
- * checks the constraint **structurally only** — there is no object-literal
2064
- * *freshness* (excess-property) check, and `Real` (key inputs / `EntityInput`)
2065
- * has all-optional-style members in several positions, so a stray key like
2066
- * `bogus` slips through. This mirrors the exact hole `StrictSelect` (#37) closes
2067
- * for `select`.
2068
- *
2069
- * {@link ExactParam} maps every key of the inferred `K` that is **not** a known
2070
- * key of the (possibly union) `Real` input to `never`. Applied as
2071
- * `key: K & ExactParam<Real, K>`, a stray key forces that property's type to
2072
- * `never`, which the supplied value is not assignable to — a compile error.
2073
- * Known keys keep their inferred value type, so {@link CollectParams} inference
2074
- * is unaffected. Recurses into nested objects (embedded inputs).
2075
- *
2076
- * @typeParam Real - The underlying (non-parameterized) input type.
2077
- * @typeParam K - The inferred parameterized structure (per call).
2078
- */
2079
- type ExactParam<Real, K> = Real extends object ? {
2080
- [P in keyof K]: P extends KnownKeyOf<Real> ? K[P] extends Param<unknown> ? K[P] : K[P] extends object ? K[P] & ExactParam<NonNullableMember<Real, P>, K[P]> : K[P] : never;
2081
- } : K;
2082
- /** Union of all keys across every member of a (possibly union) `Real` type. */
2083
- type KnownKeyOf<Real> = Real extends object ? keyof Real : never;
2084
- /**
2085
- * The value type of key `P` across the members of a (possibly union) `Real`,
2086
- * with `null` / `undefined` stripped so the recursion targets the object shape.
2087
- */
2088
- type NonNullableMember<Real, P extends PropertyKey> = NonNullable<Real extends object ? (P extends keyof Real ? Real[P] : never) : never>;
2089
- /**
2090
- * Extracts the value type a {@link Param} represents, or `never` for non-params.
2091
- */
2092
- type ParamValueOf<X> = X extends Param<infer V> ? V : never;
2093
- /**
2094
- * Builds the `ParamDescriptor` (value type preserved) for a {@link Param}.
2095
- * Mirrors the runtime descriptor shape produced by `collectParams`.
2096
- */
2097
- type DescriptorOf<V> = ParamDescriptor<V>;
2098
- /**
2099
- * Walks a supplied parameterized structure `K` and collects every
2100
- * {@link Param} placeholder into a flat `name → ParamDescriptor` map. The
2101
- * **object key** that holds a placeholder becomes the parameter name; the
2102
- * placeholder's value type is preserved in the descriptor.
2103
- *
2104
- * Nested objects are flattened (a placeholder at any depth contributes its
2105
- * immediate holding key). Concrete scalar leaves contribute nothing.
2106
- */
2107
- type CollectParams<K> = K extends object ? {
2108
- [P in keyof K as [ParamLeavesOf<K[P]>] extends [never] ? never : P]: K[P] extends Param<unknown> ? DescriptorOf<ParamValueOf<K[P]>> : DescriptorOf<ParamLeavesOf<K[P]>>;
2109
- } extends infer M ? {
2110
- -readonly [P in keyof M]-?: M[P];
2111
- } : never : Record<never, ParamDescriptor>;
2112
- /**
2113
- * Union of the value types of every {@link Param} reachable in `X` (itself or
2114
- * nested). `never` when `X` contains no placeholders.
2115
- */
2116
- type ParamLeavesOf<X> = X extends Param<infer V> ? V : X extends object ? {
2117
- [P in keyof X]: ParamLeavesOf<X[P]>;
2118
- }[keyof X] : never;
2119
-
2120
- /**
2121
- * Parameterized query / command definition DSL (issue #41, Python-bridge
2122
- * Phase 0a).
2123
- *
2124
- * ## API shape (deviation from the proposal, by design)
2125
- *
2126
- * The proposal sketches `User.query({ email: param.string() }, {...})`. That
2127
- * record cannot be adopted without **polluting the live `Model.query` runtime
2128
- * type** to accept {@link Param} placeholders at key/value positions — exactly
2129
- * what issue #41 forbids ("must not pollute the runtime `Model.query` type with params"). So
2130
- * this module provides **dedicated definition entry points** instead:
2131
- *
2132
- * - `defineQuery(Model, key, select)`
2133
- * - `defineList(Model, key, select)`
2134
- * - `definePut(Model, item)`
2135
- * - `defineUpdate(Model, key, changes)`
2136
- * - `defineDelete(Model, key)`
2137
- *
2138
- * These accept {@link Param} placeholders at scalar leaves while keeping field
2139
- * names / value types / the key boundary / strict-select checked at definition
2140
- * time (via {@link Parameterize} layered on the model's real key & select
2141
- * types). `Model.query` / `Model.putItem` / … are **untouched**.
2142
- *
2143
- * `defineQueries({...})` / `defineCommands({...})` take a record of these
2144
- * definition nodes and return the same record (typed), forming the IR the
2145
- * static planner (#42) consumes.
2146
- */
2147
-
2148
- /**
2149
- * The model-class constructor type carried by a `ModelStatic<T, C>`. Mirrors the
2150
- * pattern used by `model/types.ts` to recover precise key inference from the
2151
- * model passed to a `define*` entry point.
2152
- */
2153
- type AnyModelClass = abstract new (...args: any[]) => DDBModel;
2154
- /** Recovers the constructor type `C` from a `ModelStatic<T, C>` argument. */
2155
- type ClassOf<M> = M extends ModelStatic<infer _T, infer C> ? C : unknown;
2156
- /** Recovers the entity type `T` from a `ModelStatic<T, C>` argument. */
2157
- type EntityOf<M> = M extends ModelStatic<infer T, infer _C> ? T : never;
2158
- /**
2159
- * Define a parameterized **single-item read** (`query`). The `key` accepts
2160
- * {@link Param} placeholders at scalar leaves, but is otherwise checked against
2161
- * the model's unique-key boundary (`UniqueQueryKeyOf`); `select` is the strict
2162
- * projection (#37) typed against the entity.
2163
- */
2164
- declare function defineQuery<M extends ModelStatic<DDBModel, AnyModelClass>, const K extends Parameterize<UniqueQueryKeyOf<ClassOf<M>>>, const S extends SelectableOf<EntityOf<M>>>(model: M, key: K & ExactParam<UniqueQueryKeyOf<ClassOf<M>>, K>, select: S & StrictSelectSpec<EntityOf<M>, S>, options?: ReadDefinitionOptions): OperationDefinition<EntityOf<M>, 'query', K, S, undefined, CollectParams<K>>;
2165
- /**
2166
- * Define a parameterized **partition read** (`list`). The `key` accepts a
2167
- * partial (partition-key) input with {@link Param} placeholders; `select` is
2168
- * the strict projection typed against the entity.
2169
- */
2170
- declare function defineList<M extends ModelStatic<DDBModel, AnyModelClass>, const K extends Parameterize<PartialQueryKeyOf<ClassOf<M>>>, const S extends SelectableOf<EntityOf<M>>>(model: M, key: K & ExactParam<PartialQueryKeyOf<ClassOf<M>>, K>, select: S & StrictSelectSpec<EntityOf<M>, S>, options?: ReadDefinitionOptions): OperationDefinition<EntityOf<M>, 'list', K, S, undefined, CollectParams<K>>;
2171
- /**
2172
- * Define a parameterized **put** (`put`). The `item` accepts {@link Param}
2173
- * placeholders at scalar leaves and is otherwise checked against `EntityInput`
2174
- * (relations excluded, scalar/embedded field types enforced).
2175
- */
2176
- declare function definePut<M extends ModelStatic<DDBModel, AnyModelClass>, const I extends Parameterize<EntityInput<EntityOf<M>>>>(model: M, item: I & ExactParam<EntityInput<EntityOf<M>>, I>, options?: WriteDefinitionOptions): OperationDefinition<EntityOf<M>, 'put', I, undefined, undefined, CollectParams<I>>;
2177
- /**
2178
- * Define a parameterized **update** (`update`). Both `key` (PK) and `changes`
2179
- * (a partial of the entity) accept {@link Param} placeholders at scalar leaves;
2180
- * field names / value types are checked against the entity. Params from both
2181
- * structures are merged into the definition's `params` map.
2182
- */
2183
- declare function defineUpdate<M extends ModelStatic<DDBModel, AnyModelClass>, const K extends Parameterize<PrimaryKeyOf<ClassOf<M>>>, const Ch extends Parameterize<Partial<EntityInput<EntityOf<M>>>>>(model: M, key: K & ExactParam<PrimaryKeyOf<ClassOf<M>>, K>, changes: Ch & ExactParam<Partial<EntityInput<EntityOf<M>>>, Ch>, options?: WriteDefinitionOptions): OperationDefinition<EntityOf<M>, 'update', K, undefined, Ch, CollectParams<K & Ch>>;
2184
- /**
2185
- * Define a parameterized **delete** (`delete`). The `key` (PK) accepts
2186
- * {@link Param} placeholders at scalar leaves and is otherwise checked against
2187
- * the model's primary-key type.
2188
- */
2189
- declare function defineDelete<M extends ModelStatic<DDBModel, AnyModelClass>, const K extends Parameterize<PrimaryKeyOf<ClassOf<M>>>>(model: M, key: K & ExactParam<PrimaryKeyOf<ClassOf<M>>, K>, options?: WriteDefinitionOptions): OperationDefinition<EntityOf<M>, 'delete', K, undefined, undefined, CollectParams<K>>;
2190
- /**
2191
- * Group a record of **read** definitions (`defineQuery` / `defineList`) into the
2192
- * query IR consumed by the static planner (#42). The argument is validated to
2193
- * contain only read operations; the returned map preserves each definition's
2194
- * precise type (params, entity, select).
2195
- */
2196
- declare function defineQueries<const D extends DefinitionMap>(definitions: D & ReadOnlyDefinitions<D>): D;
2197
- /**
2198
- * Group a record of **write** definitions (`definePut` / `defineUpdate` /
2199
- * `defineDelete`) into the command IR consumed by the static planner (#42). The
2200
- * argument is validated to contain only write operations.
2201
- */
2202
- declare function defineCommands<const D extends DefinitionMap>(definitions: D & WriteOnlyDefinitions<D>): D;
2203
- /** Constrains every entry of `D` to be a read (`query` / `list`) definition. */
2204
- type ReadOnlyDefinitions<D extends DefinitionMap> = {
2205
- [K in keyof D]: D[K] extends OperationDefinition<infer _T, infer Op, infer _K, infer _S, infer _Ch, infer _P> ? Op extends 'query' | 'list' ? D[K] : OperationDefinition<DDBModel, 'query' | 'list', unknown, unknown, unknown, Record<string, ParamDescriptor>> : never;
2206
- };
2207
- /** Constrains every entry of `D` to be a write (`put`/`update`/`delete`) definition. */
2208
- type WriteOnlyDefinitions<D extends DefinitionMap> = {
2209
- [K in keyof D]: D[K] extends OperationDefinition<infer _T, infer Op, infer _K, infer _S, infer _Ch, infer _P> ? Op extends 'put' | 'update' | 'delete' ? D[K] : OperationDefinition<DDBModel, 'put' | 'update' | 'delete', unknown, unknown, unknown, Record<string, ParamDescriptor>> : never;
2210
683
  };
2211
684
 
2212
- export { AggregateOptions, AggregateValue, AotPreparedWriteStatement, BATCH_GET_MAX_KEYS, BATCH_WRITE_MAX_ITEMS, BatchExecOptions, BatchGetExecInput, BatchWriteExecItem, ClientManager, type CollectParams, type ConditionExpressionResult, CtxModel, DDBModel, DefinitionMap, DeleteInput, type DriftReport, DynamoDBOperation, DynamoExecutor, DynamoType, EDGE_WRITES_MARKER, type EdgeLifecycle, type EdgeWriteDeclaration, type EdgeWriteRecorder, type EdgeWritesDefinition, EntityMetadata, ExecutionPlan, Executor, ExecutorResult, type ExplainInput, FieldMetadata, FieldOptions, type FilterExpressionResult, GsiDefinition, type HasManyVersionedCallback, type HasManyVersionedOptions, type HasOneVersionedCallback, type HasOneVersionedOptions, Item, KeyDefinition, type ListInput, type ListOptions, MAX_TRANSACT_ITEMS, MaintainConsistency, MaintainEffect, MaintainEvent, MaintainUpdateMode, type MaintainedFromCallback, type MaintainedFromOptions, type MaintenanceRebuildOptions, MaintenanceRebuilder, Manifest, MembershipPredicate, MembershipPredicateOp, Middleware, type ModelOptions, ModelStatic, OLD_VALUE_NAMESPACE, OperationDefinition, ParallelOpResult, Param, ParamDescriptor, type Parameterize, PartialQueryKeyOf, type PerKeyCursorEnvelope, type PlanInput, PreparedBody, PreparedPlanDocument, PreparedStatement, PreparedWriteExecOptions, PreparedWriteOpSpec, PrimaryKeyOf, type ProjectionResult, ProjectionTransform, type ProjectionValue, PutInput, type QueryOptions$1 as QueryOptions, RawCondition, ReadExecOptions, ReadParams, ReadRequestCtx, ReadRequestKind, type RebuildResult, type RefsOptions, RelationMetadata, RelationOptions, type RelationTraversalOptions, RequestContext, ResolvedKey, RetryOverride, RetryPolicy, RetryingExecutor, SelectableOf, type SelfProxy, type SelfRef, type SourceProxy, type SourceRef, TableMapping, TransactWriteExecItem, TransactionItemSpec, TransactionSpec, UniqueQueryKeyOf, type UpdateExpressionResult, UpdateInput, type VersionedHistoryOptions, type VersionedLatestOptions, ViewDefinition, WriteDefinitionOptions, WriteExecOptions, WriteResult, aggregate, belongsTo, binary, boolean, buildConditionExpression, buildProjection, buildUpdateExpression, cdcProjected, compileFilterExpression, count, createMaintenanceRebuilder, datetime, decodeCursor, decodePerKeyCursor, defineCommands, defineDelete, defineList, definePut, defineQueries, defineQuery, defineUpdate, deriveEdgeWriteItems, deriveEdgeWriteItemsFor, deriveModelEdgeWriteItems, derivePrefix, detectRelationFields, edgeWrites, embedded, encodeCursor, encodePerKeyCursor, evaluateFilter, execute, executeDeclarativeTransaction, executeExplain, executeList, executeQuery, expandTransaction, field, getEdgeWrites, getImplicitKeyFields, hasMany, hasOne, hydrate, isEdgeWritesDefinition, isSelectBuilder, list, literal, loadPreparedPlan, maintainedFrom, map, max, model, number, numberSet, plan, refs, resolveKey, resolveRelations, serializeContractKey, serializeFieldValue, string, stringSet, ttl, validateDepth, validateGsiAmbiguity, whenMember };
685
+ export { DDBModel, type HasManyVersionedCallback, type HasManyVersionedOptions, type HasOneVersionedCallback, type HasOneVersionedOptions, type ListOptions, type MaintainedFromCallback, type MaintainedFromOptions, Middleware, type ModelOptions, PrimaryKeyOf, type ProjectionValue, type QueryOptions, type RefsOptions, SelectableOf, type SelfProxy, type SelfRef, type SourceProxy, type SourceRef, type VersionedHistoryOptions, type VersionedLatestOptions, aggregate, belongsTo, binary, boolean, cdcProjected, count, datetime, embedded, field, graphddb, hasMany, hasOne, list, literal, maintainedFrom, map, max, model, number, numberSet, refs, string, stringSet, ttl, whenMember };