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