graphddb 0.7.9 → 0.8.0

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