graphddb 0.1.1 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4607 @@
1
+ import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
2
+
3
+ interface RangeCondition {
4
+ operator: 'begins_with';
5
+ key: string;
6
+ value: unknown;
7
+ }
8
+ interface DynamoDBOperationBase {
9
+ tableName: string;
10
+ projectionExpression?: string;
11
+ expressionAttributeNames?: Record<string, string>;
12
+ consistentRead?: boolean;
13
+ /**
14
+ * Server-side DynamoDB FilterExpression (independent of the
15
+ * KeyConditionExpression and ProjectionExpression). Names/values here are
16
+ * merged into the command's ExpressionAttributeNames/Values by the executor.
17
+ */
18
+ filterExpression?: string;
19
+ filterExpressionAttributeNames?: Record<string, string>;
20
+ filterExpressionAttributeValues?: Record<string, unknown>;
21
+ }
22
+ interface GetItemOperation extends DynamoDBOperationBase {
23
+ type: 'GetItem';
24
+ keyCondition: Record<string, unknown>;
25
+ }
26
+ interface QueryOperation extends DynamoDBOperationBase {
27
+ type: 'Query';
28
+ indexName?: string;
29
+ keyCondition: Record<string, unknown>;
30
+ rangeCondition?: RangeCondition;
31
+ scanIndexForward?: boolean;
32
+ limit?: number;
33
+ exclusiveStartKey?: Record<string, unknown>;
34
+ }
35
+ interface BatchGetItemOperation extends DynamoDBOperationBase {
36
+ type: 'BatchGetItem';
37
+ keys: Record<string, unknown>[];
38
+ }
39
+ type DynamoDBOperation = GetItemOperation | QueryOperation | BatchGetItemOperation;
40
+ interface ExecutionPlan {
41
+ operations: DynamoDBOperation[];
42
+ }
43
+ type ResolvedKey = {
44
+ type: 'pk';
45
+ partial: boolean;
46
+ inputFieldNames: string[];
47
+ } | {
48
+ type: 'gsi';
49
+ indexName: string;
50
+ unique: boolean;
51
+ partial: boolean;
52
+ inputFieldNames: string[];
53
+ };
54
+
55
+ /**
56
+ * Parameter placeholders for the parameterized query / command definition DSL
57
+ * (issue #41, Python-bridge Phase 0a).
58
+ *
59
+ * A `Param<T>` is a **branded placeholder** standing in for a value that is not
60
+ * known at definition time but is supplied later (at execution, e.g. from
61
+ * Python). It carries:
62
+ *
63
+ * - the **TypeScript value type** `T` it represents (`string`, `number`, or a
64
+ * string-literal union), preserved through the IR and the inferred return
65
+ * types of `defineQueries` / `defineCommands`; and
66
+ * - a **runtime descriptor** (`kind` + optional `literals`) that the static
67
+ * planner (#42) and the code generator read to emit `operations.json`.
68
+ *
69
+ * Placeholders are created with `param.string()`, `param.number()`, and
70
+ * `param.literal(...)`. They are *only* legal at value positions inside a
71
+ * parameterized key / changes structure passed to the `define*` entry points —
72
+ * never to the live `Model.query` / `Model.putItem` runtime API, whose types remain
73
+ * uncontaminated by params.
74
+ */
75
+ /** Brand tag preventing a plain object from structurally matching a {@link Param}. */
76
+ declare const PARAM_BRAND: unique symbol;
77
+ /** Runtime discriminant for a parameter placeholder. */
78
+ type ParamKind = 'string' | 'number' | 'literal' | 'array';
79
+ /**
80
+ * A branded parameter placeholder representing a value of TypeScript type `T`
81
+ * that is bound at execution time rather than definition time.
82
+ *
83
+ * The brand (`[PARAM_BRAND]: T`) keeps `Param<string>` and `Param<number>`
84
+ * distinct and prevents a bare value from being mistaken for a placeholder.
85
+ *
86
+ * @typeParam T - The value type this placeholder stands in for.
87
+ */
88
+ interface Param<out T> {
89
+ /** @internal Type brand carrying the represented value type. */
90
+ readonly [PARAM_BRAND]: T;
91
+ /** Runtime discriminant: `'string'` | `'number'` | `'literal'` | `'array'`. */
92
+ readonly kind: ParamKind;
93
+ /**
94
+ * For `param.literal(...)`, the allowed literal values (preserved at runtime
95
+ * for the generator). `undefined` for `string` / `number`.
96
+ */
97
+ readonly literals?: readonly T[];
98
+ /**
99
+ * For `param.array({...})`, the descriptor of each element's fields. Lets the
100
+ * transaction planner (#46) type `forEach` element references and emit the
101
+ * `{item.<field>}` templates. `undefined` for scalar params.
102
+ */
103
+ readonly element?: Readonly<Record<string, Param<unknown>>>;
104
+ }
105
+ /** A `Param` whose represented value type is `string`. */
106
+ type StringParam = Param<string>;
107
+ /** A `Param` whose represented value type is `number`. */
108
+ type NumberParam = Param<number>;
109
+ /** A `Param` whose represented value type is the literal union `L`. */
110
+ type LiteralParam<L extends string | number> = Param<L>;
111
+ /** The element-field descriptor shape accepted by {@link param.array}. */
112
+ type ArrayElementShape = Record<string, Param<unknown>>;
113
+ /**
114
+ * The TypeScript element type implied by an {@link ArrayElementShape} `E`: each
115
+ * field carries the value type its placeholder represents.
116
+ */
117
+ type ElementOf<E extends ArrayElementShape> = {
118
+ [K in keyof E]: E[K] extends Param<infer V> ? V : never;
119
+ };
120
+ /** A `Param` standing in for an **array** whose elements have shape `E`. */
121
+ type ArrayParam<E extends ArrayElementShape> = Param<ElementOf<E>[]> & {
122
+ readonly element: E;
123
+ };
124
+ /** Allowed scalar value types a placeholder may stand in for. */
125
+ type ParamValue = string | number;
126
+ /**
127
+ * Placeholder factory. Each call returns a branded {@link Param} carrying both
128
+ * the TypeScript value type and a runtime descriptor.
129
+ */
130
+ declare const param: {
131
+ /** A placeholder for a `string` value. */
132
+ readonly string: () => StringParam;
133
+ /** A placeholder for a `number` value. */
134
+ readonly number: () => NumberParam;
135
+ /**
136
+ * A placeholder constrained to one of the given literal values. The value
137
+ * type of the resulting {@link Param} is the **union of the literals**, so it
138
+ * is preserved precisely in the IR and the inferred definition types.
139
+ *
140
+ * @example
141
+ * ```ts
142
+ * param.literal('active', 'disabled'); // Param<'active' | 'disabled'>
143
+ * ```
144
+ */
145
+ readonly literal: <const L extends readonly [ParamValue, ...ParamValue[]]>(...values: L) => LiteralParam<L[number]>;
146
+ /**
147
+ * A placeholder for an **array** parameter whose elements have the given field
148
+ * shape. Used by `defineTransaction`'s `tx.forEach(p.<arrayParam>, …)` to bind
149
+ * each element's fields to `{item.<field>}` templates.
150
+ *
151
+ * @example
152
+ * ```ts
153
+ * param.array({ userId: param.string(), role: param.string() });
154
+ * // Param<{ userId: string; role: string }[]>
155
+ * ```
156
+ */
157
+ readonly array: <const E extends ArrayElementShape>(element: E) => ArrayParam<E>;
158
+ };
159
+ /** Runtime type guard: is `value` a parameter placeholder? */
160
+ declare function isParam(value: unknown): value is Param<unknown>;
161
+
162
+ /**
163
+ * Internal brand identifying a {@link RawCondition} produced by {@link cond}.
164
+ */
165
+ declare const RAW_COND: unique symbol;
166
+ /**
167
+ * A compiled raw filter fragment produced by the {@link cond} escape hatch.
168
+ * Column references are pre-resolved to `#`-aliased placeholders and embedded
169
+ * values to `:`-aliased parameters; the executor merges these alongside the
170
+ * declarative filter's names/values.
171
+ *
172
+ * @typeParam M - The owning model brand. A `cond` is only assignable to a
173
+ * `FilterInput<M>` of the same model, so embedding a different model's column
174
+ * is a compile error.
175
+ */
176
+ interface RawCondition<M = unknown> {
177
+ readonly [RAW_COND]: true;
178
+ /** @internal phantom — owning-model brand. */
179
+ readonly __model?: M;
180
+ /** Expression fragment with `{name}` / `{value}` markers already inlined. */
181
+ readonly template: TemplateStringsArray;
182
+ /** The interpolated parts, each either a Column ref or an embedded value. */
183
+ readonly parts: unknown[];
184
+ }
185
+ /**
186
+ * The interpolation slot type accepted by {@link cond}. A column reference
187
+ * (`Model.col.<field>`) — required for naming a column — or an embedded value
188
+ * that is type-checked against that column. Bare column-name strings are NOT
189
+ * accepted; you must use a {@link Column} reference, which keeps the fragment
190
+ * refactor-safe.
191
+ *
192
+ * @typeParam M - The owning model brand. Constrains all columns to one model,
193
+ * so passing another model's column is a compile error.
194
+ */
195
+ type CondSlot<M> = Column<any, M> | string | number | boolean | Date | Param<unknown>;
196
+ /**
197
+ * Raw DynamoDB condition escape hatch.
198
+ *
199
+ * Use only for conditions the declarative operator objects cannot express.
200
+ * Column names MUST be embedded via `Model.col.<field>` references (never bare
201
+ * strings); values are embedded directly and parameterized.
202
+ *
203
+ * @example
204
+ * ```ts
205
+ * filter: cond`${User.col.age} > ${18} and attribute_exists(${User.col.email})`
206
+ * ```
207
+ */
208
+ declare function cond<M = unknown>(template: TemplateStringsArray, ...parts: CondSlot<M>[]): RawCondition<M>;
209
+
210
+ /**
211
+ * Type-safe attribute condition API (DynamoDB `FilterExpression`).
212
+ *
213
+ * Modeled on AWS AppSync's `ModelFilterInput`: field keys map to either an
214
+ * equality shorthand (a bare value) or an operator object. Operators are
215
+ * constrained per field type so that e.g. `beginsWith` on a numeric field is a
216
+ * compile error. Logical groups are expressed with the reserved `and` / `or`
217
+ * keys (arrays) and `not` (a single nested filter).
218
+ *
219
+ * The condition is typed against the *full* Entity `T` (its scalar fields),
220
+ * **not** the `select` projection — so server-side filters can reference
221
+ * attributes that are not projected, and nested-relation filters stay fully
222
+ * type-checked without callbacks (this is what structurally closes the #29
223
+ * type hole).
224
+ */
225
+ type IsString<V> = [V] extends [string] ? true : false;
226
+ type IsNumber<V> = [V] extends [number | bigint] ? true : false;
227
+ /**
228
+ * Operators common to all scalar field types.
229
+ *
230
+ * @typeParam V - The declared field value type.
231
+ */
232
+ interface CommonOperators<V> {
233
+ /** `#f = :v` */
234
+ eq?: V;
235
+ /** `#f <> :v` */
236
+ ne?: V;
237
+ /** `#f IN (:v0, :v1, …)` */
238
+ in?: readonly V[];
239
+ /** `attribute_exists(#f)` (true) / `attribute_not_exists(#f)` (false) */
240
+ attributeExists?: boolean;
241
+ /** `attribute_type(#f, :v)` — DynamoDB attribute type code, e.g. `'S'`, `'N'`. */
242
+ attributeType?: string;
243
+ }
244
+ /** Comparison operators valid on orderable (string / number / Date) fields. */
245
+ interface ComparableOperators<V> {
246
+ /** `#f > :v` */
247
+ gt?: V;
248
+ /** `#f >= :v` */
249
+ ge?: V;
250
+ /** `#f < :v` */
251
+ lt?: V;
252
+ /** `#f <= :v` */
253
+ le?: V;
254
+ /** `#f BETWEEN :lo AND :hi` */
255
+ between?: readonly [V, V];
256
+ }
257
+ /** Operators valid only on string fields. */
258
+ interface StringOnlyOperators {
259
+ /** `begins_with(#f, :v)` */
260
+ beginsWith?: string;
261
+ /** `contains(#f, :v)` */
262
+ contains?: string;
263
+ /** `NOT contains(#f, :v)` */
264
+ notContains?: string;
265
+ /** `size(#f) = :v` (string length) */
266
+ size?: number;
267
+ }
268
+ /**
269
+ * The operator object accepted for a field of value type `V`.
270
+ *
271
+ * - string → common + comparable + string-only (`beginsWith`, `contains`, …)
272
+ * - number → common + comparable (no `beginsWith` / `contains`)
273
+ * - Date → common + comparable (compared against `Date`)
274
+ * - boolean → common only
275
+ * - other → common only
276
+ */
277
+ type FieldCondition<V> = IsString<V> extends true ? CommonOperators<V> & ComparableOperators<V> & StringOnlyOperators : IsNumber<V> extends true ? CommonOperators<V> & ComparableOperators<V> : [NonNullable<V>] extends [Date] ? CommonOperators<V> & ComparableOperators<V> : CommonOperators<V>;
278
+ /** A field key may be given as an equality shorthand or an operator object. */
279
+ type FieldFilter<V> = V | FieldCondition<V>;
280
+ /**
281
+ * Same scalar classification used across the public API: a field is filterable
282
+ * when its value type is a leaf scalar (string/number/boolean/Date/bigint/…),
283
+ * not a relation (`DDBModel` / `DDBModel[]`) or embedded object.
284
+ */
285
+ type FilterableScalar = string | number | boolean | bigint | Date | null | undefined;
286
+ /**
287
+ * The set of filterable scalar field keys of `T` (relations and embedded
288
+ * objects excluded). Mirrors the projection rules in `selectable.ts`.
289
+ */
290
+ type ScalarKeys<T> = {
291
+ [K in keyof T]: T[K] extends Function ? never : T[K] extends Array<infer E> ? [E] extends [DDBModel] ? never : K : NonNullable<T[K]> extends DDBModel ? never : NonNullable<T[K]> extends FilterableScalar ? K : never;
292
+ }[keyof T];
293
+ /**
294
+ * AppSync `ModelFilterInput`-compatible filter for the full Entity `T`.
295
+ *
296
+ * Field keys (scalar attributes of `T`) accept an equality shorthand or an
297
+ * operator object; the reserved `and` / `or` keys take arrays of nested
298
+ * filters and `not` takes a single nested filter.
299
+ */
300
+ type FilterInput<T> = ({
301
+ [K in ScalarKeys<T>]?: FieldFilter<T[K]>;
302
+ } & {
303
+ and?: readonly (FilterInput<T> | RawCondition<T>)[];
304
+ or?: readonly (FilterInput<T> | RawCondition<T>)[];
305
+ not?: FilterInput<T> | RawCondition<T>;
306
+ }) | RawCondition<T>;
307
+ /**
308
+ * A type-safe **write condition** for `putItem` / `updateItem` / `deleteItem`
309
+ * and a transaction item (issue #114-A). Structurally the declarative operator
310
+ * subset of {@link FilterInput} — the same per-field-type operator constraints
311
+ * (e.g. `beginsWith` on a numeric field is a compile error) — plus the legacy
312
+ * existence primitives (`{ notExists }` / `{ attributeExists }` /
313
+ * `{ attributeNotExists }`). The raw `cond` escape hatch (issue #114-B) is also
314
+ * accepted — as a whole condition or as a member of an `and` / `or` / `not`
315
+ * group — for conditions the declarative subset cannot express; its
316
+ * `Model.col.<field>` brand must match the write target model `T`
317
+ * (injection-free / refactor-safe), exactly as on the read side.
318
+ */
319
+ type WriteCondition<T> = ({
320
+ [K in ScalarKeys<T>]?: FieldFilter<T[K]>;
321
+ } & {
322
+ /** Legacy whole-row guard: `attribute_not_exists(PK)`. */
323
+ notExists?: true;
324
+ /** `attribute_exists(<field>)` — the named attribute must be present. */
325
+ attributeExists?: string;
326
+ /** `attribute_not_exists(<field>)` — the named attribute must be absent. */
327
+ attributeNotExists?: string;
328
+ and?: readonly (WriteCondition<T> | RawCondition<T>)[];
329
+ or?: readonly (WriteCondition<T> | RawCondition<T>)[];
330
+ not?: WriteCondition<T> | RawCondition<T>;
331
+ }) | RawCondition<T>;
332
+ /** Options carrying an optional type-safe write {@link WriteCondition}. */
333
+ interface WriteOptions<T> {
334
+ readonly condition?: WriteCondition<T>;
335
+ }
336
+
337
+ /**
338
+ * Internal brand symbol identifying a compiled select builder at runtime.
339
+ * Consumers should not depend on this; it is used by the execution layer to
340
+ * distinguish a builder instance from a plain select object.
341
+ */
342
+ declare const SELECT_BUILDER: unique symbol;
343
+ /**
344
+ * The normalized spec a select builder resolves to. This is the shape the
345
+ * planner / traversal layer consumes. `filter` is the declarative server-side
346
+ * DynamoDB FilterExpression input.
347
+ *
348
+ * @internal
349
+ */
350
+ interface SelectBuilderSpec {
351
+ readonly [SELECT_BUILDER]: true;
352
+ select: Record<string, unknown>;
353
+ filter?: Record<string, unknown>;
354
+ limit?: number;
355
+ after?: string;
356
+ order?: 'ASC' | 'DESC';
357
+ }
358
+ /**
359
+ * Top-level select builder produced by `Model.project(...)`.
360
+ *
361
+ * Two-stage, model-bound inference: the entity type `T` is fixed by the model,
362
+ * and the projection `S` is inferred from the `project(...)` argument.
363
+ *
364
+ * `.filter(...)` is the declarative server-side DynamoDB FilterExpression,
365
+ * typed against the full Entity `T` (so it may reference unprojected attributes
366
+ * and type-checks operators / value types).
367
+ *
368
+ * @typeParam T - The (model-bound) Entity type.
369
+ * @typeParam S - The (fixed) select projection.
370
+ */
371
+ interface SelectBuilder<T, S> {
372
+ /** Declarative server-side DynamoDB FilterExpression, typed against `T`. */
373
+ filter(filter: FilterInput<T>): SelectBuilder<T, S>;
374
+ }
375
+ /**
376
+ * Relation select builder produced by `Model.relation(...)`. Same model-bound
377
+ * two-stage inference as {@link SelectBuilder}, plus per-relation pagination
378
+ * controls.
379
+ *
380
+ * @typeParam T - The (model-bound) relation target Entity type.
381
+ * @typeParam S - The (fixed) select projection for this relation.
382
+ */
383
+ interface RelationBuilder<T, S> {
384
+ filter(filter: FilterInput<T>): RelationBuilder<T, S>;
385
+ limit(limit: number): RelationBuilder<T, S>;
386
+ after(cursor: string): RelationBuilder<T, S>;
387
+ order(order: 'ASC' | 'DESC'): RelationBuilder<T, S>;
388
+ }
389
+ /**
390
+ * Extracts the projection `S` carried by a top-level or relation builder.
391
+ * Used by `QueryResult` so a builder in a select position resolves to the same
392
+ * result type as the bare projection it wraps.
393
+ */
394
+ type SelectOf<B> = B extends SelectBuilder<infer _T, infer S> ? S : B extends RelationBuilder<infer _RT, infer RS> ? RS : never;
395
+
396
+ type Scalar = string | number | boolean | bigint | symbol | null | undefined | Date | Uint8Array | Set<any>;
397
+ /**
398
+ * Derives the select specification type from an Entity type.
399
+ *
400
+ * - Scalar fields → `true`
401
+ * - Embedded objects (non-DDBModel) → nested `SelectableOf<E>`
402
+ * - hasMany relations (`DDBModel[]`) → `RelationSelect<E>`
403
+ * - belongsTo/hasOne relations (`DDBModel | null`) → `RelationSelect<E>`
404
+ * - Function properties → excluded
405
+ */
406
+ type SelectableOf<T> = {
407
+ [K in keyof T as T[K] extends Function ? never : K]?: T[K] extends Array<infer E> ? [E] extends [DDBModel] ? RelationSpec<E> : true : NonNullable<T[K]> extends DDBModel ? null extends T[K] ? RelationSpec<NonNullable<T[K]>> : SelectableOf<T[K]> : T[K] extends Scalar ? true : SelectableOf<T[K]>;
408
+ };
409
+ /**
410
+ * What a relation field accepts in a `select`: either the plain
411
+ * {@link RelationSelect} object, or a {@link RelationBuilder} produced by the
412
+ * target model's `Model.relation(...)`. The model-bound builder pins the
413
+ * relation's target type, so passing another model's builder here is a compile
414
+ * error (closes #33).
415
+ */
416
+ type RelationSpec<T> = RelationSelect<T> | RelationBuilder<T, any>;
417
+ /**
418
+ * Plain-object selection spec for a Relation field. Wraps `SelectableOf<T>`
419
+ * with pagination controls and an optional declarative server-side `filter`
420
+ * (typed against the full relation target `T`, so it may reference attributes
421
+ * not in the projection).
422
+ *
423
+ * For arbitrary post-load TypeScript filtering, narrow as far as possible with
424
+ * the declarative server-side `filter`, then apply any remaining JS-only
425
+ * condition on the (already fully typed) result with `result.items.filter(...)`.
426
+ *
427
+ * @typeParam T - The target relation Entity type
428
+ * @typeParam S - The select specification for this relation (inferred per call)
429
+ */
430
+ type RelationSelect<T, S extends SelectableOf<T> = SelectableOf<T>> = {
431
+ select: S;
432
+ limit?: number;
433
+ after?: string;
434
+ order?: 'ASC' | 'DESC';
435
+ filter?: FilterInput<T>;
436
+ };
437
+ /**
438
+ * Strict ("exact") version of a select projection that rejects excess
439
+ * properties.
440
+ *
441
+ * # Why this exists
442
+ *
443
+ * `query` / `list` infer the projection as a free generic parameter
444
+ * (`<Sel extends SelectSpec<T>>`, `select: Sel`). When an object literal is
445
+ * matched against a *bare type parameter* TypeScript infers `Sel` to the
446
+ * literal's exact shape and then only checks the constraint
447
+ * `Sel extends SelectSpec<T>` **structurally** — there is no object-literal
448
+ * *freshness* (excess-property) check against a concrete target, because the
449
+ * target (`Sel`) is whatever the literal is. `SelectableOf<T>` has all-optional
450
+ * properties, so an extra key like `BOGUS: true` satisfies the constraint and
451
+ * compiles. The `SelectableOf<T> | SelectBuilder<…>` union compounds this
452
+ * (freshness checks are also suppressed against unions).
453
+ *
454
+ * `StrictSelect` closes the hole without `any`: it maps every key of the
455
+ * inferred `Sel` that is **not** a known key of `SelectableOf<T>` to `never`.
456
+ * Used as an *additional* constraint at the call site
457
+ * (`select: Sel & StrictSelect<T, Sel>`), a stray key forces that property's
458
+ * type to `never`, which the supplied `true` (or nested object) is not
459
+ * assignable to — so the call is a compile error. Known keys keep their
460
+ * inferred value type, so the result-type inference (`QueryResult`) and the
461
+ * builder branch are unaffected.
462
+ *
463
+ * Recurses into the plain-object nested forms so the same rejection applies to
464
+ * nested **relation** selects (`{ select: { … } }`) and **embedded** object
465
+ * selects. Builder values (`RelationBuilder` / `SelectBuilder`) are left as-is —
466
+ * their projection was already constrained to `SelectableOf<T>` when the
467
+ * `Model.relation(...)` / `Model.project(...)` factory produced them.
468
+ *
469
+ * @typeParam T - The Entity type the projection is taken against.
470
+ * @typeParam Sel - The inferred select object (per call).
471
+ */
472
+ type StrictSelect<T, Sel> = {
473
+ [K in keyof Sel]: K extends keyof SelectableOf<T> ? Sel[K] extends {
474
+ select: infer Nested;
475
+ } ? K extends keyof T ? Sel[K] & {
476
+ select: StrictSelect<RelationTargetOf<T[K]>, Nested>;
477
+ } : Sel[K] : Sel[K] extends object ? Sel[K] extends RelationBuilder<infer _RT, infer _RS> ? Sel[K] : K extends keyof T ? Sel[K] & StrictSelect<NonNullable<T[K]>, Sel[K]> : Sel[K] : Sel[K] : never;
478
+ };
479
+ /**
480
+ * Extracts the relation **target entity type** from an entity field type:
481
+ * `E[]` → `E`, `E | null` → `E`. Mirrors the helper in `select-builder.ts`;
482
+ * used by {@link StrictSelect} to recurse a nested relation select against the
483
+ * related model's fields.
484
+ */
485
+ type RelationTargetOf<F> = F extends Array<infer E> ? E : NonNullable<F> extends DDBModel ? NonNullable<F> : NonNullable<F>;
486
+ /**
487
+ * Call-site excess-property constraint for a top-level `select` (which may be a
488
+ * plain projection **or** a `Model.project(...)` builder).
489
+ *
490
+ * - If `Sel` is a {@link SelectBuilder}, it passes through untouched — the
491
+ * builder's projection was already constrained to `SelectableOf<T>` by the
492
+ * `Model.project(...)` factory, and applying {@link StrictSelect} would
493
+ * wrongly poison the builder's own `filter` member.
494
+ * - Otherwise `Sel` is a plain object and {@link StrictSelect} rejects any key
495
+ * not declared on `T` (top-level and nested).
496
+ *
497
+ * Applied as `select: Sel & StrictSelectSpec<T, Sel>`, so known keys keep their
498
+ * inferred type (result inference unaffected) and stray keys become `never`.
499
+ *
500
+ * @typeParam T - The Entity type the projection is taken against.
501
+ * @typeParam Sel - The inferred select spec (per call).
502
+ */
503
+ type StrictSelectSpec<T, Sel> = Sel extends SelectBuilder<any, any> ? Sel : StrictSelect<T, Sel>;
504
+ /**
505
+ * Derives the persistable input type for `put()` from an Entity type.
506
+ *
507
+ * - Function properties → excluded
508
+ * - Relation fields (`DDBModel[]`, `DDBModel | null`) → excluded
509
+ * (relations are not written by `put`)
510
+ * - Scalar fields → kept with their declared type
511
+ * - Embedded objects (non-`DDBModel`) → recursively `EntityInput`
512
+ */
513
+ type EntityInput<T> = {
514
+ [K in keyof T as T[K] extends Function ? never : T[K] extends Array<infer E> ? [E] extends [DDBModel] ? never : K : NonNullable<T[K]> extends DDBModel ? null extends T[K] ? never : K : K]: T[K] extends Scalar ? T[K] : NonNullable<T[K]> extends DDBModel ? EntityInput<NonNullable<T[K]>> : T[K] extends object ? EntityInput<T[K]> : T[K];
515
+ };
516
+
517
+ /**
518
+ * Derives the return type from an Entity type `T` and a select specification `S`.
519
+ *
520
+ * - `S[K] === true` → `T[K]` (scalar value)
521
+ * - `S[K]` has `select` property and `T[K]` is an array (hasMany) →
522
+ * `{ items: QueryResult<E, Sel>[]; cursor: string | null }`
523
+ * - `S[K]` has `select` property and `T[K]` is nullable (belongsTo/hasOne) →
524
+ * `QueryResult<E, Sel> | null`
525
+ * - `S[K]` is a plain object → nested `QueryResult` (embedded)
526
+ * - fallback → `T[K]` (for dynamic/widened select types)
527
+ *
528
+ * Distributes over union types in `S` so that dynamically constructed
529
+ * selects produce a union of result types.
530
+ */
531
+
532
+ type QueryResult<T, S> = S extends unknown ? {
533
+ [K in keyof S & keyof T]: S[K] extends true ? T[K] : S[K] extends RelationBuilder<infer _RT, infer Sel> ? T[K] extends Array<infer E> ? {
534
+ items: QueryResult<E, Sel>[];
535
+ cursor: string | null;
536
+ } : QueryResult<NonNullable<T[K]>, Sel> | null : S[K] extends {
537
+ select: infer Sel;
538
+ } ? T[K] extends Array<infer E> ? {
539
+ items: QueryResult<E, Sel>[];
540
+ cursor: string | null;
541
+ } : QueryResult<NonNullable<T[K]>, Sel> | null : S[K] extends Record<string, unknown> ? QueryResult<T[K], S[K]> : T[K];
542
+ } : never;
543
+
544
+ /**
545
+ * Compile-time brand for read results that carry a hidden, resolved base-table
546
+ * key (issue #54). A `query(..., { updatable: true })` / `list(..., { updatable:
547
+ * true })` result is typed `QueryResult<...> & Updatable` so it can be passed
548
+ * straight back to `update()`.
549
+ *
550
+ * The brand is phantom (no runtime field). Spreading / cloning / JSON
551
+ * round-tripping a branded value produces a plain object that no longer
552
+ * structurally matches `Updatable`, so the compiler rejects passing a
553
+ * stripped copy where an updatable entity is required. (Runtime existence of
554
+ * the hidden key is enforced separately by `update()`, which throws when it is
555
+ * absent — see `resolveUpdateKey`.)
556
+ *
557
+ * @example
558
+ * ```ts
559
+ * const user = await User.query({ userId: 'u' }, { name: true }, { updatable: true });
560
+ * if (user) await User.updateItem(user, { name: 'New' }); // ok — branded
561
+ * // await User.updateItem({ ...user }, { name: 'New' }); // compile error — spread drops the brand
562
+ * ```
563
+ */
564
+ declare const updatableBrand: unique symbol;
565
+ interface Updatable {
566
+ readonly [updatableBrand]: true;
567
+ }
568
+
569
+ declare const GSI_MARKER: unique symbol;
570
+ interface GsiDefinitionMarker<T extends Record<string, unknown> = Record<string, unknown>, U extends boolean = boolean> {
571
+ readonly [GSI_MARKER]: true;
572
+ readonly indexName: string;
573
+ readonly segmented: SegmentedKey;
574
+ readonly inputFieldNames: string[];
575
+ readonly unique: U;
576
+ /** @internal Phantom field for type-level input type extraction. */
577
+ readonly _phantom?: T;
578
+ }
579
+ interface GsiOptions {
580
+ unique?: boolean;
581
+ }
582
+ type GsiBuilder<T extends Record<string, unknown>> = (c: {
583
+ readonly [K in keyof T]-?: Column<T[K], T>;
584
+ }) => KeyStructure;
585
+ declare function gsi<T extends Record<string, unknown>>(indexName: string, builder: GsiBuilder<T>, options: GsiOptions & {
586
+ unique: true;
587
+ }): GsiDefinitionMarker<T, true>;
588
+ declare function gsi<T extends Record<string, unknown>>(indexName: string, builder: GsiBuilder<T>, options?: GsiOptions): GsiDefinitionMarker<T, false>;
589
+
590
+ /**
591
+ * Extracts the Primary Key input type from a model class.
592
+ * Scans static properties for `KeyDefinitionMarker<T>` and returns `T`.
593
+ *
594
+ * @typeParam C - The model class constructor (`typeof UserModel`)
595
+ */
596
+ type PrimaryKeyOf<C> = {
597
+ [K in keyof C]: C[K] extends KeyDefinitionMarker<infer I> ? I : never;
598
+ }[keyof C];
599
+ /**
600
+ * Union of all query key input types (PK + all GSIs).
601
+ * Used as the constraint for `list()`.
602
+ *
603
+ * @typeParam C - The model class constructor (`typeof UserModel`)
604
+ */
605
+ type QueryKeyOf<C> = {
606
+ [K in keyof C]: C[K] extends KeyDefinitionMarker<infer I> ? I : C[K] extends GsiDefinitionMarker<infer I, any> ? I : never;
607
+ }[keyof C];
608
+ /**
609
+ * Union of unique query key input types (PK + unique GSIs only).
610
+ * Used as the constraint for `query()` — only keys guaranteed to
611
+ * return a single item are allowed.
612
+ *
613
+ * @typeParam C - The model class constructor (`typeof UserModel`)
614
+ */
615
+ type UniqueQueryKeyOf<C> = {
616
+ [K in keyof C]: C[K] extends KeyDefinitionMarker<infer I> ? I : C[K] extends GsiDefinitionMarker<infer I, true> ? I : never;
617
+ }[keyof C];
618
+ /**
619
+ * "At least one" relaxation of a single key input type `T`: every field is
620
+ * known and correctly typed, but only a non-empty subset need be supplied (the
621
+ * remaining fields become optional).
622
+ *
623
+ * For a composite key `{ groupId: string; userId: string }` this yields
624
+ * `{ groupId: string; userId?: string } | { groupId?: string; userId: string }`
625
+ * — i.e. you may supply just the partition-key field(s) without the full
626
+ * sort-key tail. Unknown fields are still rejected (object-literal excess
627
+ * property checking), so it stays strictly typed.
628
+ */
629
+ type AtLeastOneKey<T> = {
630
+ [K in keyof T]-?: {
631
+ [P in K]-?: T[P];
632
+ } & {
633
+ [P in Exclude<keyof T, K>]?: T[P];
634
+ } extends infer O ? {
635
+ [P in keyof O]: O[P];
636
+ } : never;
637
+ }[keyof T];
638
+ /**
639
+ * Key type accepted by `list()` / `explain()`. These issue a DynamoDB *Query*
640
+ * against a partition (PK or GSI), so a **partial** key — the partition-key
641
+ * subset of a PK/GSI input — is sufficient; the sort-key portion is optional
642
+ * (it narrows the range, e.g. `begins_with`). This mirrors the runtime key
643
+ * resolver, which accepts any field subset that produces a valid partition key
644
+ * (`src/planner/key-resolver.ts`) and throws otherwise.
645
+ *
646
+ * Distinct from {@link QueryKeyOf} (full key, used as the constraint label by
647
+ * the type tests) and {@link UniqueQueryKeyOf} (full unique key for `query()`,
648
+ * which must resolve to a single item). `query()` deliberately stays strict —
649
+ * only this `list`/`explain` boundary is relaxed.
650
+ *
651
+ * @typeParam C - The model class constructor (`typeof UserModel`)
652
+ */
653
+ type PartialQueryKeyOf<C> = QueryKeyOf<C> extends infer U ? U extends Record<string, unknown> ? AtLeastOneKey<U> : never : never;
654
+
655
+ /**
656
+ * A top-level `select` may be either a plain projection (`SelectableOf<T>`) or
657
+ * a {@link SelectBuilder} produced by `Model.project(...)`. The builder is
658
+ * model-bound to `T`, so its `.filter(...)` is typed against the full entity.
659
+ */
660
+ type SelectSpec<T> = SelectableOf<T> | SelectBuilder<T, any>;
661
+ /**
662
+ * Resolves the projection `S` from a top-level select spec (plain object or
663
+ * builder), so the result type is computed identically for both forms.
664
+ */
665
+ type ProjectionOf<T, Sel> = Sel extends SelectableOf<T> ? Sel : SelectOf<Sel>;
666
+ /**
667
+ * Default model-class constraint used when a `ModelStatic` is referenced
668
+ * without its concrete constructor type (e.g. `ModelStatic<UserModel>`).
669
+ *
670
+ * In that case key/option types fall back to permissive shapes so existing
671
+ * usages keep compiling, while the precise inference is available when the
672
+ * constructor type is supplied (as it is from `DDBModel.asModel()`).
673
+ */
674
+ type AnyModelClass = abstract new (...args: any[]) => DDBModel;
675
+ /**
676
+ * The accepted PK / GSI key type for `list()` / `explain()`.
677
+ *
678
+ * `list`/`explain` issue a DynamoDB *Query* against a partition, so a **partial**
679
+ * key (partition-key subset of a PK/GSI input) is accepted — the sort-key tail
680
+ * is optional. This matches the runtime key resolver. `query()` stays strict via
681
+ * {@link QueryKey}. Derived from the model class when available, otherwise
682
+ * permissive.
683
+ */
684
+ type ListKey<C> = [C] extends [AnyModelClass] ? PartialQueryKeyOf<C> : Record<string, unknown>;
685
+ /**
686
+ * The accepted key type for `query()` — only keys guaranteed to return a
687
+ * single item (PK + unique GSIs). Derived from the model class when available.
688
+ */
689
+ type QueryKey<C> = [C] extends [AnyModelClass] ? UniqueQueryKeyOf<C> : Record<string, unknown>;
690
+ /**
691
+ * The accepted key type for `delete()` (PK only). Derived from the model
692
+ * class when available.
693
+ */
694
+ type DeleteKey<C> = [C] extends [AnyModelClass] ? PrimaryKeyOf<C> : Record<string, unknown>;
695
+ /**
696
+ * The accepted *explicit* key type for `update()` — the base-table primary key
697
+ * only. DynamoDB `UpdateItem` can target an item solely by its base-table PK
698
+ * (GSI-based updates are impossible), so this is `PrimaryKeyOf<C>`, not
699
+ * `UniqueQueryKeyOf<C>`. Derived from the model class when available.
700
+ */
701
+ type UpdateExplicitKey<C> = [C] extends [AnyModelClass] ? PrimaryKeyOf<C> : Record<string, unknown>;
702
+ /**
703
+ * Options accepted by `list()` (third argument). The projection is supplied as
704
+ * the separate second `select` argument, symmetric with `query()`.
705
+ *
706
+ * - `filter` is the declarative server-side DynamoDB FilterExpression, typed
707
+ * against the full Entity `T` (may reference unprojected attributes). For
708
+ * arbitrary JS-only post-load filtering, narrow with `filter` then apply
709
+ * `result.items.filter(...)` (already fully typed).
710
+ *
711
+ * @typeParam T - The Entity type.
712
+ */
713
+ type ListCallOptions<T> = {
714
+ limit?: number;
715
+ after?: string;
716
+ order?: 'ASC' | 'DESC';
717
+ filter?: FilterInput<T>;
718
+ /**
719
+ * When `true`, each returned item is updatable: it carries its resolved
720
+ * base-table key as a hidden, non-enumerable property and is branded
721
+ * `& Updatable`, so it can be passed straight back to `updateItem()`.
722
+ */
723
+ updatable?: boolean;
724
+ };
725
+ /**
726
+ * Public, type-inferring API surface produced by {@link DDBModel.asModel}.
727
+ *
728
+ * @typeParam T - The Entity type of the model.
729
+ * @typeParam C - The model class constructor type. Supplied automatically by
730
+ * `asModel()`; when omitted, key/option types widen to permissive shapes so
731
+ * that `ModelStatic<T>` keeps compiling for callers that only name the entity.
732
+ */
733
+ interface ModelStatic<T extends DDBModel, C = unknown> {
734
+ /**
735
+ * Type-safe column references for this model, one per scalar field. Used by
736
+ * the `cond` raw escape hatch (`cond\`${Model.col.age} > ${18}\``). Each
737
+ * column carries its declared type and is branded to this model, so passing
738
+ * another model's column into this model's `cond` is a compile error.
739
+ */
740
+ readonly col: ColumnMap<T>;
741
+ /**
742
+ * Begin a model-bound, top-level select builder. The entity type `T` is fixed
743
+ * by this model and the projection `S` is inferred from the argument.
744
+ * `.filter(...)` is typed `FilterInput<T>` against the full entity.
745
+ *
746
+ * @example
747
+ * ```ts
748
+ * Order.list({ userId: 'u' },
749
+ * Order.project({ orderId: true, amount: true })
750
+ * .filter({ status: 'confirmed', amount: { gt: 100 } }),
751
+ * );
752
+ * ```
753
+ */
754
+ project<const S extends SelectableOf<T>>(select: S): SelectBuilder<T, S>;
755
+ /**
756
+ * Begin a model-bound relation select builder for use as a relation field's
757
+ * value inside a parent `select`. `T` is this (relation target) model's
758
+ * entity type; `S` is inferred from the argument. Same model-bound inference
759
+ * as {@link ModelStatic.project}, plus per-relation pagination
760
+ * (`limit` / `after` / `order`).
761
+ *
762
+ * @example
763
+ * ```ts
764
+ * User.query({ userId: 'u' }, {
765
+ * name: true,
766
+ * orders: Order.relation({ orderId: true, amount: true })
767
+ * .filter({ amount: { gt: 100 } }),
768
+ * });
769
+ * ```
770
+ */
771
+ relation<const S extends SelectableOf<T>>(select: S): RelationBuilder<T, S>;
772
+ /**
773
+ * Fetch a single item by a unique key and **hydrate** the plain result onto a
774
+ * host-side object (issue #53). The `hydrate` factory receives the fully-built
775
+ * plain object — `QueryResult<T, ProjectionOf<T, Sel>>`, typed against exactly
776
+ * the `select`ed fields — and its return type `R` becomes the read's return
777
+ * type (subsuming a `queryAs(Ctor, …)` style: `hydrate: (raw) => new Ctor(raw)`).
778
+ *
779
+ * Applied as the **final after-fetch step**, only to a present item: a missing
780
+ * item resolves to `null` and the factory is NOT invoked (hence `R | null`). A
781
+ * throw from the factory propagates verbatim as the read's rejection.
782
+ *
783
+ * Host-runtime ONLY: the factory is a host-language closure, never serialized
784
+ * into the SSoT / `operations.json`, so it does not impede the bridge (#48).
785
+ */
786
+ query<Sel extends SelectSpec<T>, R>(key: QueryKey<C>, select: Sel & StrictSelectSpec<T, Sel>, options: {
787
+ consistentRead?: boolean;
788
+ maxDepth?: number;
789
+ updatable?: boolean;
790
+ hydrate: (raw: QueryResult<T, ProjectionOf<T, Sel>>) => R;
791
+ }): Promise<R | null>;
792
+ /**
793
+ * Fetch a single item by a unique key, requesting an **updatable** result.
794
+ * The returned item (when non-null) carries its resolved base-table key as a
795
+ * hidden, non-enumerable property and is branded `& Updatable`, so it can be
796
+ * passed straight back to {@link ModelStatic.updateItem} — even for a partial
797
+ * `select` or a GSI-based query. Do not spread / clone / JSON round-trip the
798
+ * result before updating: that drops both the hidden key and the brand.
799
+ */
800
+ query<Sel extends SelectSpec<T>>(key: QueryKey<C>, select: Sel & StrictSelectSpec<T, Sel>, options: {
801
+ consistentRead?: boolean;
802
+ maxDepth?: number;
803
+ updatable: true;
804
+ }): Promise<(QueryResult<T, ProjectionOf<T, Sel>> & Updatable) | null>;
805
+ /**
806
+ * Fetch a single item by a unique key. The return type contains only the
807
+ * selected fields, derived from `select`.
808
+ */
809
+ query<Sel extends SelectSpec<T>>(key: QueryKey<C>, select: Sel & StrictSelectSpec<T, Sel>, options?: {
810
+ consistentRead?: boolean;
811
+ maxDepth?: number;
812
+ updatable?: false;
813
+ }): Promise<QueryResult<T, ProjectionOf<T, Sel>> | null>;
814
+ /**
815
+ * List items for a partition, symmetric with {@link ModelStatic.query}:
816
+ * `list(key, select, options?)`. `select` (the second argument) may be a plain
817
+ * projection or a `project(...)` builder. `options.filter` is the declarative
818
+ * server-side FilterExpression typed against the full Entity. Returned items
819
+ * contain only the selected fields.
820
+ */
821
+ list<Sel extends SelectSpec<T>>(key: ListKey<C>, select: Sel & StrictSelectSpec<T, Sel>, options: ListCallOptions<T> & {
822
+ updatable: true;
823
+ }): Promise<{
824
+ items: (QueryResult<T, ProjectionOf<T, Sel>> & Updatable)[];
825
+ cursor: string | null;
826
+ }>;
827
+ list<Sel extends SelectSpec<T>>(key: ListKey<C>, select: Sel & StrictSelectSpec<T, Sel>, options?: ListCallOptions<T>): Promise<{
828
+ items: QueryResult<T, ProjectionOf<T, Sel>>[];
829
+ cursor: string | null;
830
+ }>;
831
+ explain<Sel extends SelectSpec<T>>(key: ListKey<C>, options?: {
832
+ select?: Sel & StrictSelectSpec<T, Sel>;
833
+ limit?: number;
834
+ after?: string;
835
+ order?: 'ASC' | 'DESC';
836
+ consistentRead?: boolean;
837
+ filter?: FilterInput<T>;
838
+ }): ExecutionPlan;
839
+ putItem(item: EntityInput<T>, options?: WriteOptions<T>): Promise<void>;
840
+ /**
841
+ * Update an item identified by its **explicit base-table primary key**
842
+ * (`PrimaryKeyOf<C>`). This is the canonical form — it requires the key input
843
+ * up front, so it can never silently target the wrong item.
844
+ *
845
+ * @example `await User.updateItem({ userId: 'alice' }, { status: 'disabled' });`
846
+ */
847
+ updateItem(key: UpdateExplicitKey<C>, changes: Partial<T>, options?: WriteOptions<T>): Promise<void>;
848
+ /**
849
+ * Update an item using an **updatable** entity obtained from
850
+ * `query(..., { updatable: true })` / `list(..., { updatable: true })`. The
851
+ * hidden base-table key carried by that entity is used directly, so this
852
+ * works even for a partial `select` or a GSI-based read.
853
+ *
854
+ * Pass the entity as-is. A spread / clone / JSON round-trip drops both the
855
+ * brand (rejected here at compile time) and the hidden key (rejected at
856
+ * runtime), preventing an accidental wrong-item update.
857
+ */
858
+ updateItem(entity: Partial<T> & Updatable, changes: Partial<T>, options?: WriteOptions<T>): Promise<void>;
859
+ deleteItem(key: DeleteKey<C>, options?: WriteOptions<T>): Promise<void>;
860
+ }
861
+
862
+ interface PutOptions<T = unknown> {
863
+ condition?: WriteCondition<T>;
864
+ }
865
+ interface UpdateOptions<T = unknown> {
866
+ condition?: WriteCondition<T>;
867
+ }
868
+ interface DeleteOptions<T = unknown> {
869
+ condition?: WriteCondition<T>;
870
+ }
871
+
872
+ interface PutInput {
873
+ TableName: string;
874
+ Item: Record<string, unknown>;
875
+ ConditionExpression?: string;
876
+ ExpressionAttributeNames?: Record<string, string>;
877
+ ExpressionAttributeValues?: Record<string, unknown>;
878
+ }
879
+ declare function buildPutInput(modelClass: Function, item: Record<string, unknown>, options?: PutOptions): PutInput;
880
+ declare function executePut(modelClass: Function, item: Record<string, unknown>, options?: PutOptions): Promise<void>;
881
+
882
+ interface UpdateInput {
883
+ TableName: string;
884
+ Key: Record<string, unknown>;
885
+ UpdateExpression: string;
886
+ ExpressionAttributeNames: Record<string, string>;
887
+ ExpressionAttributeValues?: Record<string, unknown>;
888
+ ConditionExpression?: string;
889
+ }
890
+ declare function buildUpdateInput(modelClass: Function, entity: Record<string, unknown>, changes: Record<string, unknown>, options?: UpdateOptions): UpdateInput;
891
+ declare function executeUpdate(modelClass: Function, entity: Record<string, unknown>, changes: Record<string, unknown>, options?: UpdateOptions): Promise<void>;
892
+
893
+ interface DeleteInput {
894
+ TableName: string;
895
+ Key: Record<string, unknown>;
896
+ ConditionExpression?: string;
897
+ ExpressionAttributeNames?: Record<string, string>;
898
+ ExpressionAttributeValues?: Record<string, unknown>;
899
+ }
900
+ declare function buildDeleteInput(modelClass: Function, keyObj: Record<string, unknown>, options?: DeleteOptions): DeleteInput;
901
+ declare function executeDelete(modelClass: Function, keyObj: Record<string, unknown>, options?: DeleteOptions): Promise<void>;
902
+
903
+ /** Result of a read operation (GetItem / Query / BatchGetItem). */
904
+ interface ExecutorResult {
905
+ items: Record<string, unknown>[];
906
+ lastEvaluatedKey?: Record<string, unknown>;
907
+ }
908
+ /**
909
+ * Result of a single structured write. `oldItem` is the pre-write image,
910
+ * present only when the caller requested it (`returnOldImage: true`, mapped to
911
+ * DynamoDB `ReturnValues: ALL_OLD`) and an item existed before the write. The
912
+ * write-capture seam (issue #72) reads it to derive INSERT vs MODIFY and the
913
+ * REMOVE OldImage.
914
+ */
915
+ interface WriteResult {
916
+ oldItem?: Record<string, unknown>;
917
+ }
918
+ /** Options shared by the single-item structured write methods. */
919
+ interface WriteExecOptions {
920
+ /** Request the pre-write image (DynamoDB `ReturnValues: ALL_OLD`). */
921
+ returnOldImage?: boolean;
922
+ }
923
+ /**
924
+ * One BatchGetItem sub-request: a single physical table plus its keys and an
925
+ * optional projection. Mirrors {@link import('../planner/types.js').BatchGetItemOperation}
926
+ * without the discriminant `type` field, so callers that already chunked keys
927
+ * (relation traversal) and the planner-driven read path share one entry point.
928
+ */
929
+ interface BatchGetExecInput {
930
+ tableName: string;
931
+ keys: Record<string, unknown>[];
932
+ projectionExpression?: string;
933
+ expressionAttributeNames?: Record<string, string>;
934
+ consistentRead?: boolean;
935
+ }
936
+ /** A single item in a {@link Executor.batchWrite} request, grouped by table. */
937
+ type BatchWriteExecItem = {
938
+ type: 'put';
939
+ item: Record<string, unknown>;
940
+ } | {
941
+ type: 'delete';
942
+ key: Record<string, unknown>;
943
+ };
944
+ /**
945
+ * A read-only `ConditionCheck` entry for {@link Executor.transactWrite} (issue
946
+ * #81): it asserts a `ConditionExpression` holds for the keyed item without
947
+ * mutating it. A failed assertion cancels the **whole** `TransactWriteItems`
948
+ * atomically — the foundation for referential-integrity derivation.
949
+ */
950
+ interface ConditionCheckInput {
951
+ TableName: string;
952
+ Key: Record<string, unknown>;
953
+ ConditionExpression: string;
954
+ ExpressionAttributeNames?: Record<string, string>;
955
+ ExpressionAttributeValues?: Record<string, unknown>;
956
+ }
957
+ /**
958
+ * A `{ Put | Update | Delete | ConditionCheck }` entry for
959
+ * {@link Executor.transactWrite}. The first three mutate an item; `ConditionCheck`
960
+ * (issue #81) is a read-only assertion on another item.
961
+ */
962
+ type TransactWriteExecItem = {
963
+ Put: PutInput;
964
+ } | {
965
+ Update: UpdateInput;
966
+ } | {
967
+ Delete: DeleteInput;
968
+ } | {
969
+ ConditionCheck: ConditionCheckInput;
970
+ };
971
+ /**
972
+ * The unified I/O seam (issue #76). All of GraphDDB's read and write I/O is
973
+ * funneled through one injectable executor so the engine (planner / hydrator /
974
+ * relation traversal / write paths) can run unchanged against either real
975
+ * DynamoDB ({@link DynamoExecutor}, the default) or an in-process memory store
976
+ * (`MemoryExecutor`, the test adapter).
977
+ *
978
+ * Reads take a structured {@link DynamoDBOperation}; writes take the structured
979
+ * inputs the operation layer already builds (`PutInput` / `UpdateInput` / …),
980
+ * never an AWS SDK command. This keeps the seam at the *structured operation
981
+ * boundary* (proposal option B): no expression-string parsing is required to
982
+ * implement a fake.
983
+ */
984
+ interface Executor {
985
+ /** Read path: GetItem / Query / BatchGetItem. */
986
+ execute(operation: DynamoDBOperation): Promise<ExecutorResult>;
987
+ /**
988
+ * BatchGetItem for a single table. The caller is responsible for chunking
989
+ * keys ≤100 (the relation traversal and the planner-driven path both do).
990
+ */
991
+ batchGet(input: BatchGetExecInput): Promise<ExecutorResult>;
992
+ /** Put one item. */
993
+ put(input: PutInput, options?: WriteExecOptions): Promise<WriteResult>;
994
+ /** Update one item (applies its structured UpdateExpression). */
995
+ update(input: UpdateInput, options?: WriteExecOptions): Promise<WriteResult>;
996
+ /** Delete one item. */
997
+ delete(input: DeleteInput, options?: WriteExecOptions): Promise<WriteResult>;
998
+ /**
999
+ * BatchWriteItem for a single table (non-atomic). Keys/items are chunked ≤25
1000
+ * with UnprocessedItems retry by the implementation.
1001
+ */
1002
+ batchWrite(tableName: string, items: BatchWriteExecItem[]): Promise<void>;
1003
+ /** TransactWriteItems — all-or-nothing atomic. */
1004
+ transactWrite(items: TransactWriteExecItem[]): Promise<void>;
1005
+ }
1006
+
1007
+ type TransactWriteItemInput = {
1008
+ Put: PutInput;
1009
+ } | {
1010
+ Update: UpdateInput;
1011
+ } | {
1012
+ Delete: DeleteInput;
1013
+ } | {
1014
+ ConditionCheck: ConditionCheckInput;
1015
+ };
1016
+ declare function attachModelClass<T extends DDBModel>(modelStatic: ModelStatic<T>, modelClass: new (...args: unknown[]) => T): ModelStatic<T>;
1017
+ /**
1018
+ * Per-item capture descriptor recorded alongside each transact item, so the
1019
+ * write-capture seam (issue #72) can emit a labelled raw record after the
1020
+ * transaction commits. `TransactWriteItems` returns no images (spec §14), so
1021
+ * these carry the keys + the new item for Put/Update and keys only for Delete.
1022
+ */
1023
+ interface TransactCaptureMeta {
1024
+ modelName?: string;
1025
+ op: 'put' | 'update' | 'delete';
1026
+ table: string;
1027
+ keys: {
1028
+ pk: string;
1029
+ sk: string;
1030
+ };
1031
+ newItem?: Record<string, unknown>;
1032
+ }
1033
+ declare class TransactionContext {
1034
+ private readonly items;
1035
+ private readonly captureMeta;
1036
+ get itemCount(): number;
1037
+ put(model: ModelStatic<DDBModel>, item: Record<string, unknown>, options?: PutOptions): void;
1038
+ update(model: ModelStatic<DDBModel>, entity: Record<string, unknown>, changes: Record<string, unknown>, options?: UpdateOptions): void;
1039
+ delete(model: ModelStatic<DDBModel>, key: Record<string, unknown>, options?: DeleteOptions): void;
1040
+ /**
1041
+ * Add a read-only `ConditionCheck` assertion on a keyed item (issue #81). The
1042
+ * item is **not** mutated; the `options.condition` (e.g.
1043
+ * `{ attributeExists: 'PK' }` to require the row exists) is asserted, and a
1044
+ * failed assertion cancels the **whole** `TransactWriteItems` atomically. This
1045
+ * is the foundation for referential-integrity derivation (proposal: `requires
1046
+ * <Entity> exists`). A ConditionCheck emits no change-capture record (it writes
1047
+ * nothing).
1048
+ *
1049
+ * @throws if `options.condition` is missing — a ConditionCheck without an
1050
+ * assertion is meaningless.
1051
+ */
1052
+ conditionCheck(model: ModelStatic<DDBModel>, key: Record<string, unknown>, options: {
1053
+ condition: Record<string, unknown>;
1054
+ }): void;
1055
+ /** @internal */
1056
+ getTransactItems(): TransactWriteItemInput[];
1057
+ /** @internal — capture descriptors for the write-capture seam (issue #72). */
1058
+ getCaptureMeta(): TransactCaptureMeta[];
1059
+ private assertWithinLimit;
1060
+ }
1061
+ declare function executeTransaction(fn: (tx: TransactionContext) => void | Promise<void>): Promise<void>;
1062
+
1063
+ interface BatchGetRequest {
1064
+ model: ModelStatic<DDBModel>;
1065
+ keys: Record<string, unknown>[];
1066
+ }
1067
+ interface BatchPutRequest {
1068
+ type: 'put';
1069
+ model: ModelStatic<DDBModel>;
1070
+ item: Record<string, unknown>;
1071
+ options?: PutOptions;
1072
+ }
1073
+ interface BatchDeleteRequest {
1074
+ type: 'delete';
1075
+ model: ModelStatic<DDBModel>;
1076
+ key: Record<string, unknown>;
1077
+ options?: DeleteOptions;
1078
+ }
1079
+ type BatchWriteRequest = BatchPutRequest | BatchDeleteRequest;
1080
+ declare class BatchGetResult {
1081
+ private readonly groups;
1082
+ constructor(groups: Map<Function, Record<string, unknown>[]>);
1083
+ get<T extends DDBModel>(model: ModelStatic<T>): Partial<T>[];
1084
+ }
1085
+ declare function executeBatchGet(requests: BatchGetRequest[]): Promise<BatchGetResult>;
1086
+ declare function executeBatchWrite(requests: BatchWriteRequest[]): Promise<void>;
1087
+
1088
+ /**
1089
+ * Intermediate representation (IR) produced by the parameterized definition DSL
1090
+ * (issue #41). This is the **in-memory, typed** representation consumed by the
1091
+ * static planner (#42); JSON serialization is #42's responsibility, so nothing
1092
+ * here is serialized — the IR keeps full type information (param value types,
1093
+ * select projection) so downstream type derivation stays sound.
1094
+ *
1095
+ * Each `define*` entry point captures, for one definition:
1096
+ *
1097
+ * - `entity` — the model name + (runtime) class the operation targets;
1098
+ * - `operation`— the operation kind (`query` | `list` | `put` | `update`
1099
+ * | `delete`);
1100
+ * - the operation **structure** with {@link Param} placeholders left in place
1101
+ * (`key` / `item` / `changes`, plus `select` for reads); and
1102
+ * - `params` — a `name → descriptor` map collected from every placeholder in
1103
+ * the structure, with the descriptor preserving the param's value type.
1104
+ */
1105
+
1106
+ /** The set of operation kinds a definition may describe. */
1107
+ type OperationKind = 'query' | 'list' | 'put' | 'update' | 'delete';
1108
+ /**
1109
+ * A declarative write condition for a parameterized command definition (issues
1110
+ * #46, #81). Mirrors the runtime `condition` subset
1111
+ * (`expression/condition-expression.ts`):
1112
+ *
1113
+ * - `{ notExists: true }` → `attribute_not_exists(PK)` (the row must not exist).
1114
+ * This legacy whole-row form is kept verbatim for backward compatibility.
1115
+ * - `{ attributeExists: '<field>' }` → `attribute_exists(<field>)` — the named
1116
+ * attribute (any field, including PK / SK) must be present. The foundation for
1117
+ * referential-integrity derivation (proposal "capability gaps": `requires X
1118
+ * exists`).
1119
+ * - `{ attributeNotExists: '<field>' }` → `attribute_not_exists(<field>)` — the
1120
+ * named attribute must be absent (a field-level uniqueness / first-write guard).
1121
+ * - `{ field: value, … }` → field equality (`#f = :v AND …`). Each value may be
1122
+ * a concrete literal **or** a {@link Param} placeholder (bound at execution).
1123
+ *
1124
+ * The existence forms are mutually exclusive: `notExists` / `attributeExists` /
1125
+ * `attributeNotExists` are single-primitive conditions and ignore any sibling
1126
+ * fields. (When more than one would be present the most specific is taken in the
1127
+ * order `notExists` → `attributeExists` → `attributeNotExists`.)
1128
+ *
1129
+ * Beyond bare equality, a field value may be a declarative **operator object**
1130
+ * (issue #114-A) — `{ gt }`, `{ between }`, `{ in }`, `{ beginsWith }`, … —
1131
+ * mirroring the read-side `FilterInput`, and the reserved `and` / `or` (arrays)
1132
+ * / `not` (single nested condition) keys express logical groups. Each operand
1133
+ * value may be a concrete literal or a {@link Param} placeholder.
1134
+ */
1135
+ type ConditionLeaf = Param<unknown> | string | number | boolean | Date;
1136
+ /** A declarative operator object on one condition field (subset of FilterInput). */
1137
+ interface ConditionOperatorObject {
1138
+ readonly eq?: ConditionLeaf;
1139
+ readonly ne?: ConditionLeaf;
1140
+ readonly gt?: ConditionLeaf;
1141
+ readonly ge?: ConditionLeaf;
1142
+ readonly lt?: ConditionLeaf;
1143
+ readonly le?: ConditionLeaf;
1144
+ readonly between?: readonly [ConditionLeaf, ConditionLeaf];
1145
+ readonly in?: readonly ConditionLeaf[];
1146
+ readonly beginsWith?: ConditionLeaf;
1147
+ readonly contains?: ConditionLeaf;
1148
+ readonly notContains?: ConditionLeaf;
1149
+ readonly attributeExists?: boolean;
1150
+ readonly attributeType?: string;
1151
+ }
1152
+ type ConditionInput = {
1153
+ readonly notExists: true;
1154
+ } | {
1155
+ readonly attributeExists: string;
1156
+ } | {
1157
+ readonly attributeNotExists: string;
1158
+ } | RawCondition | ConditionTree;
1159
+ /** A recursive declarative condition tree (field clauses + logical groups). */
1160
+ interface ConditionTree {
1161
+ readonly and?: readonly ConditionInput[];
1162
+ readonly or?: readonly ConditionInput[];
1163
+ readonly not?: ConditionInput;
1164
+ readonly [field: string]: ConditionLeaf | ConditionOperatorObject | readonly ConditionInput[] | ConditionInput | undefined;
1165
+ }
1166
+ /** Options accepted by the write `define*` entry points (issue #46). */
1167
+ interface WriteDefinitionOptions {
1168
+ /** Optional declarative write condition (subset). */
1169
+ readonly condition?: ConditionInput;
1170
+ }
1171
+ /**
1172
+ * Identifies the entity an operation targets. `name` is the model class name
1173
+ * (entity name); `modelClass` is retained so the planner can resolve metadata
1174
+ * (keys / GSIs / fields) from the `MetadataRegistry`.
1175
+ */
1176
+ interface EntityRef {
1177
+ readonly name: string;
1178
+ readonly modelClass: Function;
1179
+ }
1180
+ /**
1181
+ * A single parameter descriptor in an operation's `params` map. Carries the
1182
+ * runtime `kind` (and, for `literal`, the allowed values) and — purely at the
1183
+ * type level — the represented value type `T`.
1184
+ *
1185
+ * @typeParam T - The value type the parameter stands in for.
1186
+ */
1187
+ interface ParamDescriptor<T = unknown> {
1188
+ readonly kind: ParamKind;
1189
+ readonly literals?: readonly T[];
1190
+ /**
1191
+ * For an `array` param (transaction `forEach` source), the per-element field
1192
+ * descriptors. `undefined` for scalar params.
1193
+ */
1194
+ readonly element?: Readonly<Record<string, ParamDescriptor>>;
1195
+ /** Parameters are always required (no optional params in this phase). */
1196
+ readonly required: true;
1197
+ }
1198
+ /**
1199
+ * Structure with {@link Param} placeholders left intact (key / item / changes).
1200
+ * A recursive record whose leaves are either concrete scalar literals (e.g. a
1201
+ * fixed sort-key discriminator) or `Param<…>` placeholders.
1202
+ */
1203
+ type ParamStructure = {
1204
+ readonly [key: string]: ParamStructure | Param<unknown> | string | number | boolean;
1205
+ };
1206
+ /**
1207
+ * The typed IR for one definition. Generic over the entity, the parameterized
1208
+ * structure(s), the (optional) select projection, and the collected params map
1209
+ * so that all type information survives into the value returned by
1210
+ * `defineQueries` / `defineCommands`.
1211
+ *
1212
+ * Field presence by `operation`:
1213
+ *
1214
+ * | op | key | select | changes | params |
1215
+ * |----------|-----|--------|---------|--------|
1216
+ * | query | ✓ | ✓ | — | ✓ |
1217
+ * | list | ✓ | ✓ | — | ✓ |
1218
+ * | put | item (in `key`) | — | — | ✓ |
1219
+ * | update | ✓ | — | ✓ | ✓ |
1220
+ * | delete | ✓ | — | — | ✓ |
1221
+ *
1222
+ * @typeParam T - The entity type.
1223
+ * @typeParam Op - The operation kind.
1224
+ * @typeParam K - The parameterized key / item structure.
1225
+ * @typeParam S - The select projection (reads only; `undefined` for writes).
1226
+ * @typeParam Ch - The parameterized changes structure (`update` only).
1227
+ * @typeParam P - The collected `name → ParamDescriptor` map.
1228
+ */
1229
+ interface OperationDefinition<T extends DDBModel, Op extends OperationKind, K, S, Ch, P extends Record<string, ParamDescriptor>> {
1230
+ /** @internal Marks this object as a definition IR node. */
1231
+ readonly __isOperationDefinition: true;
1232
+ readonly entity: EntityRef;
1233
+ readonly operation: Op;
1234
+ /**
1235
+ * The parameterized key for `query` / `list` / `update` / `delete`, or the
1236
+ * parameterized item for `put`. Placeholders are preserved in place.
1237
+ */
1238
+ readonly key: K;
1239
+ /** The select projection for `query` / `list`; `undefined` otherwise. */
1240
+ readonly select: S;
1241
+ /** The parameterized changes for `update`; `undefined` otherwise. */
1242
+ readonly changes: Ch;
1243
+ /**
1244
+ * The declarative write condition for `put` / `update` / `delete`; `undefined`
1245
+ * when no condition is attached or for reads (issue #46).
1246
+ */
1247
+ readonly condition?: ConditionInput;
1248
+ /** Collected parameters: name → descriptor (value type preserved in `P`). */
1249
+ readonly params: P;
1250
+ /** @internal Phantom carrier so the entity type `T` is retained on the node. */
1251
+ readonly __entityType?: (value: T) => void;
1252
+ }
1253
+ /** Any operation definition node, regardless of its type parameters. */
1254
+ type AnyOperationDefinition = OperationDefinition<any, OperationKind, unknown, unknown, unknown, Record<string, ParamDescriptor>>;
1255
+ /**
1256
+ * A map of definition name → {@link OperationDefinition}, as accepted by
1257
+ * `defineQueries` / `defineCommands` and returned (unchanged in type) from them.
1258
+ */
1259
+ type DefinitionMap = Record<string, AnyOperationDefinition>;
1260
+
1261
+ /**
1262
+ * Model write-semantics — the reusable *save contract* `entityWrites` (issue #83,
1263
+ * Epic #80; spec `docs/mutation-command-derivation.md` §2).
1264
+ *
1265
+ * `Model.writes` declares the **write-side invariants / effects required for this
1266
+ * entity to be consistent on DynamoDB** — a *reusable save contract*, NOT a
1267
+ * mutation and NOT business logic. It is a per-lifecycle map
1268
+ * (`{ create?, update?, remove? }`) whose values are {@link LifecycleContract}s
1269
+ * built with `w.lifecycle({...})`; each carries the §2 effect arrays
1270
+ * (`requires` / `unique` / `edges` / `derive` / `emits` / `idempotency`).
1271
+ *
1272
+ * ## What #83 does — and deliberately does NOT — with the effects
1273
+ *
1274
+ * The mutation compiler (#83) resolves a fragment's lifecycle from this save
1275
+ * contract (the fragment's intent — `m.create` / `m.update` / `m.remove` — picks
1276
+ * `create` / `update` / `remove`) and emits the **base write op** for that
1277
+ * lifecycle (create → `PutItem` with `attribute_not_exists(PK)`, update →
1278
+ * `UpdateItem`, remove → `DeleteItem`). The §2 effect arrays are **stored
1279
+ * opaquely** here and consumed by NONE of #83: deriving `ConditionCheck`
1280
+ * (requires, #84), uniqueness guards (#86), adjacency edge `Put` / `Delete` from
1281
+ * `edges` (#85), derived counter `UpdateItem`s (#85), outbox events (#87), and
1282
+ * the idempotency guard (#87) are each their own follow-up issue. #83 only shapes
1283
+ * the declaration and exposes the {@link LifecycleContract.effects} a later issue
1284
+ * reads — the **per-fragment hook** the compiler leaves empty (see
1285
+ * `compileFragment` in `src/spec/mutation-command.ts`).
1286
+ *
1287
+ * ## Coexistence with edge writes (`edgeWrites`, #82)
1288
+ *
1289
+ * This is a **distinct** construct from {@link edgeWrites} (#82, the adjacency
1290
+ * edge-only `writes` member). `edgeWrites` declares *only* the edge write side of
1291
+ * an adjacency entity; `entityWrites` is the full per-lifecycle save contract a
1292
+ * mutation fragment adopts. Both are recognized by their own brand, so a model
1293
+ * may carry either form on its static `writes` member without collision; the
1294
+ * mutation compiler reads {@link getEntityWrites} (this marker), the edge
1295
+ * derivation reads its own.
1296
+ *
1297
+ * ## Trivial base op when a model declares no `writes`
1298
+ *
1299
+ * A target model that declares **no** `entityWrites` save contract has no
1300
+ * lifecycle to resolve; the fragment then compiles the **trivial base op** for
1301
+ * its intent (create → `Put` `attribute_not_exists(PK)`, update → `Update`,
1302
+ * remove → `Delete`) directly against the plain model. So
1303
+ * `{ create: () => PostModel, key, input }` works on a bare model — see
1304
+ * {@link resolveLifecycle} in `src/spec/mutation-command.ts`.
1305
+ */
1306
+ /**
1307
+ * The lifecycle phase a save contract entry declares. The mutation fragment's
1308
+ * intent (`m.create` / `m.update` / `m.remove`) selects the matching entry.
1309
+ */
1310
+ type WriteLifecyclePhase = 'create' | 'update' | 'remove';
1311
+ /**
1312
+ * A path-rooted leaf value in a save-contract effect (proposal §2): every value
1313
+ * binds to an explicit path root so its source is unambiguous —
1314
+ * `$.input.*` (the mutation input) or `$.entity.*` (the written entity). #83
1315
+ * stores these verbatim (it consumes none); a follow-up issue (#84–#87) reads
1316
+ * them when deriving the effect's DynamoDB realization.
1317
+ */
1318
+ type EffectPath = string;
1319
+ /**
1320
+ * A referential-integrity requirement (proposal §2 `requires`): the named target
1321
+ * entity must exist for the keys bound from `$.input.*` / `$.entity.*`. Derived
1322
+ * to a `ConditionCheck` (`attribute_exists`) by #84 — **not** by #83, which only
1323
+ * stores it.
1324
+ */
1325
+ interface RequiresEffect {
1326
+ readonly kind: 'requires';
1327
+ /** Resolves the entity whose existence is asserted. */
1328
+ readonly targetFactory: () => new (...args: unknown[]) => unknown;
1329
+ /** The key binding: target key field → a path-rooted source value. */
1330
+ readonly keys: Readonly<Record<string, EffectPath>>;
1331
+ }
1332
+ /**
1333
+ * A uniqueness guard (proposal §2 `unique`): `{ name, scope, fields }` — a named
1334
+ * composite-scope uniqueness invariant. Derived to a `UNIQUE#…` guard `Put` with
1335
+ * `attribute_not_exists` by #86 — stored opaquely here.
1336
+ */
1337
+ interface UniqueEffect {
1338
+ readonly kind: 'unique';
1339
+ /** The guard name (its key-shape discriminator on DynamoDB). */
1340
+ readonly name: string;
1341
+ /** The scope the uniqueness is partitioned by (path-rooted values). */
1342
+ readonly scope: readonly EffectPath[];
1343
+ /** The fields whose combined value must be unique within the scope. */
1344
+ readonly fields: readonly EffectPath[];
1345
+ }
1346
+ /**
1347
+ * An edge write effect (proposal §2 `edges`): create / delete the adjacency row
1348
+ * that materializes a target relation. Derived to an adjacency `Put` / `Delete`
1349
+ * by #82/#85 (the `src/relation/edge-write.ts` primitive) — stored opaquely here.
1350
+ */
1351
+ interface EdgeEffect {
1352
+ readonly kind: 'putEdge' | 'deleteEdge';
1353
+ /** Resolves the target model whose relation this edge feeds. */
1354
+ readonly targetFactory: () => new (...args: unknown[]) => unknown;
1355
+ /** The relation property on the target that reads this edge. */
1356
+ readonly relationProperty: string;
1357
+ }
1358
+ /**
1359
+ * A derived (cascading) update (proposal §2 `derive`): e.g. `User.postCount +=
1360
+ * 1`. Derived to an `UpdateItem` `ADD` / `SET` in the transaction by #85 —
1361
+ * stored opaquely here.
1362
+ */
1363
+ interface DeriveEffect {
1364
+ readonly kind: 'derive';
1365
+ /** Resolves the entity whose attribute is derived-updated. */
1366
+ readonly targetFactory: () => new (...args: unknown[]) => unknown;
1367
+ /** The key binding of the updated row (path-rooted values). */
1368
+ readonly keys: Readonly<Record<string, EffectPath>>;
1369
+ /** The attribute to increment / set. */
1370
+ readonly attribute: string;
1371
+ /** The amount to add (for an increment). */
1372
+ readonly amount: number;
1373
+ }
1374
+ /**
1375
+ * A domain event (proposal §2 `emits`): an outbox `Put` drained to Streams /
1376
+ * `src/cdc/`. Derived by #87 — stored opaquely here.
1377
+ */
1378
+ interface EmitEffect {
1379
+ readonly kind: 'event';
1380
+ /** The event name (e.g. `PostCreated`). */
1381
+ readonly name: string;
1382
+ /** The event payload, each value path-rooted. */
1383
+ readonly payload: Readonly<Record<string, EffectPath>>;
1384
+ }
1385
+ /**
1386
+ * An idempotency guard (proposal §2 `idempotency`): a client-token guard item
1387
+ * (`attribute_not_exists`). Derived by #87 — stored opaquely here.
1388
+ */
1389
+ interface IdempotencyEffect {
1390
+ readonly kind: 'idempotency';
1391
+ /** The path-rooted token the guard keys on (e.g. `$.input.requestId`). */
1392
+ readonly token: EffectPath;
1393
+ }
1394
+ /**
1395
+ * The full §2 effect set a {@link LifecycleContract} carries. Every field is
1396
+ * optional; an omitted field is the empty effect set. #83 stores this verbatim
1397
+ * and consumes none — the per-fragment hook a later issue (#84–#87) reads.
1398
+ */
1399
+ interface LifecycleEffects {
1400
+ /** Referential-integrity requirements (→ `ConditionCheck`, #84). */
1401
+ readonly requires?: readonly RequiresEffect[];
1402
+ /** Uniqueness guards (→ `UNIQUE#…` guard `Put`, #86). */
1403
+ readonly unique?: readonly UniqueEffect[];
1404
+ /** Edge write effects (→ adjacency `Put` / `Delete`, #82/#85). */
1405
+ readonly edges?: readonly EdgeEffect[];
1406
+ /** Derived / cascading updates (→ `UpdateItem` `ADD`/`SET`, #85). */
1407
+ readonly derive?: readonly DeriveEffect[];
1408
+ /** Domain events (→ outbox `Put`, #87). */
1409
+ readonly emits?: readonly EmitEffect[];
1410
+ /** Idempotency guard (→ client-token guard `Put`, #87). */
1411
+ readonly idempotency?: IdempotencyEffect;
1412
+ }
1413
+ /**
1414
+ * Marker carried by a {@link LifecycleContract} so it is recognized regardless of
1415
+ * declaration shape. Built by {@link WriteRecorder.lifecycle}.
1416
+ */
1417
+ declare const LIFECYCLE_CONTRACT_MARKER: unique symbol;
1418
+ /**
1419
+ * One lifecycle's save contract — the §2 effect set for a single phase
1420
+ * (`create` / `update` / `remove`). #83 emits the lifecycle's **base write op**
1421
+ * (selected from the fragment intent) and leaves the {@link effects} as the
1422
+ * untouched per-fragment hook a later issue derives.
1423
+ */
1424
+ interface LifecycleContract {
1425
+ readonly [LIFECYCLE_CONTRACT_MARKER]: true;
1426
+ /** The §2 effect arrays, stored opaquely by #83. */
1427
+ readonly effects: LifecycleEffects;
1428
+ }
1429
+ /** Runtime guard: is `value` a {@link LifecycleContract} (a `w.lifecycle(...)`)? */
1430
+ declare function isLifecycleContract(value: unknown): value is LifecycleContract;
1431
+ /**
1432
+ * Marker carried by a model's static `writes` member when it is an
1433
+ * {@link EntityWritesDefinition} (an `entityWrites(...)` save contract), so it is
1434
+ * told apart from an {@link edgeWrites} member (#82) on the same conventional
1435
+ * property name.
1436
+ */
1437
+ declare const ENTITY_WRITES_MARKER: unique symbol;
1438
+ /**
1439
+ * The per-lifecycle save contract produced by {@link entityWrites}: a map from
1440
+ * {@link WriteLifecyclePhase} to its {@link LifecycleContract}. Every phase is
1441
+ * optional — a model may declare only the lifecycles it needs (e.g. a `create` +
1442
+ * `remove` save contract with no `update`).
1443
+ */
1444
+ interface EntityWritesDefinition {
1445
+ readonly [ENTITY_WRITES_MARKER]: true;
1446
+ /** The create-lifecycle save contract, if declared. */
1447
+ readonly create?: LifecycleContract;
1448
+ /** The update-lifecycle save contract, if declared. */
1449
+ readonly update?: LifecycleContract;
1450
+ /** The remove-lifecycle save contract, if declared. */
1451
+ readonly remove?: LifecycleContract;
1452
+ }
1453
+ /** Runtime guard: is `value` an {@link EntityWritesDefinition}? */
1454
+ declare function isEntityWritesDefinition(value: unknown): value is EntityWritesDefinition;
1455
+ /**
1456
+ * The recorder handed to the {@link entityWrites} builder. `w.lifecycle({...})`
1457
+ * accepts the §2 effect arrays and brands them into a {@link LifecycleContract}.
1458
+ * #83 stores the effects opaquely; #84–#87 read them.
1459
+ */
1460
+ interface WriteRecorder {
1461
+ /**
1462
+ * Build one lifecycle's save contract from its §2 effect arrays. Every effect
1463
+ * field is optional; an omitted field is an empty effect set. The returned
1464
+ * contract is branded so the compiler can resolve it from the model's `writes`
1465
+ * map by lifecycle phase.
1466
+ */
1467
+ lifecycle(effects?: LifecycleEffects): LifecycleContract;
1468
+ /** Declare a referential-integrity requirement (§2 `requires`; derived #84). */
1469
+ exists(target: () => new (...args: unknown[]) => unknown, keys: Readonly<Record<string, EffectPath>>): RequiresEffect;
1470
+ /** Declare a uniqueness guard (§2 `unique`; derived #86). */
1471
+ unique(spec: {
1472
+ readonly name: string;
1473
+ readonly scope: readonly EffectPath[];
1474
+ readonly fields: readonly EffectPath[];
1475
+ }): UniqueEffect;
1476
+ /** Declare an edge create effect (§2 `edges`; derived #82/#85). */
1477
+ putEdge(target: () => new (...args: unknown[]) => unknown, relationProperty: string): EdgeEffect;
1478
+ /** Declare an edge delete effect (§2 `edges`; derived #82/#85). */
1479
+ deleteEdge(target: () => new (...args: unknown[]) => unknown, relationProperty: string): EdgeEffect;
1480
+ /** Declare a derived / cascading update (§2 `derive`; derived #85). */
1481
+ increment(target: () => new (...args: unknown[]) => unknown, keys: Readonly<Record<string, EffectPath>>, attribute: string, amount: number): DeriveEffect;
1482
+ /** Declare a domain event (§2 `emits`; derived #87). */
1483
+ event(name: string, payload: Readonly<Record<string, EffectPath>>): EmitEffect;
1484
+ /** Declare an idempotency guard (§2 `idempotency`; derived #87). */
1485
+ idempotentBy(token: EffectPath): IdempotencyEffect;
1486
+ }
1487
+ /**
1488
+ * The lifecycle map a {@link entityWrites} builder returns: each phase is either
1489
+ * a built {@link LifecycleContract} (`w.lifecycle({...})`) or omitted.
1490
+ */
1491
+ interface EntityWritesShape {
1492
+ readonly create?: LifecycleContract;
1493
+ readonly update?: LifecycleContract;
1494
+ readonly remove?: LifecycleContract;
1495
+ }
1496
+ /**
1497
+ * Declare a model's reusable **save contract** (issue #83, proposal §2). The
1498
+ * builder receives a {@link WriteRecorder} and returns the per-lifecycle map
1499
+ * (`{ create?, update?, remove? }`), each value a `w.lifecycle({...})` carrying
1500
+ * the §2 effect arrays. The result is stored on the model as
1501
+ * `static readonly writes = entityWrites<Model>((w) => ({ … }))`.
1502
+ *
1503
+ * #83 reads only **which** lifecycles are declared (to resolve a fragment's base
1504
+ * write op by intent) and stores the effect arrays opaquely; the effect
1505
+ * derivation is #84–#87.
1506
+ *
1507
+ * @typeParam M The model type the save contract belongs to (documentary; the
1508
+ * recorder is structural).
1509
+ * @example
1510
+ * ```ts
1511
+ * static readonly writes = entityWrites<PostModel>((w) => ({
1512
+ * create: w.lifecycle({
1513
+ * requires: [w.exists(() => UserModel, { userId: '$.input.userId' })],
1514
+ * edges: [w.putEdge(() => UserModel, 'posts')],
1515
+ * }),
1516
+ * remove: w.lifecycle({ edges: [w.deleteEdge(() => UserModel, 'posts')] }),
1517
+ * }));
1518
+ * ```
1519
+ */
1520
+ declare function entityWrites<M = unknown>(builder: (w: WriteRecorder) => EntityWritesShape): EntityWritesDefinition;
1521
+ /**
1522
+ * Read the {@link EntityWritesDefinition} declared on a model class as its static
1523
+ * `writes` member, or `undefined` when the model declares no `entityWrites` save
1524
+ * contract. Scans the class's own static members for the {@link
1525
+ * ENTITY_WRITES_MARKER}, exactly as {@link getEdgeWrites} (#82) scans for the
1526
+ * edge marker — so the two `writes` forms coexist without a fixed property name
1527
+ * collision (a model carries one or the other; the marker disambiguates).
1528
+ */
1529
+ declare function getEntityWrites(modelClass: new (...args: unknown[]) => unknown): EntityWritesDefinition | undefined;
1530
+ /** The lifecycle phase a mutation fragment intent selects. */
1531
+ declare function lifecyclePhaseForIntent(intent: 'create' | 'update' | 'remove'): WriteLifecyclePhase;
1532
+
1533
+ /**
1534
+ * Internal write-plan composition DSL — `mutation` / `definePlan` (issue #83,
1535
+ * Epic #80; spec `docs/mutation-command-derivation.md` §1, §3).
1536
+ *
1537
+ * A **mutation is INTERNAL** — it is *not* a public API. It is a *write-plan
1538
+ * composition language* used as the **implementation** of a Command: it declares
1539
+ * a set of write **fragments** that must execute atomically. The only externally
1540
+ * exposed surface is the fixed Command IF (`publicCommandModel(...).plan(...)`,
1541
+ * `src/define/contract.ts`); the mutation document is never exposed.
1542
+ *
1543
+ * The authoring form (issue #108) is a **descriptor map**: the body returns an
1544
+ * **alias → write-descriptor** object, the declarative twin of an in-process
1545
+ * `DDBModel.mutate(...)` call (the only difference is the values are `$.field`
1546
+ * placeholders rather than concrete values):
1547
+ *
1548
+ * ```ts
1549
+ * const JoinGroup = mutation($ => ({
1550
+ * membership: { create: GroupMembership, key: { groupId: $.groupId, userId: $.userId }, input: { role: $.role } },
1551
+ * permission: { create: () => Permission, key: { id: $.permId }, input: { groupId: $.membership.groupId } },
1552
+ * }));
1553
+ * ```
1554
+ *
1555
+ * ## Descriptors are DECLARATIONS, not executed calls
1556
+ *
1557
+ * Each descriptor is a write **fragment** declaration, never an executed write.
1558
+ * The intent key (`create` / `update` / `remove`) is the **discriminator** and
1559
+ * carries the target model (directly, or as a `() => Model` thunk — the
1560
+ * forward/circular-reference escape hatch); `key` / `input` bind model fields to
1561
+ * `$.field` placeholders (or literals). The compiler
1562
+ * (`src/spec/mutation-command.ts`) expands the captured {@link MutationFragment}
1563
+ * list at compile time into a {@link CommandSpec} / `TransactionSpec`. The map's
1564
+ * **insertion order** is the fragments' declaration (execution) order, so the IR
1565
+ * is the SAME ordered `{ name, fragments }` shape the legacy recorder produced —
1566
+ * the single-fragment fast path (#83) and the N-fragment atomic merge (#90) are
1567
+ * unchanged.
1568
+ *
1569
+ * ## Intent selects the lifecycle; `use:` is optional
1570
+ *
1571
+ * Each fragment's **intent** (`create` / `update` / `remove`) selects the
1572
+ * lifecycle from the target's `writes` save contract (`entityWrites`, §2):
1573
+ * `create` → `writes.create`, etc. `use:` is **optional** — it defaults to the
1574
+ * target model's own `writes`; supply it only to adopt a *custom* write contract,
1575
+ * and it takes the **whole** `writes` set (an {@link EntityWritesDefinition}),
1576
+ * never `writes.create` (the intent already picks the lifecycle, so naming the
1577
+ * lifecycle would double-specify it). When the target declares **no** `writes`,
1578
+ * the fragment compiles the **trivial base op** for its intent (create → `Put`
1579
+ * `attribute_not_exists(PK)`, update → `Update`, remove → `Delete`).
1580
+ *
1581
+ * The `$` argument is the **placeholder proxy**: `$.userId` at a `key` / `input`
1582
+ * leaf mints a faithful {@link MutationInputRef} naming an input field, and
1583
+ * `$.alias.field` names a value an **earlier** fragment (by alias) writes — a
1584
+ * cross-fragment reference resolved at compile time (the legacy `$.entity[i].field`
1585
+ * positional form, now alias-based). No arbitrary JS may reach a binding, exactly
1586
+ * as the contract `params` sentinel enforces.
1587
+ */
1588
+
1589
+ /**
1590
+ * The intent of a write fragment (proposal §3): `create` / `update` / `remove`
1591
+ * (`remove`, **not** `del`). The intent selects the lifecycle from the target's
1592
+ * save contract, and the base write op (create → `PutItem`, update →
1593
+ * `UpdateItem`, remove → `DeleteItem`).
1594
+ */
1595
+ type MutationIntent = 'create' | 'update' | 'remove';
1596
+ declare const INPUT_REF_BRAND: unique symbol;
1597
+ /**
1598
+ * A faithful reference to a mutation **input** field, minted by reading `$.<field>`
1599
+ * in a mutation body (e.g. `$.title`). Branded so the compiler tells a faithful
1600
+ * input reference from a literal and renders its `{token}` (`{title}`) into the
1601
+ * derived write template — the declarative binding the proposal's `$.input.*`
1602
+ * path root names (here the root is implied: a fragment `input` value is always
1603
+ * an input field reference or a concrete literal).
1604
+ */
1605
+ interface MutationInputRef {
1606
+ readonly [INPUT_REF_BRAND]: true;
1607
+ /** The input field name, e.g. `title`. */
1608
+ readonly field: string;
1609
+ /** The template token this reference renders to, e.g. `{title}`. */
1610
+ readonly token: string;
1611
+ }
1612
+ /** Runtime guard: is `value` a {@link MutationInputRef} (a `$.<field>` read)? */
1613
+ declare function isMutationInputRef(value: unknown): value is MutationInputRef;
1614
+ declare const ENTITY_REF_BRAND: unique symbol;
1615
+ /**
1616
+ * A faithful **cross-fragment reference** (`$.entity[i].field`): the value a later
1617
+ * fragment binds from an **earlier** fragment's written field (proposal §3). The
1618
+ * compiler ({@link compileMutationPlan}) resolves it at **compile time** to the
1619
+ * earlier fragment's binding for that field — a `TransactWriteItems` cannot read
1620
+ * one item's value mid-transaction, so the dependency is resolved declaratively
1621
+ * from the shared input (the referenced field must itself trace to an input
1622
+ * reference or a literal), keeping the composed transaction render-time-resolvable
1623
+ * and atomic. A **forward / self** reference (to a same-or-later fragment) is a
1624
+ * circular dependency, rejected at build time.
1625
+ */
1626
+ interface MutationEntityRef {
1627
+ readonly [ENTITY_REF_BRAND]: true;
1628
+ /** The 0-based index of the producing fragment (declaration order). */
1629
+ readonly fragmentIndex: number;
1630
+ /** The produced field name on that fragment's written entity. */
1631
+ readonly field: string;
1632
+ /** The source path, e.g. `$.entity[0].postId` (documentary; surfaces in errors). */
1633
+ readonly path: string;
1634
+ }
1635
+ /**
1636
+ * The placeholder proxy handed to a mutation body as `$`. In the descriptor-map
1637
+ * authoring form (issue #108) a top-level read `$.<x>` is **ambiguous** until used:
1638
+ *
1639
+ * - `$.role` placed at a `key` / `input` leaf is an **input-field reference**
1640
+ * (a {@link MutationInputRef}, token `{role}`) — bound from the command input;
1641
+ * - `$.membership.role` is a **cross-fragment reference** — the `role` field
1642
+ * written by the fragment named `membership` (an alias). It mints an
1643
+ * {@link AliasFieldRef}, which the {@link mutation} assembler rewrites into the
1644
+ * legacy `$.entity[<index>].<field>` string the compiler resolves (the alias →
1645
+ * declaration-order index is known once every alias key is seen).
1646
+ *
1647
+ * So each top-level read returns a value that is **both** a faithful
1648
+ * {@link MutationInputRef} (when used directly as a leaf) and supports one further
1649
+ * `.<field>` access (when used as an alias root). Any other access / coercion
1650
+ * throws — the binding stays declarative (only a direct `$.field` / `$.alias.field`
1651
+ * reference or a literal is representable, never a transform).
1652
+ */
1653
+ type MutationInputProxy = Record<string, MutationInputRef>;
1654
+ declare const FRAGMENT_BRAND: unique symbol;
1655
+ /**
1656
+ * The per-field input binding of a fragment: model field name → a faithful
1657
+ * {@link MutationInputRef} (a `$.<field>` reference) or a concrete scalar literal
1658
+ * (e.g. a fixed discriminator). One declarative level — the values bind the model
1659
+ * field they key, never a transform.
1660
+ */
1661
+ type FragmentInput = Readonly<Record<string, MutationInputRef | string | number | boolean | Date | null>>;
1662
+ /**
1663
+ * A descriptor `key` / `input` value as authored in the descriptor-map form
1664
+ * (issue #108): a `$.field` input reference, a `$.alias.field` cross-fragment
1665
+ * reference (an opaque alias-field ref the assembler rewrites), or a literal.
1666
+ * Structurally `unknown` at the leaf because the alias-field ref is internal; the
1667
+ * assembler validates every leaf via {@link assertFaithfulInput}.
1668
+ */
1669
+ type DescriptorBinding = Readonly<Record<string, unknown>>;
1670
+ /**
1671
+ * One recorded write fragment (proposal §3): an **intent** + a target entity + an
1672
+ * optional adopted save contract (`use:`) + the input binding. It is a
1673
+ * *declaration* the compiler expands — never an executed write.
1674
+ *
1675
+ * - `intent` — `create` / `update` / `remove`; selects the lifecycle + base op.
1676
+ * - `entity` — the target entity (resolved at definition time).
1677
+ * - `input` — the model-field → input-field / literal binding (declarative).
1678
+ * - `use` — the adopted {@link EntityWritesDefinition} (whole set), or `undefined`
1679
+ * to default to the target model's own `writes` (and, absent that, the trivial
1680
+ * base op). Per the `use:` rule it is **never** a single lifecycle.
1681
+ */
1682
+ interface MutationFragment {
1683
+ readonly [FRAGMENT_BRAND]: true;
1684
+ readonly intent: MutationIntent;
1685
+ readonly entity: EntityRef;
1686
+ readonly input: FragmentInput;
1687
+ readonly use?: EntityWritesDefinition;
1688
+ }
1689
+ /** Runtime guard: is `value` a {@link MutationFragment}? */
1690
+ declare function isMutationFragment(value: unknown): value is MutationFragment;
1691
+ /**
1692
+ * A model reference in a write descriptor (issue #108): either the model class /
1693
+ * `.asModel()` value **directly** (`{ create: GroupMembership }`) or a **thunk**
1694
+ * (`{ create: () => GroupMembership }`). The thunk form is the escape hatch that
1695
+ * preserves forward / circular references — a model not yet defined at the
1696
+ * descriptor's evaluation point is named through a closure, exactly as the legacy
1697
+ * `m.create(() => Model, …)` recorder required.
1698
+ */
1699
+ type ModelRef = ModelStatic<DDBModel> | (new (...args: unknown[]) => unknown) | (() => ModelStatic<DDBModel> | (new (...args: unknown[]) => unknown));
1700
+ /**
1701
+ * One **write descriptor** in the descriptor-map authoring form (issue #108) —
1702
+ * the declarative twin of an in-process `DDBModel.mutate` entry (#101). The intent
1703
+ * key (`create` / `update` / `remove`) is the **discriminator** and carries the
1704
+ * target {@link ModelRef} (direct or thunk). The remaining fields are declarative
1705
+ * bindings (`$.field` references or literals):
1706
+ *
1707
+ * - `key` — the target row's primary-key binding (identifies the row);
1708
+ * - `input` — the non-key field binding (the body / changed fields). Optional for
1709
+ * a `remove` (a delete writes no fields);
1710
+ * - `condition` — an optional declarative write gate (accepted for authoring
1711
+ * parity with the #101 descriptor; threaded by #101's in-process executor — the
1712
+ * declaration-DSL compiler does not consume it, so the serialized IR is
1713
+ * unchanged);
1714
+ * - `result` — an optional read-back projection `{ select, options? }` (likewise
1715
+ * accepted for authoring parity; the public read-back projection of a
1716
+ * `command(...).plan(...)` method is still declared on `command({ select })`).
1717
+ *
1718
+ * Exactly one intent key must be present.
1719
+ */
1720
+ interface WriteDescriptor {
1721
+ readonly create?: ModelRef;
1722
+ readonly update?: ModelRef;
1723
+ readonly remove?: ModelRef;
1724
+ /** The target row's primary-key binding (`{ field: $.field | literal }`). */
1725
+ readonly key: DescriptorBinding;
1726
+ /** The non-key field binding; optional (a `remove` writes no fields). */
1727
+ readonly input?: DescriptorBinding;
1728
+ /** Optional declarative write gate (accepted for #101 parity; see above). */
1729
+ readonly condition?: DescriptorBinding;
1730
+ /** Optional read-back projection `{ select, options? }` (accepted for #101 parity). */
1731
+ readonly result?: {
1732
+ readonly select?: unknown;
1733
+ readonly options?: unknown;
1734
+ };
1735
+ /**
1736
+ * Optional custom save contract to adopt (the **whole** {@link
1737
+ * EntityWritesDefinition}). Omit to default to the target model's own `writes`.
1738
+ * Per the `use:` rule this is never a single lifecycle — the intent picks it.
1739
+ */
1740
+ readonly use?: EntityWritesDefinition;
1741
+ }
1742
+ /** The descriptor map a {@link mutation} body returns: alias → {@link WriteDescriptor}. */
1743
+ type MutationDescriptorMap = Record<string, WriteDescriptor>;
1744
+ declare const COMMAND_PLAN_BRAND: unique symbol;
1745
+ /**
1746
+ * The recorded **CommandPlan** — a mutation's IR (proposal §3). It carries the
1747
+ * mutation `name` and the **list** of {@link MutationFragment}s the body declared
1748
+ * (1..N; #83 compiles the 1-fragment case end-to-end, the list shape is ready for
1749
+ * the #90 N-fragment merge). The body closure is discarded — only the declarative
1750
+ * fragment list is kept, exactly as the contract IR discards its method closures.
1751
+ */
1752
+ interface CommandPlan {
1753
+ readonly [COMMAND_PLAN_BRAND]: true;
1754
+ /** The mutation name (documentary; surfaces in compiler error messages). */
1755
+ readonly name: string;
1756
+ /** The declared write fragments, in declaration order (the mutation IR). */
1757
+ readonly fragments: readonly MutationFragment[];
1758
+ }
1759
+ /** Runtime guard: is `value` a {@link CommandPlan} (a `mutation(...)` result)? */
1760
+ declare function isCommandPlan(value: unknown): value is CommandPlan;
1761
+ /**
1762
+ * The body of a {@link mutation} in the descriptor-map authoring form (issue #108):
1763
+ * `$ => ({ alias: descriptor, … })`. It receives the placeholder proxy `$` and
1764
+ * returns an **alias → {@link WriteDescriptor}** map. The map's **insertion order**
1765
+ * is the fragments' declaration (execution) order, and each alias is the name a
1766
+ * later descriptor's `$.alias.field` cross-fragment reference resolves against.
1767
+ */
1768
+ type MutationBody = ($: MutationInputProxy) => MutationDescriptorMap;
1769
+ /**
1770
+ * Declare an internal **mutation** — a write-plan composition in the descriptor-map
1771
+ * authoring form (issue #108). The body returns an **alias → {@link WriteDescriptor}**
1772
+ * map; each descriptor names its target via the intent key (`create` / `update` /
1773
+ * `remove`: a model or a `() => Model` thunk) and binds `key` / `input` with `$.field`
1774
+ * placeholders. Insertion order is execution order, and a later descriptor may read
1775
+ * an earlier one's written field via `$.alias.field`.
1776
+ *
1777
+ * ```ts
1778
+ * const JoinGroup = mutation($ => ({
1779
+ * membership: { create: GroupMembership, key: { groupId: $.groupId, userId: $.userId }, input: { role: $.role } },
1780
+ * permission: { create: Permission, key: { id: $.permissionId }, input: { groupId: $.membership.groupId } },
1781
+ * }));
1782
+ * ```
1783
+ *
1784
+ * A leading **name** is optional (it surfaces only in compiler error messages — the
1785
+ * alias map already names each fragment): `mutation('JoinGroup', $ => ({ … }))`.
1786
+ *
1787
+ * The result is a branded {@link CommandPlan} (the mutation IR) — *not* public; it
1788
+ * implements a Command IF via `publicCommandModel(...).plan(mutation)`. The IR shape
1789
+ * (`{ name, fragments }`) is unchanged from the legacy recorder form, so the compiler,
1790
+ * serializer, and every runtime consume it identically.
1791
+ *
1792
+ * @throws if the body returns a non-object / empty map, a descriptor has no (or
1793
+ * multiple) intent key(s), a binding is non-declarative, or a `$.alias.field`
1794
+ * reference names an unknown alias.
1795
+ */
1796
+ declare function mutation(body: MutationBody): CommandPlan;
1797
+ declare function mutation(name: string, body: MutationBody): CommandPlan;
1798
+ /**
1799
+ * Alias of {@link mutation} (proposal naming: the internal DSL is `mutation` /
1800
+ * `definePlan`). Identical behavior; provided so a producer may name the plan
1801
+ * declaration `definePlan(…)` when that reads more clearly at the call site.
1802
+ */
1803
+ declare const definePlan: typeof mutation;
1804
+
1805
+ /**
1806
+ * Serializable static operation specs and manifest (issue #42, Python-bridge
1807
+ * Phase 0b).
1808
+ *
1809
+ * Everything in this module is **JSON-serializable** by construction (only
1810
+ * strings / numbers / booleans / arrays / plain objects — no functions, classes,
1811
+ * `Date`, `Set`, or `undefined`-valued fields are emitted). These types mirror
1812
+ * the `graphddb_manifest.json` / `graphddb_operations.json` shapes sketched in
1813
+ * `docs/python-bridge.md`. The generator (#43) writes them to disk; the
1814
+ * Python runtime (#44+) interprets them.
1815
+ *
1816
+ * ## Templates and placeholders
1817
+ *
1818
+ * Key-condition / range / item / change *values* are **template strings** with
1819
+ * two placeholder forms:
1820
+ *
1821
+ * - `{paramName}` — bound from the caller-supplied params at execution time.
1822
+ * - `{result.sourceField}` — bound from a field of the **prior** operation's
1823
+ * result item(s), used to chain relation operations.
1824
+ *
1825
+ * A value with no `{...}` is a literal (e.g. a fixed sort-key discriminator
1826
+ * `PROFILE`).
1827
+ */
1828
+
1829
+ /** The schema version emitted in every manifest / operations document. */
1830
+ declare const SPEC_VERSION: "1.0";
1831
+ /** Field type as carried in the manifest (derived from the DynamoDB type). */
1832
+ type ManifestFieldType = 'string' | 'number' | 'boolean' | 'binary' | 'stringSet' | 'numberSet' | 'list' | 'map';
1833
+ interface ManifestField {
1834
+ readonly type: ManifestFieldType;
1835
+ /**
1836
+ * Semantic format for `string` fields that carry a serialized temporal value.
1837
+ * `datetime` → ISO 8601 instant, `date` → `YYYY-MM-DD`. Absent for plain
1838
+ * fields. The Python runtime uses this to restore `datetime` on hydration,
1839
+ * mirroring the TS hydrator's `format`-driven `Date` reconstruction.
1840
+ */
1841
+ readonly format?: 'datetime' | 'date';
1842
+ }
1843
+ /** A key (PK or GSI) template descriptor in the manifest. */
1844
+ interface ManifestKey {
1845
+ /** Input field names the key is composed from (in declaration order). */
1846
+ readonly inputFields: readonly string[];
1847
+ /** Template for the partition-key value, e.g. `EMAIL#{email}`. */
1848
+ readonly pkTemplate: string;
1849
+ /** Template for the sort-key value, or `null` when the key has no sort key. */
1850
+ readonly skTemplate: string | null;
1851
+ }
1852
+ interface ManifestGsi extends ManifestKey {
1853
+ readonly indexName: string;
1854
+ readonly unique: boolean;
1855
+ }
1856
+ interface ManifestRelation {
1857
+ readonly type: 'hasMany' | 'hasOne' | 'belongsTo';
1858
+ /** Target entity (model class) name. */
1859
+ readonly target: string;
1860
+ /** target field → source field (on this entity). */
1861
+ readonly keyBinding: Readonly<Record<string, string>>;
1862
+ }
1863
+ interface ManifestEntity {
1864
+ /** Declared (logical) table name. */
1865
+ readonly table: string;
1866
+ /** Physical table name (after `TableMapping` resolution). */
1867
+ readonly physicalName: string;
1868
+ /** PK prefix, e.g. `USER#`. */
1869
+ readonly prefix: string;
1870
+ readonly fields: Readonly<Record<string, ManifestField>>;
1871
+ /** Primary key, or `null` when the entity has none. */
1872
+ readonly key: ManifestKey | null;
1873
+ readonly gsis: readonly ManifestGsi[];
1874
+ readonly relations: Readonly<Record<string, ManifestRelation>>;
1875
+ }
1876
+ interface ManifestTable {
1877
+ readonly physicalName: string;
1878
+ }
1879
+ interface Manifest {
1880
+ readonly version: typeof SPEC_VERSION;
1881
+ readonly tables: Readonly<Record<string, ManifestTable>>;
1882
+ readonly entities: Readonly<Record<string, ManifestEntity>>;
1883
+ }
1884
+ interface ParamSpec {
1885
+ readonly type: ParamKind;
1886
+ readonly required: boolean;
1887
+ /** Allowed literal values for `literal` params; omitted otherwise. */
1888
+ readonly literals?: readonly (string | number)[];
1889
+ /**
1890
+ * For an `array` param (transaction `forEach` source), the per-element field
1891
+ * descriptors (field name → element param spec). Omitted for scalar params.
1892
+ */
1893
+ readonly element?: Readonly<Record<string, ParamSpec>>;
1894
+ }
1895
+ /** A `begins_with` range condition on a sort key (templated value). */
1896
+ interface RangeConditionSpec {
1897
+ readonly operator: 'begins_with';
1898
+ readonly key: string;
1899
+ /** Template value, e.g. `GROUP#` or `GROUP#{result.groupId}`. */
1900
+ readonly value: string;
1901
+ }
1902
+ /** Server-side declarative filter, carried verbatim for the runtime to compile. */
1903
+ interface FilterSpec {
1904
+ /** The declarative {@link FilterInput} tree (JSON-safe). */
1905
+ readonly declarative: unknown;
1906
+ }
1907
+ type ReadOperationType = 'Query' | 'GetItem' | 'BatchGetItem';
1908
+ /**
1909
+ * A single static read operation. Relation chains are expressed as multiple
1910
+ * operations whose `resultPath` / templated key conditions wire them together.
1911
+ */
1912
+ interface OperationSpec {
1913
+ readonly type: ReadOperationType;
1914
+ readonly tableName: string;
1915
+ readonly indexName?: string;
1916
+ /**
1917
+ * Key condition: attribute name → template value. For a `BatchGetItem` this is
1918
+ * the per-key shape (one key per resolved source item at runtime).
1919
+ */
1920
+ readonly keyCondition: Readonly<Record<string, string>>;
1921
+ readonly rangeCondition?: RangeConditionSpec;
1922
+ /** Projected fields (entity field names). */
1923
+ readonly projection: readonly string[];
1924
+ readonly limit?: number;
1925
+ readonly filter?: FilterSpec;
1926
+ /**
1927
+ * Where the result of this operation is placed in the assembled result.
1928
+ * `$` is the root; `$.groups.items` a relation connection's items, etc.
1929
+ */
1930
+ readonly resultPath: string;
1931
+ /**
1932
+ * For relation operations: the source field on the prior result whose value(s)
1933
+ * drive this operation's `{result.*}` placeholders. Absent on the root op.
1934
+ */
1935
+ readonly sourceField?: string;
1936
+ }
1937
+ /**
1938
+ * The **execution plan** for a multi-operation read (issue #70a). It is the
1939
+ * Single Source of Truth for *how* a query's {@link OperationSpec}[] is staged:
1940
+ * which operations are independent (may run concurrently) and which must wait for
1941
+ * a prior operation's result. Every runtime that honors it produces identical
1942
+ * effects with identical concurrency discipline — the staging is **serialized,
1943
+ * not re-derived** (cf. the contract decided-facts discipline).
1944
+ *
1945
+ * - `groups` is an ordered list of **stages**; each stage is a list of indices
1946
+ * into the query's `operations[]`. Operations **within** a stage are mutually
1947
+ * independent and may be issued concurrently; stages run **in order** because a
1948
+ * later stage reads a `{result.*}` value produced by an earlier one. Stage 0 is
1949
+ * always `[0]` (the root). Every operation index appears in exactly one stage,
1950
+ * and the stages are a topological layering of the result-dependency graph.
1951
+ * - `concurrency` is the declared in-flight bound a runtime applies **within**
1952
+ * each stage (the shared {@link RELATION_TRAVERSAL_CONCURRENCY} = 16): at most
1953
+ * this many operations of a stage are issued at once.
1954
+ *
1955
+ * ## Derivation (see `buildReadOperations` / `deriveExecutionPlan`)
1956
+ *
1957
+ * The planner emits `operations[]` in a deterministic pre-order: the root at
1958
+ * index 0, then each relation child immediately followed by its own descendants.
1959
+ * A child's key templates reference `{result.<sourceField>}` of the **operation
1960
+ * whose `resultPath` is the child's parent path** — that producer is the child's
1961
+ * sole dependency. The stage of an operation is therefore `1 + stage(parent)`:
1962
+ * the root is stage 0; sibling relations that share a parent path land in the
1963
+ * **same** stage (no inter-`{result.*}` dependency between siblings, so they are
1964
+ * independent and concurrency-eligible); a grandchild that reads its parent's
1965
+ * `{result.*}` lands one stage later. This mirrors exactly the level-by-level
1966
+ * fan-out the TS relation runtime performs at runtime (`resolveRelations` issues
1967
+ * sibling relations together under the same bound) — #70a only *records* it.
1968
+ *
1969
+ * Absent on a single-operation spec and on specs produced before #70a; a consumer
1970
+ * without a plan falls back to one-operation-per-stage **sequential** execution
1971
+ * (the pre-#70 behavior), so an absent plan never changes results.
1972
+ */
1973
+ interface ExecutionPlanSpec {
1974
+ /** Ordered stages; each stage is indices into `operations[]` (stage 0 = `[0]`). */
1975
+ readonly groups: readonly (readonly number[])[];
1976
+ /** The declared in-flight bound applied within each stage (16). */
1977
+ readonly concurrency: number;
1978
+ }
1979
+ /** A query (read) definition's full execution spec. */
1980
+ interface QuerySpec {
1981
+ readonly params: Readonly<Record<string, ParamSpec>>;
1982
+ readonly operations: readonly OperationSpec[];
1983
+ /**
1984
+ * Result cardinality of the **root** (`$`) operation, derived from the
1985
+ * definition kind: `'one'` for `defineQuery` (a single entity object, with any
1986
+ * relations attached directly), `'many'` for `defineList` (a `{ items, cursor }`
1987
+ * connection). The relation runtime (#45) uses this to shape the root result:
1988
+ * a `'one'` Query root takes the first matched item rather than returning a
1989
+ * connection. Absent in specs produced before #45; consumers should treat an
1990
+ * absent value as `'many'` for a `Query`/`BatchGetItem` root and `'one'` for a
1991
+ * `GetItem` root (the pre-#45 behavior).
1992
+ */
1993
+ readonly cardinality?: 'one' | 'many';
1994
+ /**
1995
+ * The staged execution plan (issue #70a): which {@link operations} are
1996
+ * independent (concurrency-eligible) vs. result-dependent. Present only when the
1997
+ * query has more than one operation (a relation chain); a single-operation read
1998
+ * needs no plan. See {@link ExecutionPlanSpec} for the derivation and the
1999
+ * backward-compatible (plan-absent → sequential) fallback.
2000
+ */
2001
+ readonly executionPlan?: ExecutionPlanSpec;
2002
+ }
2003
+ type WriteOperationType = 'PutItem' | 'UpdateItem' | 'DeleteItem';
2004
+ /**
2005
+ * A condition expression on a write, as a JSON-serializable spec (issues #46,
2006
+ * #81). The supported subset:
2007
+ *
2008
+ * - `{ kind: 'notExists' }` → `attribute_not_exists(PK)` (legacy whole-row guard).
2009
+ * - `{ kind: 'attributeExists'; field }` → `attribute_exists(<field>)` — the named
2010
+ * attribute (any field, incl. PK/SK) must be present. The foundation for
2011
+ * referential-integrity derivation.
2012
+ * - `{ kind: 'attributeNotExists'; field }` → `attribute_not_exists(<field>)` — the
2013
+ * named attribute must be absent (field-level uniqueness / first-write guard).
2014
+ * - `{ kind: 'equals'; fields }` → `#f = :v AND …` field equality (each value a
2015
+ * `{param}` / literal template).
2016
+ * - `{ kind: 'expr'; declarative }` → the full declarative operator tree (issue
2017
+ * #114-A): comparison (`gt`/`ge`/`lt`/`le`/`ne`), `between`, `in`,
2018
+ * `begins_with`/`contains`/`notContains`, `attributeType`, and the logical
2019
+ * `and`/`or`/`not` groups, mirroring the read-side {@link FilterSpec}. The tree
2020
+ * is JSON-safe: each leaf value is either a native literal (string / number /
2021
+ * boolean) or a {@link ConditionParamLeaf} (`{ $param }`) marking a caller
2022
+ * param bound at execution time. The runtime resolves the param leaves against
2023
+ * the caller params, then compiles the tree to a DynamoDB `ConditionExpression`
2024
+ * with the SAME mechanics the filter compiler uses (so TS and Python emit an
2025
+ * identical expression / semantics).
2026
+ */
2027
+ type ConditionSpec = {
2028
+ readonly kind: 'notExists';
2029
+ } | {
2030
+ readonly kind: 'attributeExists';
2031
+ readonly field: string;
2032
+ } | {
2033
+ readonly kind: 'attributeNotExists';
2034
+ readonly field: string;
2035
+ } | {
2036
+ readonly kind: 'equals';
2037
+ readonly fields: Readonly<Record<string, string>>;
2038
+ } | {
2039
+ readonly kind: 'expr';
2040
+ readonly declarative: unknown;
2041
+ } | {
2042
+ /**
2043
+ * A raw DynamoDB condition produced by the `cond` escape hatch (issue
2044
+ * #114-B), for write conditions that the declarative operator subset
2045
+ * cannot express. It is **pre-compiled and deterministic**: the
2046
+ * `expression` is a finished DynamoDB `ConditionExpression` whose name
2047
+ * placeholders are stable `#cr_<field>` aliases (reused per distinct
2048
+ * column) and whose value placeholders are sequential `:cr0`, `:cr1`, …
2049
+ * (assigned in template order), so the serialized golden is stable. The
2050
+ * names map binds each `#cr_<field>` alias to its entity field; the values
2051
+ * map binds each `:crN` alias to either a native literal (an embedded
2052
+ * `cond` value — in-process the concrete value) or a {@link ConditionParamLeaf}
2053
+ * (`{ $param }`) marker bound from a caller param at execution time (the
2054
+ * public-contract slot). The runtime substitutes the param leaves, then
2055
+ * attaches `expression` / `names` / serialized `values` to the write —
2056
+ * TS and Python build the identical DynamoDB expression.
2057
+ */
2058
+ readonly kind: 'raw';
2059
+ readonly expression: string;
2060
+ readonly names: Readonly<Record<string, string>>;
2061
+ readonly values: Readonly<Record<string, unknown>>;
2062
+ };
2063
+ interface CommandSpec {
2064
+ readonly type: WriteOperationType;
2065
+ readonly tableName: string;
2066
+ readonly entity: string;
2067
+ readonly params: Readonly<Record<string, ParamSpec>>;
2068
+ /**
2069
+ * Key condition for `UpdateItem` / `DeleteItem` (attribute name → template).
2070
+ * Absent for `PutItem` (the whole item is built from `item`).
2071
+ */
2072
+ readonly keyCondition?: Readonly<Record<string, string>>;
2073
+ /** The full item template for `PutItem` (field name → template / literal). */
2074
+ readonly item?: Readonly<Record<string, string>>;
2075
+ /** Field-level changes for `UpdateItem` (field name → template / literal). */
2076
+ readonly changes?: Readonly<Record<string, string>>;
2077
+ /** Optional write condition (subset). */
2078
+ readonly condition?: ConditionSpec;
2079
+ }
2080
+ /**
2081
+ * A declarative `when` guard on a transaction item (issue #46). Compares a
2082
+ * templated left-hand value against a templated right-hand value; the item is
2083
+ * skipped when the comparison does not hold. Both values are template strings
2084
+ * (`{param}` / `{item.<field>}` / literal), resolved by the runtime before the
2085
+ * comparison. The runtime compares the resolved **string** values.
2086
+ */
2087
+ interface WhenSpec {
2088
+ readonly op: 'eq' | 'ne';
2089
+ /** Templated left-hand value (typically an `{item.<field>}` or `{param}`). */
2090
+ readonly left: string;
2091
+ /** Templated right-hand value (literal or another reference). */
2092
+ readonly right: string;
2093
+ }
2094
+ /**
2095
+ * The kind of a transaction item. The three write kinds (`Put` / `Update` /
2096
+ * `Delete`) mutate an item; `ConditionCheck` (issue #81) is a **read-only
2097
+ * assertion** on another item — it asserts a {@link ConditionSpec} holds for the
2098
+ * keyed row without modifying it, and its failure cancels the **whole**
2099
+ * `TransactWriteItems` atomically. It is the foundation for referential-integrity
2100
+ * derivation (proposal: `requires <Entity> exists` → a `ConditionCheck` with
2101
+ * `attribute_exists`).
2102
+ */
2103
+ type TransactionItemType = 'Put' | 'Update' | 'Delete' | 'ConditionCheck';
2104
+ /**
2105
+ * A single templated item in a transaction. When `forEach` is present, the item
2106
+ * is expanded **once per element** of the named array param, with each element's
2107
+ * fields bound to the item's `{item.<field>}` placeholders.
2108
+ *
2109
+ * Field presence by `type`:
2110
+ *
2111
+ * | type | item | keyCondition | changes | add | condition |
2112
+ * |----------------|------|--------------|---------|-----|--------------------------|
2113
+ * | `Put` | ✓ | — | — | — | optional |
2114
+ * | `Update` | — | ✓ | ✓ / — | ✓ / — | optional |
2115
+ * | `Delete` | — | ✓ | — | — | optional |
2116
+ * | `ConditionCheck` | — | ✓ | — | — | **required** (the assert) |
2117
+ *
2118
+ * An `Update` carries `changes` (a `SET` of named fields) and/or `add` (an atomic
2119
+ * `ADD` of named numeric fields, issue #85) — at least one of the two. The two are
2120
+ * distinct DynamoDB update actions: `SET #f = :v` **overwrites**, while
2121
+ * `ADD #f :delta` **atomically increments** without a read (concurrency-safe), so
2122
+ * a derived counter (`User.postCount += 1`) MUST be an `add`, never a `changes`
2123
+ * `SET` (which would clobber a concurrent increment).
2124
+ */
2125
+ interface TransactionItemSpec {
2126
+ readonly type: TransactionItemType;
2127
+ readonly tableName: string;
2128
+ readonly entity: string;
2129
+ /** Put: the full item template (field → template / literal). */
2130
+ readonly item?: Readonly<Record<string, string>>;
2131
+ /** Update / Delete / ConditionCheck: the key template (attribute → template). */
2132
+ readonly keyCondition?: Readonly<Record<string, string>>;
2133
+ /** Update: field-level `SET` change templates (overwrite). */
2134
+ readonly changes?: Readonly<Record<string, string>>;
2135
+ /**
2136
+ * Update: field-level atomic `ADD` deltas (issue #85, derived counters). Each
2137
+ * value is a numeric template (`{param}` / a literal number string); the runtime
2138
+ * applies `ADD #field :delta`, an atomic increment that needs no prior read and
2139
+ * is safe under concurrency. A negative delta decrements (e.g. `-1` on a remove).
2140
+ */
2141
+ readonly add?: Readonly<Record<string, string>>;
2142
+ /**
2143
+ * The write / assertion condition (subset). Optional on `Put` / `Update` /
2144
+ * `Delete`; **required** on a `ConditionCheck` (the assertion it makes).
2145
+ */
2146
+ readonly condition?: ConditionSpec;
2147
+ /** Optional declarative guard; the item is skipped when it does not hold. */
2148
+ readonly when?: WhenSpec;
2149
+ /** When present, expand this item once per element of the named array param. */
2150
+ readonly forEach?: {
2151
+ readonly source: string;
2152
+ };
2153
+ /**
2154
+ * Marks a **raw marker-row** write (issue #86 uniqueness guard): the row is NOT a
2155
+ * modeled entity, so its primary key is carried **literally** rather than derived
2156
+ * from manifest metadata. When `true`:
2157
+ *
2158
+ * - a `Put` writes its `item` record **verbatim** as the stored item — the `item`
2159
+ * already carries the synthetic `PK` / `SK` templates (`UNIQUE#…`), so the
2160
+ * runtimes do NOT prepend a model prefix or compute GSI attributes;
2161
+ * - a `Delete` uses its `keyCondition` (the `PK` / `SK` templates) as the row Key
2162
+ * verbatim.
2163
+ *
2164
+ * The companion {@link entity} is the {@link MARKER_ROW_ENTITY} sentinel (there is
2165
+ * no manifest entity to resolve). Absent / `false` on every modeled write (a base
2166
+ * entity / edge / counter / ConditionCheck item), so those paths are unchanged.
2167
+ */
2168
+ readonly literalKey?: boolean;
2169
+ }
2170
+ /** A declarative transaction definition's full execution spec. */
2171
+ interface TransactionSpec {
2172
+ readonly params: Readonly<Record<string, ParamSpec>>;
2173
+ readonly items: readonly TransactionItemSpec[];
2174
+ /**
2175
+ * A static upper bound on the expanded item count when computable (no `forEach`
2176
+ * present → the exact count; with `forEach` it is left absent because the
2177
+ * element count is only known at execution — the runtime then enforces the
2178
+ * DynamoDB ≤25 limit after expansion).
2179
+ */
2180
+ readonly maxItems?: number;
2181
+ }
2182
+ /**
2183
+ * Whether a contract is a public **read** (`'query'`) or **write** (`'command'`)
2184
+ * interface. Mirrors {@link QueryModelContract} / {@link CommandModelContract}'s
2185
+ * `kind` discriminant from the Contract IR (#58).
2186
+ */
2187
+ type ContractKind = 'query' | 'command';
2188
+ /**
2189
+ * The resolution kind of a contract query method, **derived** by the planner from
2190
+ * the internal op (never hand-written; see proposal "N+1 Safety"):
2191
+ *
2192
+ * - `'point'` — target keys are known (unique-key `query` / `GetItem`); a key
2193
+ * array coalesces to one `BatchGetItem`.
2194
+ * - `'range'` — target key set is unknown (partition `list` / `Query`); one
2195
+ * request per partition key, so it is only ever safe for a single key.
2196
+ */
2197
+ type ContractResolution = 'point' | 'range';
2198
+ /**
2199
+ * What input arity a contract method accepts, **derived** from its resolution:
2200
+ *
2201
+ * - `'either'` — a single key **or** an array (a `point` read / a known-key
2202
+ * write; the array form is one `BatchGetItem` / batched write).
2203
+ * - `'single'` — a single key only (a `range` read; an array would be an N+1
2204
+ * fan-out and is rejected by construction).
2205
+ * - `'array'` — an array only (reserved; not produced by the current resolvers).
2206
+ */
2207
+ type ContractInputArity = 'single' | 'array' | 'either';
2208
+ /**
2209
+ * The per-key result cardinality of a contract query method, **derived** from the
2210
+ * internal op kind (proposal "Cardinality matrix"):
2211
+ *
2212
+ * - `'one'` — at most one item per key (a `point` read).
2213
+ * - `'many'` — a connection (`{ items, cursor }`) per key (a `range` read).
2214
+ *
2215
+ * This is the per-key shape; the input arity (single vs. array) then decides
2216
+ * whether the overall result is bare or keyed.
2217
+ */
2218
+ type ContractCardinality = 'one' | 'many';
2219
+ /**
2220
+ * The category of a contract command method's declared result, part of the
2221
+ * contract (it surfaces in OpenAPI and every binding; proposal "Return values"):
2222
+ *
2223
+ * - `'void'` — fire-and-forget write (no body).
2224
+ * - `'result'` — an outcome / status object (e.g. `{ ok, version }`).
2225
+ * - `'entity'` — the updated entity (the post-write projection).
2226
+ */
2227
+ type ContractCommandResult = 'void' | 'result' | 'entity';
2228
+ /** The Key of a contract: the field names that compose its access / join key. */
2229
+ interface ContractKeySpec {
2230
+ /** The key field names, in declaration order (e.g. `["articleId"]`). */
2231
+ readonly fields: readonly string[];
2232
+ }
2233
+ /**
2234
+ * One External Query (Query Composition) binding on a contract query method
2235
+ * (proposal "External Query"). It is a **build-time, in-process** read dependency
2236
+ * on another contract referenced by name in the same SSoT — there is no protocol
2237
+ * or transport. The runtime collects every parent key produced at this level and
2238
+ * resolves the referenced contract **once, batched**, for all of them.
2239
+ *
2240
+ * A composed child MUST be `point` (proposal "N+1 Safety"): the parent step may
2241
+ * yield N records, so a `range` child would be an N+1 fan-out.
2242
+ */
2243
+ interface ComposeSpec {
2244
+ /** The result property the composed value is attached to (e.g. `billing`). */
2245
+ readonly as: string;
2246
+ /** The referenced contract name (a symbol in the same SSoT). */
2247
+ readonly contract: string;
2248
+ /** The referenced method on that contract (e.g. `get`). */
2249
+ readonly method: string;
2250
+ /**
2251
+ * An optional **logical** owner label (the bounded context that owns the
2252
+ * referenced contract) — not a network address. Omitted when unlabeled.
2253
+ */
2254
+ readonly context?: string;
2255
+ /**
2256
+ * The child-key binding: child key field → a `from` source path on the parent
2257
+ * result, e.g. `{ accountId: "$.billingAccountId" }`.
2258
+ */
2259
+ readonly bind: Readonly<Record<string, string>>;
2260
+ /**
2261
+ * The composed child's resolution. It MUST be `'point'` in a valid contract — a
2262
+ * composed child coalesces to one `BatchGetItem` across all parent keys, so a
2263
+ * `range` child (a partition `Query` that cannot be batched across the parent's
2264
+ * keys) is an N+1 fan-out. The N+1 static checker (#60,
2265
+ * {@link assertContractN1Safe}) rejects a `'range'` value on the build path; the
2266
+ * field is widened to {@link ContractResolution} so the offending node can be
2267
+ * carried into the assembled {@link ContractSpec} for the checker to inspect and
2268
+ * reject with a clear message, rather than being silently unrepresentable.
2269
+ */
2270
+ readonly resolution: ContractResolution;
2271
+ /** The composed child's per-key cardinality (`'one'` for a valid `point`). */
2272
+ readonly cardinality: ContractCardinality;
2273
+ }
2274
+ /**
2275
+ * The **composition execution plan** for one query contract method (issue #70a),
2276
+ * the contract-layer mirror of {@link ExecutionPlanSpec}. It records how a method's
2277
+ * External Query compositions (`compose[]`) are staged relative to the parent read
2278
+ * and to one another, and the batching discipline each composed child obeys:
2279
+ *
2280
+ * - `stages` is an ordered list whose entries are indices into the method's
2281
+ * `compose[]`. The parent read (the referenced `operation`) is the implicit
2282
+ * stage before all of these. Every compose child binds its child key purely from
2283
+ * the **parent** result (`from('$.…')` paths), so sibling compositions are
2284
+ * mutually independent: they form a single stage `[0, 1, …]` and may be resolved
2285
+ * concurrently. (A composed child cannot, today, bind from another composed
2286
+ * child — the #63 DSL only exposes parent `from` paths — so there is never a
2287
+ * cross-composition dependency; the field is a list of stages so the shape can
2288
+ * carry one if a future primitive introduces it.)
2289
+ * - `concurrency` is the declared in-flight bound applied **within** a stage (the
2290
+ * shared {@link RELATION_TRAVERSAL_CONCURRENCY} = 16).
2291
+ * - `batchChunkSize` is the per-request key cap for a composed child read (100,
2292
+ * the DynamoDB `BatchGetItem` limit): a composition resolves **one batched
2293
+ * call** across all parent records (chunked at this size), never an N+1 fan-out
2294
+ * (the composed child is `point`, enforced by the #60 N+1 checker).
2295
+ *
2296
+ * Emitted only when the method declares at least one composition. A method without
2297
+ * compositions carries no plan, and a runtime without one resolves any
2298
+ * compositions sequentially (the pre-#70 behavior) — so an absent plan never
2299
+ * changes results.
2300
+ */
2301
+ interface CompositionPlanSpec {
2302
+ /** Ordered stages of indices into `compose[]` (independent siblings share a stage). */
2303
+ readonly stages: readonly (readonly number[])[];
2304
+ /** The declared in-flight bound applied within each composition stage (16). */
2305
+ readonly concurrency: number;
2306
+ /** Per-request key cap for a composed child's batched read (100, BatchGetItem limit). */
2307
+ readonly batchChunkSize: number;
2308
+ }
2309
+ /**
2310
+ * The serialized spec of one **query** contract method. Carries the decided facts
2311
+ * (`resolution` / `inputArity` / `cardinality`) the planner derived in #58, a
2312
+ * reference into `queries` by name (the internal op), and any External Query
2313
+ * compositions. The decided facts are **serialized, not re-derived** — every
2314
+ * runtime honors them.
2315
+ */
2316
+ interface QueryContractMethodSpec {
2317
+ /** Derived: `'point'` (unique-key query) or `'range'` (partition list). */
2318
+ readonly resolution: ContractResolution;
2319
+ /** Derived: `'either'` for `point`, `'single'` for `range`. */
2320
+ readonly inputArity: ContractInputArity;
2321
+ /** Derived per-key result shape: `'one'` (point) or `'many'` (range). */
2322
+ readonly cardinality: ContractCardinality;
2323
+ /** The referenced read op name in `queries` (e.g. `ArticleById__get`). */
2324
+ readonly operation: string;
2325
+ /** External Query compositions on this method; omitted when there are none. */
2326
+ readonly compose?: readonly ComposeSpec[];
2327
+ /**
2328
+ * The composition staging + batch plan (issue #70a); present only when `compose`
2329
+ * is. See {@link CompositionPlanSpec}: independent compositions form one
2330
+ * concurrency-eligible stage after the parent read, each resolved as one batched
2331
+ * call (chunk ≤100). Absent → resolve compositions sequentially (pre-#70).
2332
+ */
2333
+ readonly compositionPlan?: CompositionPlanSpec;
2334
+ }
2335
+ /**
2336
+ * How a contract command method resolves a single key vs. an array of keys to the
2337
+ * underlying write surface (proposal "Consistency with the existing write
2338
+ * surface"). A single key → one write op (or one transaction); an array → a
2339
+ * batched write — a `TransactWriteItems` when atomicity / per-item conditions are
2340
+ * required, or a `BatchWriteItem` when neither is.
2341
+ */
2342
+ type CommandResolutionTarget =
2343
+ /** One write op, referenced by name in `commands`. */
2344
+ {
2345
+ readonly mode: 'op';
2346
+ readonly operation: string;
2347
+ }
2348
+ /** One transaction, referenced by name in `transactions`. */
2349
+ | {
2350
+ readonly mode: 'transaction';
2351
+ readonly transaction: string;
2352
+ }
2353
+ /**
2354
+ * A non-atomic **parallel** fan-out over the per-key write op (#101). The op
2355
+ * MAY carry a condition (guarded create / conditioned update). Unconditioned
2356
+ * `put`/`delete` ops coalesce into a `BatchWriteItem` (with `UnprocessedItems`
2357
+ * retry); conditioned/update ops issue individual conditional writes
2358
+ * concurrently. Per-op success/failure is reported (partial success); a
2359
+ * per-op failure never aborts the others. References the per-key write op in
2360
+ * `commands`.
2361
+ */
2362
+ | {
2363
+ readonly mode: 'parallel';
2364
+ readonly operation: string;
2365
+ };
2366
+ /**
2367
+ * The serialized spec of one **command** contract method. Symmetric to
2368
+ * {@link QueryContractMethodSpec}: it carries the declared `result` type and the
2369
+ * `single` / `batch` resolution targets into the existing write specs.
2370
+ */
2371
+ interface CommandContractMethodSpec {
2372
+ /** Derived input arity: writes accept `'either'` (single key or key array). */
2373
+ readonly inputArity: ContractInputArity;
2374
+ /** The declared result type (`void` | a `Result` | the updated entity). */
2375
+ readonly result: ContractCommandResult;
2376
+ /** How a single key resolves (one op / one transaction). */
2377
+ readonly single: CommandResolutionTarget;
2378
+ /**
2379
+ * How an array of keys resolves (a transaction / `BatchWriteItem`); omitted
2380
+ * when the contract does not declare a batched write form.
2381
+ */
2382
+ readonly batch?: CommandResolutionTarget;
2383
+ /**
2384
+ * The **return projection** of a `mutation`-derived command method (issue #83;
2385
+ * proposal §3, "return selection as a read projection"). A JSON-safe boolean
2386
+ * field map: after the write commits, the runtime issues a **`GetItem` with
2387
+ * `ConsistentRead`** of the written entity's primary key and applies this
2388
+ * projection via the existing read-projection machinery, returning the projected
2389
+ * item. This is the **uniform** return mechanism for both a single-op command
2390
+ * and a future transaction (a `TransactWriteItems` cannot return item images),
2391
+ * so TS and Python return an **identical** projected item.
2392
+ *
2393
+ * The consistent read-back is issued against the read op named
2394
+ * `<Contract>__<method>__readback` (a synthesized {@link QuerySpec}: a `GetItem`
2395
+ * on the written entity's primary key projecting these fields). Present only on
2396
+ * a `.plan(mutation)` method; a hand-written #64 command omits it (its
2397
+ * {@link result} stays `'void'` / `'entity'` and it returns no projected item).
2398
+ */
2399
+ readonly returnSelection?: Readonly<Record<string, boolean>>;
2400
+ }
2401
+ /**
2402
+ * A serialized contract — a keyed public read (`'query'`) or write (`'command'`)
2403
+ * interface holding one or more named methods. Each method references an existing
2404
+ * operation spec by name and adds the decided contract facts. The existing
2405
+ * operation-spec shapes are unchanged, so the current runtime keeps working.
2406
+ */
2407
+ interface ContractSpec {
2408
+ /** Whether this is a read (`'query'`) or write (`'command'`) contract. */
2409
+ readonly kind: ContractKind;
2410
+ /** The contract Key (the access pattern / join / batch key). */
2411
+ readonly key: ContractKeySpec;
2412
+ /**
2413
+ * The named methods (use cases over the same Key). A query contract's methods
2414
+ * are {@link QueryContractMethodSpec}; a command contract's are
2415
+ * {@link CommandContractMethodSpec}. The map is keyed by contract `kind`.
2416
+ */
2417
+ readonly methods: Readonly<Record<string, QueryContractMethodSpec>> | Readonly<Record<string, CommandContractMethodSpec>>;
2418
+ }
2419
+ /**
2420
+ * One bounded context's membership (issue #59, "context ownership"). Lists the
2421
+ * Models and Contracts that belong to the same context, so the future boundary
2422
+ * lint (#61) can tell **own** from **foreign**: a contract may resolve its own
2423
+ * context's Models directly, but may only reach another context through that
2424
+ * context's published Contract (direct use of a foreign Model is a build error).
2425
+ *
2426
+ * This issue only **serializes / emits** this declaration; it does not enforce
2427
+ * it. Both lists are entity / contract *names* (matching `manifest.entities` keys
2428
+ * and `contracts` keys respectively).
2429
+ */
2430
+ interface ContextSpec {
2431
+ /** Model (entity) names owned by this context (sorted; manifest entity keys). */
2432
+ readonly models: readonly string[];
2433
+ /** Contract names owned by this context (sorted; `contracts` map keys). */
2434
+ readonly contracts: readonly string[];
2435
+ }
2436
+ interface OperationsDocument {
2437
+ readonly version: typeof SPEC_VERSION;
2438
+ readonly queries: Readonly<Record<string, QuerySpec>>;
2439
+ readonly commands: Readonly<Record<string, CommandSpec>>;
2440
+ /** Declarative transactions (issue #46). Absent in pre-#46 specs. */
2441
+ readonly transactions?: Readonly<Record<string, TransactionSpec>>;
2442
+ /**
2443
+ * The CQRS Contract layer (issue #59): contract name → {@link ContractSpec}.
2444
+ * Layered **on top of** the existing `queries` / `commands` / `transactions`
2445
+ * (each method references one of them by name). **Absent** when the input
2446
+ * defines no contracts — so a contract-free input produces a byte-identical
2447
+ * pre-#59 operations document (backward compatibility).
2448
+ */
2449
+ readonly contracts?: Readonly<Record<string, ContractSpec>>;
2450
+ /**
2451
+ * Context-ownership declarations (issue #59): context name →
2452
+ * {@link ContextSpec}. Used by the boundary lint (#61) to tell own from
2453
+ * foreign. **Absent** when no contexts are declared (backward compatibility).
2454
+ */
2455
+ readonly contexts?: Readonly<Record<string, ContextSpec>>;
2456
+ }
2457
+ /** The full `{ manifest, operations }` bundle produced by the static planner. */
2458
+ interface BridgeBundle {
2459
+ readonly manifest: Manifest;
2460
+ readonly operations: OperationsDocument;
2461
+ }
2462
+
2463
+ /**
2464
+ * Single-fragment mutation → command-op compiler (issue #83, Epic #80; proposal
2465
+ * `docs/mutation-command-derivation.md` §3).
2466
+ *
2467
+ * A {@link CommandPlan} (a `mutation(...)` result) is the internal IR: a **list**
2468
+ * of write fragments to compose atomically. This module compiles the
2469
+ * **single-fragment** case end-to-end into the **same** {@link ContractMethodOp}
2470
+ * the #58 contract recorder produces, so the existing serializer
2471
+ * (`opToDefinition` → `buildCommandSpec`, `src/spec/contracts.ts` /
2472
+ * `src/spec/operations.ts`) and the existing runtimes consume it with no new
2473
+ * write substrate. The IR is already a list, so the **N-fragment atomic merge is
2474
+ * #90** — this module rejects N>1 with a clear pointer rather than half-merging.
2475
+ *
2476
+ * ## Intent → base op (proposal §3, "single fragment compile")
2477
+ *
2478
+ * | intent | base op |
2479
+ * |----------|-------------------------------------------|
2480
+ * | `create` | `PutItem` (`attribute_not_exists(PK)`) |
2481
+ * | `update` | `UpdateItem` (key + changed fields) |
2482
+ * | `remove` | `DeleteItem` (key) |
2483
+ *
2484
+ * ## `use:` resolution (the resolved fork — proposal §3, pinned spec correction)
2485
+ *
2486
+ * The fragment's **intent** selects the lifecycle; `use:` is **optional**:
2487
+ *
2488
+ * - `use:` present → adopt that {@link EntityWritesDefinition} (the **whole**
2489
+ * `writes` set; never a single lifecycle).
2490
+ * - `use:` omitted → default to the **target model's own** `entityWrites` save
2491
+ * contract ({@link getEntityWrites}).
2492
+ * - the resolved save contract (custom or default) has **no** entry for the
2493
+ * intent's lifecycle, **or the target declares no `writes` at all** → the
2494
+ * **trivial base op** for the intent (create → `Put` `attribute_not_exists(PK)`,
2495
+ * update → `Update`, remove → `Delete`) against the plain model. So
2496
+ * `{ create: () => PostModel, key, input }` works on a bare model.
2497
+ *
2498
+ * Either way #83 emits the **same** base op — the resolved
2499
+ * {@link LifecycleContract} only matters as the **per-fragment hook** the
2500
+ * effect-derivation issues (#84–#87) read: the resolved lifecycle's
2501
+ * {@link LifecycleContract.effects} are exposed on {@link CompiledFragment} and
2502
+ * consumed by NONE of #83 (the empty hook).
2503
+ */
2504
+
2505
+ /**
2506
+ * A **referential-integrity assertion** derived from a `requires`
2507
+ * ({@link RequiresEffect}) effect (issue #84; proposal §2/§3). It is the
2508
+ * realization of `w.exists(() => Entity, { <entityKeyField>: '$.input.<field>' })`:
2509
+ * the referenced entity must already exist for the bound keys, asserted by a
2510
+ * read-only `ConditionCheck` (`attribute_exists(PK)`) item composed into the
2511
+ * mutation's atomic `TransactWriteItems`. A failed assertion cancels the WHOLE
2512
+ * transaction, so the entity / edges / derived updates / events that the same
2513
+ * transaction would write never persist (referential integrity is enforced
2514
+ * atomically, not by a pre-read).
2515
+ *
2516
+ * The shape is **runtime-neutral** — it carries the structural binding (target
2517
+ * entity + the target key fields resolved from the mutation input) that BOTH the
2518
+ * spec serializer (→ a {@link import('./types.js').TransactionItemSpec} with
2519
+ * templated `keyCondition`, for the Python bridge) and the in-process TS runtime
2520
+ * (→ a `ConditionCheck` `TransactWriteItems` entry, keyed from `(key, params)`)
2521
+ * derive their concrete form from. The compiler resolves the key binding once,
2522
+ * here, so both runtimes assert the **same** keyed row from the same input.
2523
+ */
2524
+ interface DerivedConditionCheck {
2525
+ /** The referenced entity whose existence is asserted (resolved target model). */
2526
+ readonly entity: EntityRef;
2527
+ /**
2528
+ * The asserted row's key binding: target primary-key **input field name** → the
2529
+ * **mutation input field** that supplies it (parsed from the effect's
2530
+ * `$.input.<field>` value). Every target primary-key field is bound (a partial
2531
+ * key cannot identify a row to assert on), so the runtimes build the referenced
2532
+ * entity's full primary key from the mutation params.
2533
+ */
2534
+ readonly keyBinding: Readonly<Record<string, string>>;
2535
+ }
2536
+ /**
2537
+ * A **derived edge write** — the adjacency `Put` / `Delete` {@link TransactionItemSpec}
2538
+ * set that materializes a relation edge on DynamoDB (issue #85; proposal §2 `edges`),
2539
+ * derived from one `w.putEdge(() => Target, 'relation')` / `w.deleteEdge(...)` effect.
2540
+ *
2541
+ * The effect names a **relation** (`Target.relationProperty`) whose rows form the
2542
+ * edge; the **adjacency entity** — the entity whose row IS that edge — is the
2543
+ * relation's *target model* ({@link RelationMetadata.targetFactory}), resolved here
2544
+ * and round-trip-verified by the #82 primitive ({@link deriveEdgeWriteItemsFor}, with
2545
+ * the #85-tightened PK-partition guard). The resolved item set is lifecycle-distinguished
2546
+ * by the fragment's intent ({@link INTENT_EDGE_LIFECYCLE}):
2547
+ *
2548
+ * - `create` → `onCreate`: a single adjacency `Put` (the new edge row);
2549
+ * - `remove` → `onDelete`: a single adjacency `Delete` (the edge row keyed by PK/SK);
2550
+ * - `update` → `onUpdateKeyChange`: `Delete`(old edge, keyed in the `{old.*}`
2551
+ * namespace) + `Put`(new edge) — the edge **moves** atomically (spec correction).
2552
+ *
2553
+ * The items carry templated key/item fields (`{field}` / `{old.field}`); the
2554
+ * serializer composes them into the atomic `TransactWriteItems` and the in-process
2555
+ * runtime renders the same items from the shared `(key, params)`, so both runtimes
2556
+ * write the **same** adjacency row(s) from the same input.
2557
+ */
2558
+ interface DerivedEdgeWrite {
2559
+ /** The adjacency entity (the relation's target — whose row is the edge). */
2560
+ readonly entity: EntityRef;
2561
+ /** The relation (`Target.relationProperty`) the edge materializes (documentary). */
2562
+ readonly relation: {
2563
+ readonly target: string;
2564
+ readonly property: string;
2565
+ };
2566
+ /** The lifecycle-distinguished adjacency item set (`Put` / `Delete`). */
2567
+ readonly items: readonly TransactionItemSpec[];
2568
+ }
2569
+ /**
2570
+ * A **derived update** — an atomic `ADD` (`UpdateItem`) on a target row, derived
2571
+ * from one `w.increment(() => Entity, { key }, 'field', delta)` effect (issue #85;
2572
+ * proposal §2 `derive`). It realizes a cascading counter such as
2573
+ * `User.postCount += 1`: an atomic `ADD #field :delta` that needs no prior read and
2574
+ * is safe under concurrency (a `SET` would clobber a concurrent increment).
2575
+ *
2576
+ * The lifecycle decides the sign convention the *declaration* uses (the author
2577
+ * declares `+1` on `create`, `-1` on `remove`, and a `-1`-on-old + `+1`-on-new pair
2578
+ * on `update` / onUpdateKeyChange); the compiler stores the declared delta verbatim.
2579
+ * The target row's key binds from the mutation input — `$.input.<field>` (the new /
2580
+ * current value) or `$.old.<field>` (the pre-mutation value, for decrementing the
2581
+ * **old** parent on a key change; the {@link OLD_VALUE_NAMESPACE} convention).
2582
+ */
2583
+ interface DerivedUpdate {
2584
+ /** The entity whose attribute is derive-updated (resolved target model). */
2585
+ readonly entity: EntityRef;
2586
+ /**
2587
+ * The target row's key binding: target primary-key **input field name** → the
2588
+ * source param that supplies it. A `$.input.<field>` source binds the param
2589
+ * `<field>`; a `$.old.<field>` source binds the param `old.<field>` (the
2590
+ * pre-mutation value). Every target primary-key field is bound.
2591
+ */
2592
+ readonly keyBinding: Readonly<Record<string, string>>;
2593
+ /** The attribute to atomically `ADD` to. */
2594
+ readonly attribute: string;
2595
+ /** The signed delta to add (e.g. `+1` create, `-1` remove). */
2596
+ readonly amount: number;
2597
+ }
2598
+ /**
2599
+ * A **derived uniqueness guard** — the marker-row `Put` (and, on a unique-field
2600
+ * change, the old-guard `Delete` + new-guard `Put` swap) that enforces an
2601
+ * application-level uniqueness invariant declared by `w.unique({ name, scope,
2602
+ * fields })` (issue #86; proposal §2 `unique`). It is the realization of a named,
2603
+ * composite-scope uniqueness constraint that a unique GSI cannot express
2604
+ * (e.g. *unique title per user*).
2605
+ *
2606
+ * ## The guard-row key (deterministic, built from `name` / `scope` / `fields`)
2607
+ *
2608
+ * The guard occupies a synthetic marker row whose primary key is built **purely**
2609
+ * from the constraint:
2610
+ *
2611
+ * - `PK` = `UNIQUE#<name>` followed by one `#{<scopeField>}` segment per `scope`
2612
+ * path (a composite scope yields several segments) — the **partition** the
2613
+ * uniqueness applies within, e.g. `UNIQUE#postTitlePerUser#{userId}`;
2614
+ * - `SK` = `VALUE#` followed by one `#{<field>}` segment per `fields` path (the
2615
+ * first carries no leading separator) — the **unique value** within that scope,
2616
+ * e.g. `VALUE#{title}`.
2617
+ *
2618
+ * Each `$.input.<field>` path contributes a `{<field>}` template token (rendered
2619
+ * from the mutation input). The shape is **deterministic** (same constraint →
2620
+ * same key), so a second write of the same scoped value targets the SAME row, and
2621
+ * the `attribute_not_exists` guard on the `Put` then fails — rolling back the
2622
+ * WHOLE transaction so the entity is never created.
2623
+ *
2624
+ * ## create vs. update-swap
2625
+ *
2626
+ * - **create / remove** — a single guard item: `create` → `Put` (claim the value)
2627
+ * guarded by `attribute_not_exists(PK)`; `remove` → `Delete` (release the value);
2628
+ * - **update changing a unique field** — `Delete`(OLD guard) + `Put`(NEW guard):
2629
+ * the old marker row (keyed in the `{old.*}` namespace, the {@link
2630
+ * OLD_VALUE_NAMESPACE} convention shared with the edge `onUpdateKeyChange` and
2631
+ * the derived-counter old-parent decrement) is released and the new value claimed
2632
+ * atomically, so the uniqueness moves with the field. The new `Put` keeps the
2633
+ * `attribute_not_exists` guard so the moved-to value cannot collide.
2634
+ *
2635
+ * The items are **runtime-neutral** {@link TransactionItemSpec}s carrying the
2636
+ * literal-key marker ({@link TransactionItemSpec.literalKey}) and the
2637
+ * {@link MARKER_ROW_ENTITY} sentinel (there is no manifest entity), so BOTH runtimes
2638
+ * write the SAME marker row(s) from the same input and the guard composes into the
2639
+ * entity's atomic `TransactWriteItems` (#90).
2640
+ */
2641
+ interface DerivedUniqueGuard {
2642
+ /** The constraint name (its key-shape discriminator on DynamoDB). */
2643
+ readonly name: string;
2644
+ /** The guard's marker-row item set (`Put` / `Delete`), lifecycle-distinguished. */
2645
+ readonly items: readonly TransactionItemSpec[];
2646
+ }
2647
+ /**
2648
+ * A **derived outbox event** — the transactional-outbox `Put` ({@link
2649
+ * TransactionItemSpec}) that records a domain event ATOMICALLY with the entity
2650
+ * write, derived from one `w.event(name, payload)` effect (issue #87; proposal §2
2651
+ * `emits`, §4 "delivery is outside"). The event row is written in the **same**
2652
+ * `TransactWriteItems` as the entity, so the event and the state it announces can
2653
+ * never diverge (no "wrote the row, lost the event" gap).
2654
+ *
2655
+ * ## The outbox-row key + shape (deterministic; drainable via `src/cdc/`)
2656
+ *
2657
+ * The row occupies a synthetic marker row in its own `OUTBOX#…` key-space:
2658
+ *
2659
+ * - `PK` = `OUTBOX#<eventName>` followed by one `#{<keyField>}` segment per
2660
+ * primary-key input field of the **writing entity** — so each entity emission has
2661
+ * its own deterministic outbox row, e.g. `OUTBOX#PostCreated#{postId}`;
2662
+ * - `SK` = `EVENT#` followed by the same per-key-field segments — a stable,
2663
+ * single-row-per-emission sort key;
2664
+ * - the item also stores `eventName` plus every declared `payload` field (each a
2665
+ * `{field}` template rendered from the shared method params), so the **`newImage`**
2666
+ * of the row carries the complete, typed event body.
2667
+ *
2668
+ * Because the row is a normal table item, the `src/cdc/` Streams substrate captures
2669
+ * its write as a {@link import('../cdc/types.js').ChangeEvent} (`eventName:
2670
+ * 'INSERT'`, `newImage` = this item). A consumer drains the outbox by filtering the
2671
+ * stream on `PK begins_with 'OUTBOX#'` and reading the event body off `newImage` —
2672
+ * **delivery / handlers / business reactions live OUTSIDE the mutation** (proposal
2673
+ * §4); #87 only writes the drainable RECORD.
2674
+ */
2675
+ interface DerivedOutboxEvent {
2676
+ /** The event name (e.g. `PostCreated`). */
2677
+ readonly name: string;
2678
+ /** The outbox marker-row `Put` (one item; `literalKey`, `MARKER_ROW_ENTITY`). */
2679
+ readonly items: readonly TransactionItemSpec[];
2680
+ }
2681
+ /**
2682
+ * A **derived idempotency guard** — the client-token guard `Put` ({@link
2683
+ * TransactionItemSpec}) that makes a same-token re-execution a NO-OP, derived from
2684
+ * the lifecycle's `w.idempotentBy(token)` effect (issue #87; proposal §2
2685
+ * `idempotency`). It is the transactional realization of "process this command at
2686
+ * most once for this client token".
2687
+ *
2688
+ * ## The guard-row key (deterministic, built from the token)
2689
+ *
2690
+ * The guard occupies a synthetic marker row keyed **purely** by the client token:
2691
+ *
2692
+ * - `PK` = `IDEMP#{<tokenField>}` — the `$.input.<field>` token (e.g.
2693
+ * `IDEMP#{requestId}`), rendered from the shared method params;
2694
+ * - `SK` = `TOKEN` (a fixed discriminator — one guard row per token).
2695
+ *
2696
+ * The `Put` is guarded by `attribute_not_exists(PK)`: the FIRST execution claims the
2697
+ * token row; a SECOND execution with the same token targets the SAME row and the
2698
+ * guard fails, cancelling the WHOLE `TransactWriteItems` — so the entity write, the
2699
+ * outbox event, and every other effect roll back and **no effect is double-applied**.
2700
+ * The shape is identical to the uniqueness guard's claim `Put` (the same `literalKey`
2701
+ * marker mechanism), so BOTH runtimes write / assert the SAME row from the same input.
2702
+ */
2703
+ interface DerivedIdempotencyGuard {
2704
+ /** The mutation-input field the token binds from (documentary; surfaces in errors). */
2705
+ readonly tokenField: string;
2706
+ /** The guard marker-row `Put` (one item; `attribute_not_exists`, `literalKey`). */
2707
+ readonly items: readonly TransactionItemSpec[];
2708
+ }
2709
+ /**
2710
+ * The result of compiling one fragment: the {@link ContractMethodOp} the existing
2711
+ * serializer / runtime consume, the resolved contract Key fields (the model's
2712
+ * primary-key input fields, also the consistent read-back key), and the **opaque
2713
+ * per-fragment hook** — the resolved lifecycle's §2 effect set (empty-consumed by
2714
+ * #83; read by #84–#87).
2715
+ */
2716
+ interface CompiledFragment {
2717
+ /** The resolved write op (`put` / `update` / `delete`), serializer-ready. */
2718
+ readonly op: ContractMethodOp;
2719
+ /** The contract Key fields (the model primary-key input fields). */
2720
+ readonly keyFields: readonly string[];
2721
+ /**
2722
+ * The resolved lifecycle's §2 effect set, or `undefined` when the fragment
2723
+ * resolved to the trivial base op (no `writes` entry). **The per-fragment hook
2724
+ * for #84–#87** — #83 derives NO items from it. #84 reads the `requires` arm to
2725
+ * derive {@link conditionChecks}; the remaining arms (unique / edges / derive /
2726
+ * emits / idempotency) are still stored opaquely for #85–#87.
2727
+ */
2728
+ readonly effects?: LifecycleEffects;
2729
+ /**
2730
+ * The referential-integrity assertions derived from the resolved lifecycle's
2731
+ * `requires` effects (issue #84): one {@link DerivedConditionCheck} per
2732
+ * `w.exists(...)`. Present (non-empty) only when the fragment's lifecycle
2733
+ * declares `requires`; absent otherwise (no regression — a fragment with no
2734
+ * `requires` compiles to exactly the #83/#90 base op). Each becomes a read-only
2735
+ * `ConditionCheck` (`attribute_exists`) item in the mutation's atomic
2736
+ * `TransactWriteItems`, and their presence **promotes** an otherwise single-op
2737
+ * mutation to a transaction (a `ConditionCheck` only exists inside a
2738
+ * `TransactWriteItems`).
2739
+ */
2740
+ readonly conditionChecks?: readonly DerivedConditionCheck[];
2741
+ /**
2742
+ * The adjacency edge writes derived from the resolved lifecycle's `edges` effects
2743
+ * (issue #85): one {@link DerivedEdgeWrite} per `w.putEdge(...)` / `w.deleteEdge(...)`,
2744
+ * lifecycle-distinguished by the fragment intent (`create` → `Put`, `remove` →
2745
+ * `Delete`, `update` → `Delete(old) + Put(new)`). Present (non-empty) only when the
2746
+ * lifecycle declares `edges`; absent otherwise (no regression). Each derived item is
2747
+ * composed into the method's atomic `TransactWriteItems`.
2748
+ */
2749
+ readonly edgeWrites?: readonly DerivedEdgeWrite[];
2750
+ /**
2751
+ * The derived (cascading) updates from the resolved lifecycle's `derive` effects
2752
+ * (issue #85): one {@link DerivedUpdate} per `w.increment(...)`, each an atomic
2753
+ * `ADD` on a target counter. Present (non-empty) only when the lifecycle declares
2754
+ * `derive`; absent otherwise (no regression). Each becomes an `UpdateItem` (`ADD`)
2755
+ * in the method's atomic `TransactWriteItems`.
2756
+ */
2757
+ readonly derivedUpdates?: readonly DerivedUpdate[];
2758
+ /**
2759
+ * The uniqueness guards derived from the resolved lifecycle's `unique` effects
2760
+ * (issue #86): one {@link DerivedUniqueGuard} per `w.unique({ name, scope,
2761
+ * fields })`, lifecycle-distinguished by the fragment intent (`create` → a guarded
2762
+ * marker `Put`, `remove` → a marker `Delete`, `update` → `Delete(old) + Put(new)`
2763
+ * — the value swap). Present (non-empty) only when the lifecycle declares
2764
+ * `unique`; absent otherwise (no regression — a fragment with no `unique` compiles
2765
+ * to exactly the #83/#90 base op). Each guard's items compose into the method's
2766
+ * atomic `TransactWriteItems`; the guard `Put`'s `attribute_not_exists` makes a
2767
+ * duplicate roll the WHOLE transaction back, so the entity is never created.
2768
+ */
2769
+ readonly uniqueGuards?: readonly DerivedUniqueGuard[];
2770
+ /**
2771
+ * The outbox events derived from the resolved lifecycle's `emits` effects (issue
2772
+ * #87): one {@link DerivedOutboxEvent} per `w.event(name, payload)`, each a
2773
+ * transactional-outbox `Put` recording the event ATOMICALLY with the entity write
2774
+ * (so event and state cannot diverge). Present (non-empty) only when the lifecycle
2775
+ * declares `emits`; absent otherwise (no regression — a fragment with no `emits`
2776
+ * compiles to exactly the #83/#90 base op). Each event's `Put` composes into the
2777
+ * method's atomic `TransactWriteItems`; its row is drainable via `src/cdc/`
2778
+ * (`PK begins_with 'OUTBOX#'`). Delivery / handlers are OUT of scope (proposal §4).
2779
+ */
2780
+ readonly outboxEvents?: readonly DerivedOutboxEvent[];
2781
+ /**
2782
+ * The idempotency guard derived from the resolved lifecycle's `idempotency` effect
2783
+ * (issue #87): a single {@link DerivedIdempotencyGuard} (the `w.idempotentBy(token)`
2784
+ * client-token guard `Put`, `attribute_not_exists`). Present only when the lifecycle
2785
+ * declares `idempotency`; absent otherwise (no regression). Its `Put` composes into
2786
+ * the method's atomic `TransactWriteItems`; a same-token re-execution fails the
2787
+ * guard and rolls the WHOLE transaction back — so no effect is double-applied.
2788
+ */
2789
+ readonly idempotencyGuard?: DerivedIdempotencyGuard;
2790
+ }
2791
+ /**
2792
+ * Resolve the {@link LifecycleContract} a fragment adopts for its intent:
2793
+ * `use:` (the whole save contract) when present, else the target model's own
2794
+ * `entityWrites`, then the lifecycle entry for the intent's phase. Returns
2795
+ * `undefined` (→ the trivial base op) when no save contract declares the phase or
2796
+ * the target declares no `writes`.
2797
+ */
2798
+ declare function resolveLifecycle(fragment: MutationFragment): LifecycleContract | undefined;
2799
+ /**
2800
+ * A resolver for a {@link MutationEntityRef} (`$.entity[i].field`) — supplied by
2801
+ * the multi-fragment compiler ({@link compileMutationPlan}) so a later fragment's
2802
+ * leaf can be resolved to the **earlier** fragment's binding for that field. It
2803
+ * returns the already-rendered value the producer fragment stores at that field
2804
+ * (a {@link ContractParamRef} / {@link ContractKeyFieldRef} / literal), so the
2805
+ * cross-fragment value renders from the **same** shared input at execution time
2806
+ * (a `TransactWriteItems` cannot read another item mid-transaction). `null` for a
2807
+ * single-fragment compile, where a cross-fragment reference is meaningless.
2808
+ */
2809
+ type EntityRefResolver = (ref: MutationEntityRef, consumerIndex: number, consumerField: string) => unknown;
2810
+ /**
2811
+ * Compile one fragment into a {@link CompiledFragment}. Builds the intent's base
2812
+ * op as a {@link ContractMethodOp}:
2813
+ *
2814
+ * - `create` → a `put` whose `item` is the fragment input (primary-key fields as
2815
+ * {@link ContractKeyFieldRef}, the rest as {@link ContractParamRef}), guarded by
2816
+ * `attribute_not_exists(PK)` (`{ notExists: true }`);
2817
+ * - `update` → an `update` whose `keys` is the whole-keys sentinel with
2818
+ * `keyFields` = the primary-key fields (bound from `$.input.*`), and whose
2819
+ * `changes` is the **non-key** fragment input;
2820
+ * - `remove` → a `delete` whose `keys` is the whole-keys sentinel + `keyFields`.
2821
+ *
2822
+ * The resolved lifecycle's effect set is attached as the empty per-fragment hook.
2823
+ *
2824
+ * @param fragment The fragment to compile.
2825
+ * @param index Its 0-based position in the mutation's fragment list (the
2826
+ * cross-fragment reference index). `0` for a single-fragment compile.
2827
+ * @param resolveEntityRef Resolver for a `$.entity[i].field` cross-fragment
2828
+ * reference (multi-fragment compile), or `null` for a single-fragment compile
2829
+ * (where such a reference is a hard error).
2830
+ */
2831
+ declare function compileFragment(fragment: MutationFragment, index?: number, resolveEntityRef?: EntityRefResolver | null): CompiledFragment;
2832
+ /**
2833
+ * Compile a single-fragment {@link CommandPlan} into its {@link CompiledFragment}.
2834
+ * The N-fragment atomic merge is **#90** ({@link compileMutationPlan}); this entry
2835
+ * point is the single-fragment fast path used by #83 and by the multi-fragment
2836
+ * compiler for `N === 1` (no behavioral change). A cross-fragment reference is a
2837
+ * hard error here — there is no earlier fragment to read from.
2838
+ *
2839
+ * @throws if `plan` is not a {@link CommandPlan}, or declares more than one fragment.
2840
+ */
2841
+ declare function compileSingleFragmentPlan(plan: CommandPlan): CompiledFragment;
2842
+ /**
2843
+ * The result of compiling a (1..N)-fragment {@link CommandPlan} (#90): the ordered
2844
+ * per-fragment {@link CompiledFragment}s, merged so they form **one atomic
2845
+ * `TransactWriteItems`**. For a single fragment this is exactly the #83 result with
2846
+ * `fragments.length === 1` (no regression); for N≥2 the fragments are validated
2847
+ * together — same-item conflicts rejected, cross-fragment `$.entity[i].field`
2848
+ * dependencies resolved (circular deps rejected), and the composed item count
2849
+ * capped at {@link MAX_TRANSACT_COMPOSE_ITEMS}.
2850
+ *
2851
+ * The **primary fragment** is fragment 0 — the entity the public Command IF is
2852
+ * keyed by and read back from (`returnSelection`); the proposal's `CreatePost`
2853
+ * keys on `Post` (fragment 0), with `AuditLog` (fragment 1) composed atomically.
2854
+ */
2855
+ interface CompiledMutationPlan {
2856
+ /** The mutation name (documentary; surfaces in compiler errors). */
2857
+ readonly name: string;
2858
+ /** Each fragment compiled to its base op, in declaration order. */
2859
+ readonly fragments: readonly CompiledFragment[];
2860
+ }
2861
+ /**
2862
+ * Compile a (1..N)-fragment {@link CommandPlan} into one **atomically composable**
2863
+ * {@link CompiledMutationPlan} (issue #90; proposal §3, "CROSS-FRAGMENT composition
2864
+ * + validation"). Each fragment is compiled with #83's per-fragment compiler; the
2865
+ * resulting items are **merged into one `TransactWriteItems`**, with every MUST AC
2866
+ * enforced at build time:
2867
+ *
2868
+ * 1. **Cross-fragment data dependencies** — a fragment's `$.entity[i].field` leaf
2869
+ * is resolved to fragment `i`'s binding for `field` (a build-time alias, so the
2870
+ * value renders from the same shared input at execution; a `TransactWriteItems`
2871
+ * cannot read another item mid-transaction). A reference to a **same-or-later**
2872
+ * fragment (`i ≥ consumer`) is a **circular** dependency and is rejected; an
2873
+ * out-of-range index, or a field the producer does not write, is rejected.
2874
+ * 2. **Same-item conflict** — two fragments that write the **same primary key**
2875
+ * ({@link keySignature}) in one transaction are rejected (DynamoDB rejects a
2876
+ * transaction touching one item twice), naming the conflicting fragments.
2877
+ * 3. **≤25 items** — a composed plan over {@link MAX_TRANSACT_COMPOSE_ITEMS} base
2878
+ * items is a hard error (an atomic transaction is NEVER split — atomicity, #64).
2879
+ *
2880
+ * Because fragment `i` is fully compiled before fragment `i+1`, and a cross-fragment
2881
+ * reference may only point **backward** (to an already-compiled fragment), resolving
2882
+ * dependencies in declaration order is itself the topological order — a forward /
2883
+ * self reference is the only cycle shape and is rejected directly, so no separate
2884
+ * cycle search is needed.
2885
+ *
2886
+ * @throws if `plan` is not a {@link CommandPlan}; on a malformed / circular / dangling
2887
+ * cross-fragment reference; on a same-item conflict; or on >25 composed items.
2888
+ */
2889
+ declare function compileMutationPlan(plan: CommandPlan): CompiledMutationPlan;
2890
+
2891
+ /**
2892
+ * Contract DSL — `publicQueryModel` / `publicCommandModel` (issue #58, CQRS
2893
+ * Contract layer, Epic #57; spec `docs/cqrs-contract.md`).
2894
+ *
2895
+ * A **QueryModel / CommandModel** is the public, storage-independent interface
2896
+ * of a CQRS (logical) service. It is **keyed** — its Key *is* the access pattern
2897
+ * — and holds **one or more named Methods**, each a distinct **use case** (e.g.
2898
+ * `get` + `summary` over the same key, differing only in projection). The Key is
2899
+ * the access pattern; the Method is the use case.
2900
+ *
2901
+ * This module sits **on top of** the existing definition DSL
2902
+ * (`defineQuery` / `defineList` / `definePut` / `defineUpdate` / `defineDelete`,
2903
+ * `src/define/define.ts`) and shares its internal representation
2904
+ * ({@link OperationDefinition}). A contract method body resolves to a single
2905
+ * declarative operation on the underlying model; the closure is **never**
2906
+ * serialized — only the resolved declaration is captured into the IR.
2907
+ *
2908
+ * ```ts
2909
+ * const ArticleById = publicQueryModel<ArticleIdKey>()({
2910
+ * get: (keys, params) =>
2911
+ * Article.query(keys, { articleId: true, title: true, body: true }, params),
2912
+ * });
2913
+ * ```
2914
+ *
2915
+ * ## Method signatures (the generation source)
2916
+ *
2917
+ * Every method has one uniform signature, accepting **either one key or an array
2918
+ * of keys** (the array form is what makes a contract uniformly batch-resolvable):
2919
+ *
2920
+ * ```ts
2921
+ * type QueryMethod<TKey, TParams, TResult> = (key: TKey | readonly TKey[], params: TParams) => TResult;
2922
+ * type CommandMethod<TKey, TParams, TResult> = (key: TKey | readonly TKey[], params: TParams) => TResult;
2923
+ * ```
2924
+ *
2925
+ * These two types are the **generation source** for every binding target
2926
+ * (OpenAPI / Python / PHP / Go / Rust) — see the proposal.
2927
+ *
2928
+ * ## Method names are use cases, not operations
2929
+ *
2930
+ * A method is named for its **use case** (`get` / `summary` / `recent` /
2931
+ * `disable`), never for the storage op it runs. Whether it resolves internally
2932
+ * to a `query` (point lookup) or a `list` (partition read) is the **Runtime's**
2933
+ * concern and is invisible to callers and to generated bindings. The internal op
2934
+ * still determines the **derived** `resolution` / `inputArity` facts (below), but
2935
+ * those are bookkeeping, not part of the method's public name.
2936
+ *
2937
+ * ## Derived facts: `resolution` and `inputArity`
2938
+ *
2939
+ * Per the proposal's N+1 Safety section these are **derived** from the internal
2940
+ * op kind, never hand-written:
2941
+ *
2942
+ * - a unique-key `query` / `GetItem` → `resolution: 'point'`, `inputArity:
2943
+ * 'either'` (a key array becomes one `BatchGetItem`);
2944
+ * - a partition `list` / `Query` → `resolution: 'range'`, `inputArity: 'single'`
2945
+ * (DynamoDB cannot coalesce N partition `Query`s — feeding an array would be an
2946
+ * N+1 fan-out, so the range method only accepts a single key).
2947
+ *
2948
+ * Command methods additionally carry an explicit **result type** (`void` | a
2949
+ * `Result` | the updated entity) as part of the contract; see
2950
+ * {@link CommandResultKind}.
2951
+ *
2952
+ * ## Build-time hardening (reused from `defineTransaction`)
2953
+ *
2954
+ * The method body is evaluated at definition time with **throwing sentinels** in
2955
+ * place of `keys` / `params`, recorded through a model recorder, and subjected to
2956
+ * the **same** hardening `defineTransaction` (`src/define/transaction.ts`) applies
2957
+ * to value positions:
2958
+ *
2959
+ * 1. **Throwing sentinel Proxy** — every property / method access other than the
2960
+ * internal brand reads and primitive coercion throws at the access site.
2961
+ * 2. **Per-access coercion ledger (primary boundary)** — a faithful reference is
2962
+ * stored as the Proxy object and is *never* coerced, so any coercion of a
2963
+ * `params` field is positive proof the body consumed it into a transform /
2964
+ * branch / interpolation and the build is rejected.
2965
+ * 3. **Differential evaluation (secondary)** — the body runs twice with disjoint
2966
+ * per-field markers; every recorded leaf must be a faithful reference or a
2967
+ * byte-identical literal across passes.
2968
+ * 4. **Consumed-field check** — a `params` field the body accessed but that never
2969
+ * reached the recorded operation (a silent value branch) is rejected.
2970
+ *
2971
+ * So any non-declarative body (a transform, a value branch, a coercion, arbitrary
2972
+ * JS over `keys` / `params`) is rejected at build time — never silently emitted
2973
+ * as a wrong spec. The #58 issue was the **DSL + IR + type-level surface only**:
2974
+ * serialization to the JSON SSoT (#59) and execution against DynamoDB (#62) were
2975
+ * out of scope there.
2976
+ *
2977
+ * ## External Query composition (`query` / `from`, issue #63)
2978
+ *
2979
+ * The {@link query} / {@link from} primitive (added by #63) extends a read method
2980
+ * body with a **build-time-resolved, in-process** reference to **another query
2981
+ * contract's** method — relation chaining where the link is a contract reference
2982
+ * instead of a model relation (there is no protocol / transport). Placed at a
2983
+ * `select` property (`as: query(OtherContract.method, { childKey: from("$.field") })`),
2984
+ * it is recorded as a declarative {@link RecordedCompose} edge on the method op
2985
+ * (the property name becomes `as`, and it is removed from the read projection).
2986
+ * The binding stays declarative — `from` admits only a literal `$`-rooted path —
2987
+ * so no arbitrary JS reaches a key binding. The serializer (#59) recovers the
2988
+ * referenced contract name by identity and emits the proposal's `compose` shape;
2989
+ * the N+1 checker (#60) enforces the `point`-child rule; the runtimes (#62 infra
2990
+ * reused) resolve the child once, batched, across all parent records.
2991
+ */
2992
+
2993
+ /**
2994
+ * The **resolution kind** of a method, derived from its internal op:
2995
+ *
2996
+ * - `'point'` — target keys are known (unique-key `query` / `GetItem`, fixed key
2997
+ * sets); coalesces to a `BatchGetItem` for a key array.
2998
+ * - `'range'` — target key set is unknown (partition `list` / `Query`); one
2999
+ * request per partition key, so it is only ever safe for a single key.
3000
+ */
3001
+ type Resolution = 'point' | 'range';
3002
+ /**
3003
+ * What input arity a method accepts, derived from its `resolution`:
3004
+ *
3005
+ * - `'either'` — a single key **or** an array (a `point` read; the array form is
3006
+ * one `BatchGetItem`).
3007
+ * - `'single'` — a single key only (a `range` read; an array would be an N+1
3008
+ * fan-out and is rejected by construction).
3009
+ * - `'array'` — an array only (reserved; not produced by the current resolvers).
3010
+ */
3011
+ type InputArity = 'single' | 'array' | 'either';
3012
+ /**
3013
+ * The category of a {@link CommandMethod}'s declared result, part of the
3014
+ * contract (it surfaces in OpenAPI and every binding):
3015
+ *
3016
+ * - `'void'` — fire-and-forget write (no body);
3017
+ * - `'result'` — an outcome/status object (e.g. `{ ok, version }`);
3018
+ * - `'entity'` — the updated entity (the post-write projection).
3019
+ *
3020
+ * The concrete `TResult` type is preserved at the type level on
3021
+ * {@link CommandMethodSpec}; this discriminant is the runtime/SSoT-facing label.
3022
+ */
3023
+ type CommandResultKind = 'void' | 'result' | 'entity';
3024
+ /**
3025
+ * The formal **query** method signature (proposal: "Query Method — Common
3026
+ * Interface"). Accepts a single key **or** an array of keys and returns
3027
+ * `TResult`. This one type is the generation source for every binding target.
3028
+ *
3029
+ * @typeParam TKey - The contract Key (the access pattern / join key).
3030
+ * @typeParam TParams - The retrieval options (pagination / cursor / consistency).
3031
+ * @typeParam TResult - The method's result type.
3032
+ */
3033
+ type QueryMethod<TKey, TParams, TResult> = (key: TKey | readonly TKey[], params: TParams) => TResult;
3034
+ /**
3035
+ * The formal **command** method signature (proposal: "Command Definition").
3036
+ * Symmetric to {@link QueryMethod}, with an explicit result type. A single key
3037
+ * maps to one write op; an array maps to a batched write.
3038
+ *
3039
+ * @typeParam TKey - The contract Key (the target identity).
3040
+ * @typeParam TParams - The call params.
3041
+ * @typeParam TResult - The declared result type (`void` | a `Result` | entity).
3042
+ */
3043
+ type CommandMethod<TKey, TParams, TResult> = (key: TKey | readonly TKey[], params: TParams) => TResult;
3044
+ declare const KEY_REF_BRAND: unique symbol;
3045
+ declare const KEY_FIELD_REF_BRAND: unique symbol;
3046
+ /**
3047
+ * The captured `keys` argument of a contract method body. A branded sentinel that
3048
+ * supports the two faithful uses the proposal's examples need:
3049
+ *
3050
+ * - **passed whole** into a model op's key position
3051
+ * (`Model.query(keys, …)` / `Model.updateItem(keys, …)`) — the point / write form;
3052
+ * - **destructured per field** to rebuild a partition key
3053
+ * (`Model.list({ categoryId: keys.categoryId }, …)`) — the range form. Each
3054
+ * `keys.<field>` mints a faithful {@link ContractKeyFieldRef}.
3055
+ *
3056
+ * Both forms are declarative: the recorded key slot is either this sentinel
3057
+ * itself (whole) or a `{ field: ContractKeyFieldRef }` record. Any non-faithful
3058
+ * use — coercing `keys`, or transforming a `keys.<field>` ref — is rejected by the
3059
+ * same coercion ledger / differential checks as `params`.
3060
+ */
3061
+ interface ContractKeyRef {
3062
+ readonly [KEY_REF_BRAND]: true;
3063
+ }
3064
+ /**
3065
+ * A captured reference to a single field of the method's `keys` argument
3066
+ * (`keys.<field>`), minted when a range body rebuilds a partition key. Branded so
3067
+ * the verifier can distinguish a faithful key-field reference from a literal, and
3068
+ * so it cannot be silently coerced.
3069
+ */
3070
+ interface ContractKeyFieldRef {
3071
+ readonly [KEY_FIELD_REF_BRAND]: true;
3072
+ /** The key field name, e.g. `categoryId`. */
3073
+ readonly field: string;
3074
+ /** The template token this reference renders to, e.g. `{key.categoryId}`. */
3075
+ readonly token: string;
3076
+ }
3077
+ /** Runtime guard: is `value` a {@link ContractKeyRef} (the whole key sentinel)? */
3078
+ declare function isContractKeyRef(value: unknown): value is ContractKeyRef;
3079
+ /**
3080
+ * Mint a faithful {@link ContractKeyFieldRef} for a named key field — a plain
3081
+ * (non-Proxy) reference rendering the `{key.field}` differential token but, at a
3082
+ * **value** position (a `put` item field), serialized as `{field}` (see
3083
+ * `templateLeaf`, `src/spec/operations.ts`). Used by the **mutation compiler**
3084
+ * (#83) to mark which of a `create` fragment's item fields are the model's
3085
+ * primary-key (contract Key) fields, so the existing serializer recovers the
3086
+ * contract Key from the put item (`keyFieldsOf`) exactly as it does for a
3087
+ * hand-written #64 `put` that binds `keys.<field>` into the item. Mirrors
3088
+ * {@link mintContractParamRef}.
3089
+ *
3090
+ * @param field The key field name (rendered `{field}` at a value position).
3091
+ */
3092
+ declare function mintContractKeyFieldRef(field: string): ContractKeyFieldRef;
3093
+ /**
3094
+ * The whole-`keys` sentinel value an `update` / `delete` op records in its key
3095
+ * slot when the body passed `keys` whole into the op's key position. Exposed so
3096
+ * the **mutation compiler** (#83) can build an equivalent planned `update` /
3097
+ * `delete` op without re-entering the hardening machinery (a mutation has no
3098
+ * `keys` argument — its key fields are bound from `$.input.*`, captured via
3099
+ * {@link ContractMethodOp.keyFields}).
3100
+ */
3101
+ declare function wholeKeysSentinel(): ContractKeyRef;
3102
+ /** Runtime guard: is `value` a {@link ContractKeyFieldRef}? */
3103
+ declare function isContractKeyFieldRef(value: unknown): value is ContractKeyFieldRef;
3104
+ declare const PARAM_REF_BRAND: unique symbol;
3105
+ /**
3106
+ * A captured reference to a field of the method's `params` argument (`{<field>}`).
3107
+ * Branded so the recorder can distinguish a faithful reference from a concrete
3108
+ * literal, and so a stray reference cannot be silently coerced.
3109
+ */
3110
+ interface ContractParamRef {
3111
+ readonly [PARAM_REF_BRAND]: true;
3112
+ /** The template token this reference renders to, e.g. `{limit}`. */
3113
+ readonly token: string;
3114
+ /** The field name on `params`, e.g. `limit`. */
3115
+ readonly field: string;
3116
+ }
3117
+ /** Runtime guard: is `value` a {@link ContractParamRef}? */
3118
+ declare function isContractParamRef(value: unknown): value is ContractParamRef;
3119
+ /**
3120
+ * Mint a faithful {@link ContractParamRef} for a named field — a concrete
3121
+ * (non-Proxy) reference rendering the `{field}` token. Unlike the hardening
3122
+ * sentinels {@link makeParamFieldRef} builds during a contract method body (which
3123
+ * throw on any transform to detect non-declarative use), this is a *plain* ref
3124
+ * used by the **mutation compiler** (#83, `src/spec/mutation-command.ts`): a
3125
+ * mutation fragment's `input` binding is captured as a {@link MutationInputRef}
3126
+ * (its own declarative sentinel), and the compiler translates each one into this
3127
+ * `ContractParamRef` so the **existing** contract serializer
3128
+ * ({@link opToDefinition} → {@link buildCommandSpec}) and TS runtime
3129
+ * ({@link renderLeaf}) consume it with **zero** new template / param machinery —
3130
+ * the param name and `{token}` are the input field name, exactly as a hand-written
3131
+ * `params.field` reference would render.
3132
+ *
3133
+ * @param field The param / input field name (the rendered token is `{field}`).
3134
+ */
3135
+ declare function mintContractParamRef(field: string): ContractParamRef;
3136
+ declare const FROM_REF_BRAND: unique symbol;
3137
+ declare const COMPOSE_NODE_BRAND: unique symbol;
3138
+ /**
3139
+ * A parent-result source path produced by {@link from} (proposal "External
3140
+ * Query"). It names the field of the **parent** method's result that supplies a
3141
+ * composed child's key, e.g. `from("$.billingAccountId")`. The path is a plain
3142
+ * `"$.field"` string — the binding is **declarative** (no arbitrary JS): the
3143
+ * value is read from the resolved parent record at execution time, never computed.
3144
+ *
3145
+ * The grammar is intentionally minimal — a single dotted field path rooted at
3146
+ * `$` (the parent record). `$.a.b` reaches a nested field; anything else (a bare
3147
+ * field with no `$`, an index, a wildcard, an empty path) is rejected by
3148
+ * {@link from} at build time so a malformed binding can never reach the SSoT.
3149
+ */
3150
+ interface ContractFromRef {
3151
+ readonly [FROM_REF_BRAND]: true;
3152
+ /** The `$`-rooted source path on the parent result, e.g. `$.billingAccountId`. */
3153
+ readonly path: string;
3154
+ }
3155
+ /** Runtime guard: is `value` a {@link ContractFromRef} (a `from(...)` binding)? */
3156
+ declare function isContractFromRef(value: unknown): value is ContractFromRef;
3157
+ /**
3158
+ * A resolved External Query composition node produced by {@link query} and placed
3159
+ * at a `select` property of a contract method body (proposal "External Query"):
3160
+ *
3161
+ * ```ts
3162
+ * Account.query(keys, {
3163
+ * accountId: true,
3164
+ * billing: query(BillingPlanQueries.get, { accountId: from("$.billingAccountId") }),
3165
+ * })
3166
+ * ```
3167
+ *
3168
+ * It carries a faithful reference to the **referenced method spec** (so the
3169
+ * serializer can recover the referenced contract's *name* by identity against the
3170
+ * contract map, and its derived `resolution` / `cardinality` facts) plus the
3171
+ * declarative child-key binding (child key field → a {@link ContractFromRef}).
3172
+ * The owning `select` key becomes the composition's `as` property at record time
3173
+ * (it is not known to {@link query} itself).
3174
+ */
3175
+ interface ContractComposeNode {
3176
+ readonly [COMPOSE_NODE_BRAND]: true;
3177
+ /** The referenced query method spec (`OtherContract.method`). */
3178
+ readonly method: QueryMethodSpec<unknown, unknown, unknown>;
3179
+ /** The child-key binding: child key field → parent-result `from` path. */
3180
+ readonly bind: Readonly<Record<string, ContractFromRef>>;
3181
+ }
3182
+ /** Runtime guard: is `value` a {@link ContractComposeNode} (a `query(...)` node)? */
3183
+ declare function isContractComposeNode(value: unknown): value is ContractComposeNode;
3184
+ /**
3185
+ * Declare a parent-result source path for an External Query child key binding
3186
+ * (proposal "External Query"). Used **only** inside a {@link query} binding:
3187
+ *
3188
+ * ```ts
3189
+ * query(BillingPlanQueries.get, { accountId: from("$.billingAccountId") })
3190
+ * ```
3191
+ *
3192
+ * The path is `$`-rooted (the parent record) and dotted; the bound child-key
3193
+ * field is read from that path of each resolved parent record at execution time.
3194
+ * The binding is **declarative** — `from` accepts only a literal path string and
3195
+ * rejects any other shape, so no arbitrary JS can sneak into a key binding.
3196
+ *
3197
+ * @param path A `$`-rooted dotted path, e.g. `"$.billingAccountId"` or `"$.a.b"`.
3198
+ * @throws if `path` is not a non-empty `$`-rooted dotted field path.
3199
+ */
3200
+ declare function from(path: string): ContractFromRef;
3201
+ /**
3202
+ * Reference another query contract's method as an **External Query** composition
3203
+ * child, bound to the parent result by a declarative `from` mapping (proposal
3204
+ * "Query Composition" / "External Query"). Place the result at a `select`
3205
+ * property of a contract method body; the property name becomes the composed
3206
+ * value's `as`:
3207
+ *
3208
+ * ```ts
3209
+ * export const AccountAccess = publicQueryModel<AccountKey>()({
3210
+ * get: (keys, params) => Account.query(keys, {
3211
+ * accountId: true,
3212
+ * billing: query(BillingPlanQueries.get, { accountId: from("$.billingAccountId") }),
3213
+ * }, params),
3214
+ * });
3215
+ * ```
3216
+ *
3217
+ * This is **build-time-resolved, in-process** contract chaining — the relation
3218
+ * primitive extended to point at another contract's method instead of a model
3219
+ * relation. There is no protocol / transport: the runtime collects every bound
3220
+ * key produced by the parent step and resolves the referenced contract **once,
3221
+ * batched** (proposal "External Query"). The child MUST be `point` (the parent
3222
+ * may yield N records, so a `range` child would be an N+1 fan-out) — that rule is
3223
+ * owned by the N+1 checker (#60), which rejects a `range` child at build time.
3224
+ *
3225
+ * @param method The referenced query method (`OtherContract.method`), a resolved
3226
+ * {@link QueryMethodSpec}.
3227
+ * @param bind The child-key binding: child key field → a {@link from} path on
3228
+ * the parent result. Must be non-empty and every value a `from(...)` ref.
3229
+ * @throws if `method` is not a query method spec, or `bind` is empty / carries a
3230
+ * non-`from` value.
3231
+ */
3232
+ declare function query(method: QueryMethodSpec<any, any, any>, bind: Record<string, ContractFromRef>): ContractComposeNode;
3233
+ /**
3234
+ * The {@link QueryModelContract} that owns a resolved query method spec, or
3235
+ * `undefined` if the spec was not produced by {@link publicQueryModel} (so it has
3236
+ * no registered owner). Used by the serializer to resolve a composition's
3237
+ * referenced contract by identity.
3238
+ */
3239
+ declare function contractOfMethodSpec(method: QueryMethodSpec<unknown, unknown, unknown>): QueryModelContract<unknown, Record<string, QueryMethodSpec<unknown, unknown, unknown>>> | undefined;
3240
+ /**
3241
+ * The declarative internal operation a contract method body resolves to — the
3242
+ * resolved declaration captured into the IR (the closure itself is discarded).
3243
+ * Mirrors {@link OperationDefinition} but is produced by the model recorder
3244
+ * handed to a contract method body rather than a `define*` entry point.
3245
+ *
3246
+ * @typeParam Op - The internal op kind (`query` | `list` | `put` | `update` |
3247
+ * `delete`).
3248
+ */
3249
+ interface ContractMethodOp<Op extends OperationKind = OperationKind> {
3250
+ /** @internal Marks this object as a recorded contract method op. */
3251
+ readonly __isContractMethodOp: true;
3252
+ /** The model the op targets (name + runtime class). */
3253
+ readonly entity: EntityRef;
3254
+ /** The internal op kind. Determines the derived `resolution` / `inputArity`. */
3255
+ readonly operation: Op;
3256
+ /**
3257
+ * The captured key slot. Either the whole {@link ContractKeyRef} sentinel
3258
+ * (passed faithfully into the op's key position — the point / write form) or a
3259
+ * `{ field: ContractKeyFieldRef }` record rebuilt from `keys.<field>` (the range
3260
+ * form). For `put` it is the inert {@link PUT_HAS_NO_KEY} marker. Stored as
3261
+ * evidence of faithfulness; never serialized.
3262
+ */
3263
+ readonly keys: ContractKeyRef | Readonly<Record<string, unknown>>;
3264
+ /**
3265
+ * The **explicit contract Key field names** captured from the factory's
3266
+ * key-field argument (`publicQueryModel<EmailKey>(['email'])(...)`, issue #71).
3267
+ * Present only for a **whole-`keys`** op (the {@link keys} slot is the whole
3268
+ * {@link ContractKeyRef} sentinel) when the author supplied a field list, or
3269
+ * when the model-derived factory form defaulted it to the model's primary-key
3270
+ * input fields. It is the SSoT for "which fields the Key is composed of" — fed
3271
+ * through {@link resolveKey} so the serializer emits the correct base-table PK
3272
+ * **or** GSI (`indexName` + GSI key template) key, and so the planner can tell a
3273
+ * coalescible base-table point (`GetItem` / `BatchGetItem`) from a non-coalescible
3274
+ * GSI point (a unique-GSI `Query`).
3275
+ *
3276
+ * Absent when the body rebuilds a partition key from `keys.<field>` references
3277
+ * (the range form already captures field names in the {@link keys} record) or
3278
+ * when no explicit list was given to a `publicQueryModel<TKey>()` whole-key form
3279
+ * (the legacy backward-compatible path, which then defaults to the primary key).
3280
+ */
3281
+ readonly keyFields?: readonly string[];
3282
+ /** The select projection for `query` / `list`; `undefined` for writes. */
3283
+ readonly select?: Readonly<Record<string, unknown>>;
3284
+ /** The changes structure for `update`; `undefined` otherwise. */
3285
+ readonly changes?: Readonly<Record<string, unknown>>;
3286
+ /** The item structure for `put`; `undefined` otherwise. */
3287
+ readonly item?: Readonly<Record<string, unknown>>;
3288
+ /** The optional declarative write condition for writes. */
3289
+ readonly condition?: ConditionInput;
3290
+ /**
3291
+ * External Query compositions (#63) extracted from the recorded `select`: each
3292
+ * `query(OtherContract.method, { childKey: from("$.parentField") })` node placed
3293
+ * at a `select` property becomes one {@link RecordedCompose} here (the property
3294
+ * name is its `as`), and the property is removed from the read projection. Absent
3295
+ * when the body declares no composition.
3296
+ */
3297
+ readonly compose?: readonly RecordedCompose[];
3298
+ }
3299
+ /**
3300
+ * A recorded External Query composition edge on a contract method op (#63). The
3301
+ * declarative result of a `query(OtherContract.method, { … })` call placed at a
3302
+ * `select` property: it pairs the property name (`as`) with a faithful reference
3303
+ * to the referenced method spec and the declarative child-key binding. The
3304
+ * referenced contract's *name* and the child's derived `resolution` / `cardinality`
3305
+ * are recovered by the serializer (`src/spec/contracts.ts`) from the method spec
3306
+ * by identity against the contract map; they are not duplicated here.
3307
+ */
3308
+ interface RecordedCompose {
3309
+ /** The result property the composed value attaches to (the `select` key). */
3310
+ readonly as: string;
3311
+ /** The referenced query method spec (`OtherContract.method`). */
3312
+ readonly method: QueryMethodSpec<unknown, unknown, unknown>;
3313
+ /** The child-key binding: child key field → parent-result `from` path. */
3314
+ readonly bind: Readonly<Record<string, ContractFromRef>>;
3315
+ }
3316
+ /**
3317
+ * The **call signature** a contract method exposes to a caller, narrowed by its
3318
+ * decided {@link InputArity} — the type-level half of N+1 rule (a) ("array into a
3319
+ * `range` method", #60). Given a method's `inputArity`:
3320
+ *
3321
+ * - `'single'` (a `range` method) — the key argument is a **single** `TKey` only;
3322
+ * passing an array (`readonly TKey[]`) is a **type error** by construction, so a
3323
+ * `range` method's array overload does not type-check.
3324
+ * - `'either'` (a `point` read / known-key write) — a single key **or** an array
3325
+ * (the array form coalesces to one `BatchGetItem` / batched write).
3326
+ * - `'array'` — an array only (reserved; not produced by the current resolvers).
3327
+ *
3328
+ * This mirrors the proposal's "the generated binding's types encode `inputArity`
3329
+ * (single → bare argument, array → array argument)". It is the type-level narrowing
3330
+ * used by the runtime call surface (`executeQueryMethod`, `src/runtime/contract-runtime.ts`):
3331
+ * the executor keys its key-argument type on the named method's **literal** arity
3332
+ * `A` (carried on {@link QueryMethodSpec} when the body is tagged with
3333
+ * {@link point} / {@link range}), so feeding an array into a `'single'` method is a
3334
+ * `tsc` compile error — the **primary** N+1 rule (a) layer, complementing the
3335
+ * build-time SSoT check ({@link assertContractN1Safe}) and the runtime backstop.
3336
+ *
3337
+ * @typeParam A - The decided input arity (a literal `InputArity`).
3338
+ * @typeParam TKey - The contract Key.
3339
+ * @typeParam TParams - The method's params type.
3340
+ * @typeParam TResult - The method's result type.
3341
+ */
3342
+ type ContractCallSignature<A extends InputArity, TKey, TParams, TResult> = (key: A extends 'single' ? TKey : A extends 'array' ? readonly TKey[] : TKey | readonly TKey[], params: TParams) => TResult;
3343
+ /**
3344
+ * The resolved IR of one **query** method. Carries the formal method type at the
3345
+ * type level (`QueryMethod<TKey, TParams, TResult>`) and, at runtime, the resolved
3346
+ * internal op plus the **derived** facts (`resolution` / `inputArity`).
3347
+ *
3348
+ * @typeParam TKey - The contract Key.
3349
+ * @typeParam TParams - The method's params type.
3350
+ * @typeParam TResult - The method's result type.
3351
+ * @typeParam A - The method's **literal** {@link InputArity} when the author
3352
+ * tagged the body with {@link point} / {@link range} (the type-level half of
3353
+ * N+1 rule (a), threaded into the call surface so an array fed into a `'single'`
3354
+ * method is a `tsc` error — see {@link ContractCallSignature} and
3355
+ * `executeQueryMethod`). Defaults to the wide `InputArity` for an untagged body,
3356
+ * which then relies on the build-time checker + the runtime backstop only.
3357
+ */
3358
+ interface QueryMethodSpec<TKey, TParams, TResult, A extends InputArity = InputArity> {
3359
+ /** @internal Marks this object as a query method spec. */
3360
+ readonly __methodKind: 'query';
3361
+ /** The resolved internal operation (closure discarded). */
3362
+ readonly op: ContractMethodOp;
3363
+ /** Derived: `'point'` (unique-key query) or `'range'` (partition list). */
3364
+ readonly resolution: Resolution;
3365
+ /**
3366
+ * Derived: `'either'` for a base-table `point`, `'single'` for a `range` or a
3367
+ * unique-GSI `point`. Typed as the method's **literal** arity `A` when the body
3368
+ * was tagged ({@link point} / {@link range}); a tagged literal is verified
3369
+ * against this derived value at build time (a mismatch is a build error), so the
3370
+ * type cannot lie about the runtime arity.
3371
+ */
3372
+ readonly inputArity: A;
3373
+ /**
3374
+ * @internal The method's own name in its contract (`'get'`, `'summary'`, …),
3375
+ * stamped by the factory. An External Query composition (#63) references this
3376
+ * spec object; the serializer recovers the referenced method name from here and
3377
+ * the referenced contract name by identity ({@link contractOfMethodSpec}).
3378
+ */
3379
+ readonly __methodName?: string;
3380
+ /** @internal Phantom carrier retaining the formal `QueryMethod` type. */
3381
+ readonly __signature?: QueryMethod<TKey, TParams, TResult>;
3382
+ }
3383
+ /**
3384
+ * The resolved IR of one **command** method. Symmetric to {@link QueryMethodSpec},
3385
+ * with an explicit declared result (`void` | a `Result` | the updated entity).
3386
+ *
3387
+ * @typeParam TKey - The contract Key.
3388
+ * @typeParam TParams - The method's params type.
3389
+ * @typeParam TResult - The method's declared result type.
3390
+ */
3391
+ interface CommandMethodSpec<TKey, TParams, TResult> {
3392
+ /** @internal Marks this object as a command method spec. */
3393
+ readonly __methodKind: 'command';
3394
+ /** The resolved internal write operation (closure discarded). */
3395
+ readonly op: ContractMethodOp;
3396
+ /**
3397
+ * Derived input arity: a single key maps to one write op; an array maps to a
3398
+ * batched write (a transaction / `BatchWriteItem`). Writes accept `'either'`.
3399
+ */
3400
+ readonly inputArity: InputArity;
3401
+ /**
3402
+ * The category of the declared result type (`void` | `result` | `entity`),
3403
+ * part of the contract (it surfaces in OpenAPI and every binding). Derived from
3404
+ * the internal op when not explicitly annotated: an `update` returning the
3405
+ * entity → `'entity'`; a `delete` → `'void'`.
3406
+ */
3407
+ readonly result: CommandResultKind;
3408
+ /**
3409
+ * The per-call execution **mode** (issue #101): `'transaction'` (default, atomic
3410
+ * all-or-nothing) or `'parallel'` (non-atomic, per-op partial success). It drives
3411
+ * the key-array bulk resolution form directly (the serializer's `modeTargetFor`).
3412
+ * Present on every descriptor-authored command method.
3413
+ */
3414
+ readonly mode?: CommandMode;
3415
+ /**
3416
+ * The return projection of a {@link mutation}-derived command method (issue
3417
+ * #83): a boolean field map applied to the written entity as a **consistent
3418
+ * read-back** projection after the write commits (the proposal's "return =
3419
+ * read projection"). Present only on a `.plan(mutation)` method; a hand-written
3420
+ * #64 method (the closure form) omits it. JSON-safe (only boolean leaves), so
3421
+ * it serializes verbatim into {@link CommandContractMethodSpec.returnSelection}
3422
+ * and both runtimes apply the **same** projection.
3423
+ */
3424
+ readonly returnSelection?: Readonly<Record<string, boolean>>;
3425
+ /**
3426
+ * The composed write ops of a **multi-fragment** `mutation`-derived method
3427
+ * (issue #90): when a mutation declares **2+ fragments**, each fragment compiles
3428
+ * to one write {@link ContractMethodOp} and the whole set executes as **one
3429
+ * atomic `TransactWriteItems`** (NOT sequential writes). Present only for N≥2; a
3430
+ * single-fragment method carries only {@link op} (no regression — #83 behavior).
3431
+ * The merge (same-item conflict / cross-fragment dep / ≤25 enforcement) is done
3432
+ * at build time by `compileMutationPlan`; `op` is set to the **primary** fragment
3433
+ * (fragment 0 — the entity the method is keyed by and read back from) so the
3434
+ * Key / read-back machinery is unchanged.
3435
+ */
3436
+ readonly ops?: readonly ContractMethodOp[];
3437
+ /**
3438
+ * The referential-integrity assertions derived from the mutation's `requires`
3439
+ * effects (issue #84): one {@link DerivedConditionCheck} per
3440
+ * `w.exists(...)`, flattened across every fragment. Present (non-empty) only when
3441
+ * a fragment's lifecycle declares `requires`; absent otherwise (no regression —
3442
+ * the method then carries only its base op(s)). Each becomes a read-only
3443
+ * `ConditionCheck` (`attribute_exists`) item in the method's atomic
3444
+ * `TransactWriteItems`, and their presence **promotes** an otherwise single-op
3445
+ * method to a transaction: the serializer emits a {@link
3446
+ * import('../spec/types.js').TransactionItemSpec}-based `TransactionSpec` and
3447
+ * `single: { mode: 'transaction' }`, and the in-process runtime composes the base
3448
+ * write(s) + the ConditionCheck(s) into one transaction so an absent referent
3449
+ * rolls the WHOLE write back.
3450
+ */
3451
+ readonly conditionChecks?: readonly DerivedConditionCheck[];
3452
+ /**
3453
+ * The adjacency edge writes derived from the mutation's `edges` effects (issue
3454
+ * #85), flattened across every fragment in declaration order. Present (non-empty)
3455
+ * only when a fragment's lifecycle declares `edges`; absent otherwise (no
3456
+ * regression). Each {@link DerivedEdgeWrite} contributes its adjacency `Put` /
3457
+ * `Delete` item(s) to the method's atomic `TransactWriteItems`, and their presence
3458
+ * (like {@link conditionChecks}) **promotes** an otherwise single-op method to a
3459
+ * transaction so the edge moves / removes atomically with the entity write.
3460
+ */
3461
+ readonly edgeWrites?: readonly DerivedEdgeWrite[];
3462
+ /**
3463
+ * The derived (cascading) updates from the mutation's `derive` effects (issue
3464
+ * #85), flattened across every fragment in declaration order: each an atomic `ADD`
3465
+ * (`UpdateItem`) on a target counter (e.g. `User.postCount += 1`). Present
3466
+ * (non-empty) only when a fragment's lifecycle declares `derive`; absent otherwise
3467
+ * (no regression). Each contributes an `UpdateItem` (`ADD`) to the method's atomic
3468
+ * `TransactWriteItems` and **promotes** the method to a transaction.
3469
+ */
3470
+ readonly derivedUpdates?: readonly DerivedUpdate[];
3471
+ /**
3472
+ * The uniqueness guards derived from the mutation's `unique` effects (issue #86),
3473
+ * flattened across every fragment in declaration order. Present (non-empty) only
3474
+ * when a fragment's lifecycle declares `unique`; absent otherwise (no regression).
3475
+ * Each {@link DerivedUniqueGuard} contributes its marker-row `Put` / `Delete`
3476
+ * item(s) to the method's atomic `TransactWriteItems`, and their presence (like
3477
+ * {@link conditionChecks}) **promotes** an otherwise single-op method to a
3478
+ * transaction so a duplicate scoped value (the guard `Put`'s `attribute_not_exists`
3479
+ * failing) rolls the WHOLE write back — the entity is never created.
3480
+ */
3481
+ readonly uniqueGuards?: readonly DerivedUniqueGuard[];
3482
+ /**
3483
+ * The outbox events derived from the mutation's `emits` effects (issue #87),
3484
+ * flattened across every fragment in declaration order. Present (non-empty) only
3485
+ * when a fragment's lifecycle declares `emits`; absent otherwise (no regression).
3486
+ * Each {@link DerivedOutboxEvent} contributes its transactional-outbox `Put` to the
3487
+ * method's atomic `TransactWriteItems` — recording the event ATOMICALLY with the
3488
+ * entity write (event and state cannot diverge) — and their presence (like
3489
+ * {@link conditionChecks}) **promotes** an otherwise single-op method to a
3490
+ * transaction. The outbox row is drainable via `src/cdc/`; delivery is out of scope.
3491
+ */
3492
+ readonly outboxEvents?: readonly DerivedOutboxEvent[];
3493
+ /**
3494
+ * The client-token idempotency guard derived from the mutation's `idempotency`
3495
+ * effect (issue #87). Present only when a fragment's lifecycle declares
3496
+ * `idempotency`; absent otherwise (no regression). The {@link DerivedIdempotencyGuard}
3497
+ * contributes its `attribute_not_exists` guard `Put` to the method's atomic
3498
+ * `TransactWriteItems`, and its presence (like {@link conditionChecks}) **promotes**
3499
+ * an otherwise single-op method to a transaction so a same-token re-execution fails
3500
+ * the guard and rolls the WHOLE write back — no effect is double-applied.
3501
+ */
3502
+ readonly idempotencyGuard?: DerivedIdempotencyGuard;
3503
+ /** @internal Phantom carrier retaining the formal `CommandMethod` type. */
3504
+ readonly __signature?: CommandMethod<TKey, TParams, TResult>;
3505
+ }
3506
+ /**
3507
+ * A resolved **QueryModel** — a keyed public read contract holding one or more
3508
+ * named query methods. The Key (`TKey`) is the access pattern; each entry of
3509
+ * `methods` is a use case. Carries the contract `kind` and the resolved per-method
3510
+ * IR; the source method closures are discarded.
3511
+ *
3512
+ * @typeParam TKey - The contract Key (the access pattern / join key).
3513
+ * @typeParam M - The method map (name → resolved {@link QueryMethodSpec}).
3514
+ */
3515
+ interface QueryModelContract<TKey, M extends Record<string, QueryMethodSpec<TKey, any, any>>> {
3516
+ /** @internal Marks this object as a contract IR node. */
3517
+ readonly __isContract: true;
3518
+ /** Discriminant: a read contract. */
3519
+ readonly kind: 'query';
3520
+ /** The resolved, named query methods (use cases over the same Key). */
3521
+ readonly methods: M;
3522
+ /** @internal Phantom carrier retaining the contract Key type `TKey`. */
3523
+ readonly __keyType?: (value: TKey) => void;
3524
+ }
3525
+ /**
3526
+ * A resolved **CommandModel** — the write counterpart of {@link QueryModelContract},
3527
+ * completely symmetric: keyed, holding one or more named write-use-case methods.
3528
+ *
3529
+ * @typeParam TKey - The contract Key (the target identity).
3530
+ * @typeParam M - The method map (name → resolved {@link CommandMethodSpec}).
3531
+ */
3532
+ interface CommandModelContract<TKey, M extends Record<string, CommandMethodSpec<TKey, any, any>>> {
3533
+ /** @internal Marks this object as a contract IR node. */
3534
+ readonly __isContract: true;
3535
+ /** Discriminant: a write contract. */
3536
+ readonly kind: 'command';
3537
+ /** The resolved, named command methods (write use cases over the same Key). */
3538
+ readonly methods: M;
3539
+ /** @internal Phantom carrier retaining the contract Key type `TKey`. */
3540
+ readonly __keyType?: (value: TKey) => void;
3541
+ }
3542
+ /** Runtime guard: is `value` a {@link QueryModelContract}? */
3543
+ declare function isQueryModelContract(value: unknown): value is QueryModelContract<unknown, Record<string, QueryMethodSpec<unknown, unknown, unknown>>>;
3544
+ /** Runtime guard: is `value` a {@link CommandModelContract}? */
3545
+ declare function isCommandModelContract(value: unknown): value is CommandModelContract<unknown, Record<string, CommandMethodSpec<unknown, unknown, unknown>>>;
3546
+ /** The entity type a descriptor {@link ModelRef} (model class / `.asModel()` / thunk) targets. */
3547
+ type EntityOfRef<M> = M extends ModelStatic<infer T, infer _C> ? T : M extends abstract new (...args: never[]) => infer T ? T : M extends () => infer R ? EntityOfRef<R> : unknown;
3548
+ /** The projected item type for an entity `T` and a select `S` (relation-nest aware). */
3549
+ type QueryResultFor$1<T, S> = T extends DDBModel ? S extends Record<string, unknown> ? QueryResult<T, ProjectionOf<T, S>> : Record<string, unknown> : Record<string, unknown>;
3550
+ /** The TS value type a descriptor leaf (`param.*` or a literal) represents. */
3551
+ type LeafValueOf<L> = L extends Param<infer V> ? V : L;
3552
+ /** The contract Key inferred from a descriptor `key` record (its `param.*` / literal leaves). */
3553
+ type KeyOf<KeyRec> = {
3554
+ readonly [F in keyof KeyRec]: LeafValueOf<KeyRec[F]>;
3555
+ };
3556
+ /** The params a descriptor method accepts: the union of its `key` + `input` leaf value types. */
3557
+ type DescriptorParamsOf<D> = (D extends {
3558
+ readonly key: infer K;
3559
+ } ? KeyOf<K> : Record<never, never>) & (D extends {
3560
+ readonly input: infer I;
3561
+ } ? {
3562
+ readonly [F in keyof I]: LeafValueOf<I[F]>;
3563
+ } : Record<never, never>);
3564
+ /** The result of a read descriptor: a projected item (`query` → item | null, `list` → connection). */
3565
+ type ReadDescriptorResultOf<D> = D extends {
3566
+ readonly query: infer M;
3567
+ readonly select: infer S;
3568
+ } ? QueryResultFor$1<EntityOfRef<M>, S> | null : D extends {
3569
+ readonly list: infer M;
3570
+ readonly select: infer S;
3571
+ } ? {
3572
+ items: QueryResultFor$1<EntityOfRef<M>, S>[];
3573
+ cursor: string | null;
3574
+ } : unknown;
3575
+ /** A read descriptor's contract Key (from its `key` record). */
3576
+ type ReadDescriptorKeyOf<D> = D extends {
3577
+ readonly key: infer K;
3578
+ } ? KeyOf<K> : unknown;
3579
+ /** The result of a write descriptor: the read-back projection (when `result.select`) or void. */
3580
+ type WriteDescriptorResultOf<D> = D extends {
3581
+ readonly result: {
3582
+ readonly select: infer S;
3583
+ };
3584
+ } ? S extends Record<string, unknown> ? Readonly<Record<Extract<keyof S, string>, unknown>> | null : void : void;
3585
+ /** A write descriptor's contract Key (from its `key` record). */
3586
+ type WriteDescriptorKeyOf<D> = D extends {
3587
+ readonly key: infer K;
3588
+ } ? KeyOf<K> : unknown;
3589
+ /**
3590
+ * The query-method map accepted by the #101 **direct** form
3591
+ * (`publicQueryModel({ get: descriptor, … })`): each value is a
3592
+ * {@link PublicReadDescriptor}. The contract Key and per-method result are inferred
3593
+ * from each descriptor's `key` / `select`.
3594
+ */
3595
+ type ReadDescriptorMap = Record<string, PublicReadDescriptor>;
3596
+ /** Resolve a read-descriptor map to its {@link QueryModelContract}. */
3597
+ type ResolvedReadDescriptors<M extends ReadDescriptorMap> = QueryModelContract<M[keyof M] extends infer D ? ReadDescriptorKeyOf<D> : unknown, {
3598
+ readonly [K in keyof M]: QueryMethodSpec<ReadDescriptorKeyOf<M[K]>, DescriptorParamsOf<M[K]>, ReadDescriptorResultOf<M[K]>>;
3599
+ }>;
3600
+ /**
3601
+ * The command-method map accepted by the #101 **direct** form
3602
+ * (`publicCommandModel({ create: descriptor, … })`): each value is a
3603
+ * {@link PublicWriteDescriptor}, a {@link PublicComposeDescriptor} (a `mutation()`
3604
+ * wrapped with `input` / `result` / `mode`), or a bare composite `mutation()`
3605
+ * {@link CommandPlan}.
3606
+ */
3607
+ type WriteDescriptorMap = Record<string, PublicWriteDescriptor | PublicComposeDescriptor | CommandPlan>;
3608
+ /** Resolve a write-descriptor map to its {@link CommandModelContract}. */
3609
+ type ResolvedWriteDescriptors<M extends WriteDescriptorMap> = CommandModelContract<M[keyof M] extends infer D ? WriteDescriptorKeyOf<D> : unknown, {
3610
+ readonly [K in keyof M]: CommandMethodSpec<WriteDescriptorKeyOf<M[K]>, DescriptorParamsOf<M[K]>, WriteDescriptorResultOf<M[K]>>;
3611
+ }>;
3612
+ /**
3613
+ * Create a **QueryModel** from a **descriptor map** (issue #101, the only form):
3614
+ *
3615
+ * ```ts
3616
+ * export const UserQueries = publicQueryModel({
3617
+ * get: { query: User, key: { userId: param.string() }, select: { name: true } },
3618
+ * list: { list: GroupMembership, key: { groupId: param.string() }, select: { role: true } },
3619
+ * });
3620
+ * ```
3621
+ *
3622
+ * The single argument is the method map itself; the contract Key and each method's
3623
+ * result type are **inferred** from the descriptors' `key` / `select` — there is no
3624
+ * generic key parameter, no curried factory call, and no explicit Key-field list
3625
+ * (the legacy `publicQueryModel<TKey>(['email'])` / model-derived overloads were
3626
+ * withdrawn in #101). A GSI-keyed point read is expressed by its descriptor `key`
3627
+ * (e.g. `key: { email: param.string() }`); the build resolves the access pattern
3628
+ * from the model metadata, exactly as the curried key-field list once did. Each
3629
+ * descriptor's `key` fields carry `param.*` placeholders (the public input schema);
3630
+ * a `select` is the existing query projection (relation-nest aware), and may carry
3631
+ * External Query `query(...)` nodes.
3632
+ */
3633
+ declare function publicQueryModel<const M extends ReadDescriptorMap>(methods: M): ResolvedReadDescriptors<M>;
3634
+ /**
3635
+ * Create a **CommandModel** from a **descriptor map** (issue #101, the only form):
3636
+ *
3637
+ * ```ts
3638
+ * export const MembershipCommands = publicCommandModel({
3639
+ * create: { create: GroupMembership, key: { groupId: param.string(), userId: param.string() },
3640
+ * input: { role: param.string() }, result: { select: { role: true } } },
3641
+ * addMany: { create: GroupMembership, key: { … }, input: { … }, mode: 'parallel' },
3642
+ * join: mutation($ => ({ … })), // composite (multi-fragment, atomic) method
3643
+ * });
3644
+ * ```
3645
+ *
3646
+ * The single argument is the method map itself; the contract Key and each method's
3647
+ * read-back result are **inferred** from the descriptors' `key` / `result.select` —
3648
+ * there is no generic key parameter, no curried factory call, and no explicit
3649
+ * Key-field list (the legacy `publicCommandModel<TKey>()` / model-derived overloads
3650
+ * were withdrawn in #101). A descriptor's `mode` declares the per-call execution
3651
+ * mode (`'transaction'` default | `'parallel'`); `result` presence drives whether
3652
+ * the method reads back and returns the written entity.
3653
+ */
3654
+ declare function publicCommandModel<const M extends WriteDescriptorMap>(methods: M): ResolvedWriteDescriptors<M>;
3655
+ declare const PLANNED_COMMAND_BRAND: unique symbol;
3656
+ /**
3657
+ * The input param shape a {@link command} declares: a record of named
3658
+ * {@link Param} placeholders (`param.string()` / `param.number()` /
3659
+ * `param.literal(...)`). These are the **external** params of the public Command
3660
+ * IF — the method's caller-supplied input. The mutation fragment binds them by
3661
+ * name into the written entity (`$.<field>`), so each input field name must match
3662
+ * a fragment `$.field` reference / a model field the fragment writes.
3663
+ */
3664
+ type CommandInputShape = Readonly<Record<string, Param<unknown>>>;
3665
+ /**
3666
+ * The return projection a {@link command} declares (proposal §3: "return = read
3667
+ * projection"): a JSON-safe boolean field map applied to the written entity as a
3668
+ * **consistent read-back** after the write commits. Omit for a fire-and-forget
3669
+ * command (no projected item is returned).
3670
+ */
3671
+ type CommandSelectShape = Readonly<Record<string, boolean>>;
3672
+ /**
3673
+ * A **planned command method** (#83): the branded result of
3674
+ * `command({ input, select }).plan(mutation)`. It carries the captured fragment
3675
+ * IR (the {@link CommandPlan}), the public input param shape, and the return
3676
+ * `select`. {@link buildCommandContract} detects the brand and routes it to the
3677
+ * single-fragment mutation compiler (NOT through #64's closure path).
3678
+ *
3679
+ * The external surface is **params in / result out only** — the internal mutation
3680
+ * document is never exposed.
3681
+ *
3682
+ * @typeParam TInput - The input param shape (`{ field: Param<…> }`).
3683
+ * @typeParam TSelect - The return projection (`{ field: boolean }`), or `undefined`.
3684
+ */
3685
+ interface PlannedCommandMethod<TInput extends CommandInputShape = CommandInputShape, TSelect extends CommandSelectShape | undefined = CommandSelectShape | undefined> {
3686
+ readonly [PLANNED_COMMAND_BRAND]: true;
3687
+ /** The captured mutation IR (the fragment list). */
3688
+ readonly plan: CommandPlan;
3689
+ /** The public input param shape. */
3690
+ readonly input: TInput;
3691
+ /** The return projection (consistent read-back), or `undefined`. */
3692
+ readonly select: TSelect;
3693
+ }
3694
+ /** Runtime guard: is `value` a {@link PlannedCommandMethod} (#83)? */
3695
+ declare function isPlannedCommandMethod(value: unknown): value is PlannedCommandMethod;
3696
+ /** The per-call execution mode of a command method (#101): replaces `batch`. */
3697
+ type CommandMode = 'transaction' | 'parallel';
3698
+ /** A public **read** descriptor (issue #101): `{ query | list: Model, key, select, options? }`. */
3699
+ interface PublicReadDescriptor {
3700
+ readonly query?: ModelRef;
3701
+ readonly list?: ModelRef;
3702
+ readonly key: Readonly<Record<string, unknown>>;
3703
+ readonly select: Readonly<Record<string, unknown>>;
3704
+ readonly options?: Readonly<Record<string, unknown>>;
3705
+ }
3706
+ /** A public **write** descriptor (issue #101): `{ create | update | remove: Model, key, input?, condition?, result?, mode? }`. */
3707
+ interface PublicWriteDescriptor {
3708
+ readonly create?: ModelRef;
3709
+ readonly update?: ModelRef;
3710
+ readonly remove?: ModelRef;
3711
+ readonly key: Readonly<Record<string, unknown>>;
3712
+ readonly input?: Readonly<Record<string, unknown>>;
3713
+ readonly condition?: Readonly<Record<string, unknown>>;
3714
+ readonly result?: {
3715
+ readonly select?: Readonly<Record<string, boolean>>;
3716
+ readonly options?: unknown;
3717
+ };
3718
+ readonly mode?: CommandMode;
3719
+ }
3720
+ /**
3721
+ * A public **composite write** descriptor (issue #101) — the closure-free twin of
3722
+ * `command({ input, select }).plan(mutation)`. It wraps an internal
3723
+ * {@link CommandPlan} (`mutation(...)` / `definePlan(...)`) with the public input
3724
+ * param shape, the optional read-back projection, and the optional execution mode.
3725
+ * The mutation document is never exposed; only `input` params + `result.select`
3726
+ * cross the boundary.
3727
+ */
3728
+ interface PublicComposeDescriptor {
3729
+ readonly command: CommandPlan;
3730
+ readonly input?: Readonly<Record<string, unknown>>;
3731
+ readonly result?: {
3732
+ readonly select?: Readonly<Record<string, boolean>>;
3733
+ readonly options?: unknown;
3734
+ };
3735
+ readonly mode?: CommandMode;
3736
+ }
3737
+
3738
+ /**
3739
+ * Single-service contract **Runtime** — execution of CQRS query-contract methods
3740
+ * (issue #62, Epic #57; spec `docs/cqrs-contract.md`, "Query Method
3741
+ * — Common Interface" + "Cardinality matrix"). READS only; command/write
3742
+ * execution is #64 and single-contract composition / External Query is #63 — both
3743
+ * out of scope here.
3744
+ *
3745
+ * The Runtime is the **execution engine**: given a resolved query contract (a
3746
+ * {@link QueryModelContract} from #58, carrying per-method `resolution` /
3747
+ * `inputArity` and a resolved {@link ContractMethodOp}) it executes one method
3748
+ * for a single key **or** an array of keys and returns the correct result shape
3749
+ * from the proposal's cardinality matrix:
3750
+ *
3751
+ * | Input | `resolution` | Result shape | Notation |
3752
+ * | -------------- | ------------ | ------------------------------------- | ---------------- |
3753
+ * | `key` (single) | `point` | `Item \| null` | `key1 : result1` |
3754
+ * | `key` (single) | `range` | `Connection` (`{ items, cursor }`) | `key1 : resultM` |
3755
+ * | `keys[]` (N) | `point` | keyed `Map<keyId, Item \| null>` | `keyN : resultN` |
3756
+ * | `keys[]` (N) | `range` | **unreachable for a single contract** | `keyN : resultN×M` |
3757
+ *
3758
+ * ## On `keyN : resultN×M` (range + array) — unreachable here, by design
3759
+ *
3760
+ * The fourth matrix cell — a keyed map of connections — is **not reachable for a
3761
+ * single contract** at the direct-call surface. The proposal's N+1 Safety rule
3762
+ * fixes a `range` method's `inputArity` to `'single'` ("A `range` method is
3763
+ * necessarily `'single'`"; "feeding an array of keys into a range method … is an
3764
+ * N+1 fan-out and is rejected at build time, with no opt-in escape hatch"). #58's
3765
+ * {@link inputArityOf} derives exactly that, and #60's N+1 checker rejects any
3766
+ * contract that violates it before it reaches the SSoT. So a range method only
3767
+ * ever accepts one key, and this runtime **rejects** an array fed into a `range`
3768
+ * method ({@link executeQueryMethod}) rather than fanning out. `keyN : resultN×M`
3769
+ * arises only under cross-contract composition (a `point` parent referencing a
3770
+ * child whose per-key result is a connection) — that is #63's External Query
3771
+ * territory, explicitly out of scope for #62. We therefore implement the **three
3772
+ * reachable shapes** and guard the fourth with a clear error, rather than
3773
+ * silently fanning out a forbidden N+1.
3774
+ *
3775
+ * ## Reuse, not reinvention
3776
+ *
3777
+ * Execution delegates to the proven machinery:
3778
+ *
3779
+ * - `point` single → {@link executeQuery} (GetItem / unique Query).
3780
+ * - `point` array → {@link executeKeyedBatchGet} below, which reuses the
3781
+ * `batch-retry` primitives (chunk ≤100, dedup, UnprocessedKeys retry) and
3782
+ * builds the **keyed** map (BatchGet neither preserves order nor returns absent
3783
+ * keys, so missing keys are filled with explicit `null`).
3784
+ * - `range` single → {@link executeListInternal} for a `{ items, cursor }`
3785
+ * connection, with the cursor wrapped in a per-key envelope
3786
+ * ({@link encodePerKeyCursor}) so it carries the key it belongs to.
3787
+ *
3788
+ * The result-shape typing (single → bare, array → keyed) is expressed as call
3789
+ * overloads on {@link executeQueryMethod}.
3790
+ */
3791
+
3792
+ /**
3793
+ * The **structural** shape of a resolved query contract the runtime executes.
3794
+ * It depends only on the facts the executor reads — the `query` discriminant and
3795
+ * the per-method `resolution` and internal `op` — and is deliberately
3796
+ * **independent of the phantom Key / params / result types** carried by
3797
+ * {@link QueryModelContract}. Those phantom types are invariant /
3798
+ * contravariant on the full interface, so a concrete `QueryModelContract<{…}>`
3799
+ * is not assignable to `QueryModelContract<unknown, …>`; this minimal structural
3800
+ * type sidesteps that variance while accepting every real contract (a
3801
+ * {@link QueryModelContract} is structurally assignable to it). The runtime never
3802
+ * needs the method param / result types — it returns the dynamic
3803
+ * cardinality-matrix shapes ({@link ContractItem} / {@link Connection} /
3804
+ * {@link KeyedResult}).
3805
+ */
3806
+ interface ExecutableQueryContract {
3807
+ readonly kind: 'query';
3808
+ readonly methods: Readonly<Record<string, {
3809
+ readonly resolution: Resolution;
3810
+ /**
3811
+ * The method's decided input arity (issue #71): `'either'` for a
3812
+ * coalescible base-table `point` (a key array → one `BatchGetItem`),
3813
+ * `'single'` for a `range` (partition `Query`) **or** a unique-GSI `point`
3814
+ * (a per-key GSI `Query`, not coalescible). The executor honors it to
3815
+ * reject an array fed into any `'single'` method rather than fanning out.
3816
+ */
3817
+ readonly inputArity: InputArity;
3818
+ readonly op: ContractMethodOp;
3819
+ }>>;
3820
+ }
3821
+ /** A plain contract key (the access pattern / join key), e.g. `{ articleId }`. */
3822
+ type ContractKeyInput = Record<string, unknown>;
3823
+ /**
3824
+ * The literal {@link InputArity} the contract `C` carries for method `K`. A
3825
+ * {@link QueryMethodSpec} carries the method's literal arity in its `inputArity`
3826
+ * type when the body was tagged with `point` / `range` (`src/define/contract.ts`);
3827
+ * an untagged method keeps the wide `InputArity`. Falls back to `InputArity` for a
3828
+ * structurally-typed contract (e.g. the bare {@link ExecutableQueryContract}),
3829
+ * which keeps the call surface array-capable (the runtime backstop still rejects an
3830
+ * array fed into a `'single'` method).
3831
+ */
3832
+ type ArityOfMethod<C extends ExecutableQueryContract, K extends keyof C['methods']> = C['methods'][K] extends {
3833
+ readonly inputArity: infer A extends InputArity;
3834
+ } ? A : InputArity;
3835
+ /**
3836
+ * The **array** key-argument type accepted by `executeQueryMethod` for method `K`
3837
+ * of contract `C`. A method whose literal arity is `'single'` (a `range` read **or**
3838
+ * a unique-GSI `point` — both one request per key) accepts **no** array: the type
3839
+ * collapses to `never`, so passing an array is a `tsc` compile error at the
3840
+ * user-facing surface (issue #71, N+1 rule (a), the **primary** enforcement layer).
3841
+ * Any other arity (`'either'` / `'array'`, or the wide `InputArity` for an untagged
3842
+ * / structural contract) accepts an array of keys (it coalesces to one batched
3843
+ * call). This is the array-overload half of {@link ContractCallSignature}'s
3844
+ * narrowing, applied at the real execution surface.
3845
+ */
3846
+ type ArrayKeyArg<C extends ExecutableQueryContract, K extends keyof C['methods']> = ArityOfMethod<C, K> extends 'single' ? never : readonly ContractKeyInput[];
3847
+ /** A single hydrated result item (only the method's projected fields). */
3848
+ type ContractItem = Record<string, unknown>;
3849
+ /**
3850
+ * A pagination connection: a page of items plus an opaque `cursor` for the next
3851
+ * page (`null` when the page is the last). For a `range` contract method the
3852
+ * cursor is a **per-key envelope** ({@link encodePerKeyCursor}) that identifies
3853
+ * the key it paginates.
3854
+ */
3855
+ interface Connection<TItem = ContractItem> {
3856
+ readonly items: TItem[];
3857
+ readonly cursor: string | null;
3858
+ }
3859
+ /**
3860
+ * The keyed result of a batched contract read: input-key identity (the canonical
3861
+ * {@link serializeContractKey} string) → per-key result. Backed by a `Map` so
3862
+ * the association is explicit and order-independent; missing keys are present
3863
+ * with an explicit `null` value (`point`) rather than omitted.
3864
+ *
3865
+ * @typeParam TValue The per-key value (`Item | null` for `point`).
3866
+ */
3867
+ type KeyedResult<TValue> = Map<string, TValue>;
3868
+ /**
3869
+ * Retrieval options accepted by a contract query method (the serializable subset
3870
+ * from the proposal). All optional; a `point` method ignores the pagination
3871
+ * fields, a `range` method honors them.
3872
+ */
3873
+ interface ContractQueryParams {
3874
+ /** Strongly-consistent read (point reads on the base table only). */
3875
+ readonly consistentRead?: boolean;
3876
+ /** Page size for a `range` (`list`) method. */
3877
+ readonly limit?: number;
3878
+ /** Resume token — a per-key cursor envelope from a prior `range` page. */
3879
+ readonly after?: string;
3880
+ /** Sort direction for a `range` (`list`) method. */
3881
+ readonly order?: 'ASC' | 'DESC';
3882
+ }
3883
+ /**
3884
+ * Maximum number of in-flight per-key queries for a batched `range` fan-out.
3885
+ * Reuses the relation-traversal cap — the same rationale (bound in-flight
3886
+ * requests, ride out throttling via UnprocessedKeys retry beneath the cap). Note
3887
+ * that a single-contract `range` method is `inputArity: 'single'`, so the
3888
+ * fan-out path is only exercised by the (out-of-scope, #63) composition runtime;
3889
+ * the bound is defined here so the seam is ready and tested in isolation.
3890
+ */
3891
+ declare const CONTRACT_RANGE_FANOUT_CONCURRENCY = 16;
3892
+ /**
3893
+ * Keyed BatchGet: resolve `keys` against `modelClass` in one batched call and
3894
+ * return a `Map<keyId, Item | null>` with every input key present (misses →
3895
+ * `null`). Reuses the `batch-retry` primitives (chunk ≤100, dedup, UnprocessedKeys
3896
+ * retry) — never reinvented.
3897
+ *
3898
+ * Implementation notes:
3899
+ * - **Dedup**: the same key supplied twice is fetched once; both map entries
3900
+ * point at the same resolved item (or `null`).
3901
+ * - **Order / absence**: BatchGet returns matched items unordered and omits
3902
+ * misses, so each returned raw item is indexed by its `PK::SK` and looked up
3903
+ * per input key; an unmatched key gets `null`.
3904
+ * - **Projection**: items are fetched whole (DynamoDB BatchGet has no reliable
3905
+ * server-side projection for mixed keys here) and hydrated against the
3906
+ * method's `select`, so the per-key item carries exactly the projected fields.
3907
+ */
3908
+ declare function executeKeyedBatchGet(modelClass: Function, keys: readonly ContractKeyInput[], select: Record<string, unknown>): Promise<KeyedResult<ContractItem | null>>;
3909
+ /**
3910
+ * Execute a query-contract method for a **single** key.
3911
+ *
3912
+ * The result shape is determined by the method's derived `resolution`:
3913
+ * - `point` → `Item | null` (`key1 : result1`);
3914
+ * - `range` → `Connection` (`key1 : resultM`).
3915
+ *
3916
+ * A single key is accepted for **every** method regardless of arity.
3917
+ *
3918
+ * @param contract The resolved query contract (a {@link QueryModelContract}).
3919
+ * @param methodName The method to execute (a method name of `contract`).
3920
+ * @param key A single contract key.
3921
+ * @param params Retrieval options (pagination / consistency).
3922
+ */
3923
+ declare function executeQueryMethod<C extends ExecutableQueryContract, K extends keyof C['methods'] & string>(contract: C, methodName: K, key: ContractKeyInput, params?: ContractQueryParams): Promise<ContractItem | null | Connection>;
3924
+ /**
3925
+ * Execute a query-contract method for an **array** of keys (a single batched
3926
+ * call). Valid **only** for a method whose literal arity is not `'single'` — a
3927
+ * coalescible base-table `point` (`inputArity: 'either'`, a key array → one
3928
+ * `BatchGetItem`). A `'single'` method (a `range` read **or** a unique-GSI `point`)
3929
+ * makes the array key argument `never`, so passing an array is a **`tsc` compile
3930
+ * error** by construction (issue #71, N+1 rule (a) — the primary enforcement
3931
+ * layer; the build-time checker and runtime backstop remain). The result is a keyed
3932
+ * `Map<keyId, Item | null>` (`keyN : resultN`).
3933
+ *
3934
+ * @param contract The resolved query contract.
3935
+ * @param methodName The method to execute (a method name of `contract`).
3936
+ * @param keys An array of contract keys (rejected for a `'single'` method).
3937
+ * @param params Retrieval options.
3938
+ */
3939
+ declare function executeQueryMethod<C extends ExecutableQueryContract, K extends keyof C['methods'] & string>(contract: C, methodName: K, keys: ArrayKeyArg<C, K>, params?: ContractQueryParams): Promise<KeyedResult<ContractItem | null>>;
3940
+ /**
3941
+ * The runtime fan-out seam for a batched `range` (`keyN : resultN×M`). NOT
3942
+ * reachable for a single contract under #62 (a `range` method's `inputArity` is
3943
+ * `'single'`, so {@link executeQueryMethod} rejects an array). It is the
3944
+ * cross-contract composition runtime (#63) that drives a per-key range fan-out;
3945
+ * this function implements that fan-out — N bounded-parallel Queries grouped by
3946
+ * input key, each connection carrying its own per-key cursor — so the seam is
3947
+ * defined, reused (via {@link mapWithConcurrency}), and unit-tested in isolation
3948
+ * ahead of #63, rather than left as a stub. #62 does not call it on any contract
3949
+ * surface.
3950
+ *
3951
+ * @param op The `range` method op to run per key.
3952
+ * @param keys The parent keys to fan out over.
3953
+ * @param params Retrieval options applied to **every** key's connection.
3954
+ */
3955
+ declare function executeRangeFanout(op: ContractMethodOp, keys: readonly ContractKeyInput[], params?: ContractQueryParams): Promise<KeyedResult<Connection>>;
3956
+
3957
+ /**
3958
+ * Single-service contract **Runtime** — execution of CQRS command-contract
3959
+ * methods (issue #64, Epic #57; spec `docs/cqrs-contract.md`,
3960
+ * "Command Contract" + "Command Definition"). WRITES only; query reads are #62
3961
+ * ({@link executeQueryMethod}) and cross-contract composition is #63 — both out
3962
+ * of scope here.
3963
+ *
3964
+ * Given a resolved command contract (a {@link CommandModelContract} from #58,
3965
+ * carrying per-method `result` and the declared {@link CommandBatchMode}, plus a
3966
+ * resolved write {@link ContractMethodOp}) it executes one method for a **single
3967
+ * key** or an **array of keys** and applies the write(s) to DynamoDB:
3968
+ *
3969
+ * | Input | resolution |
3970
+ * | -------------- | ----------------------------------------------------------- |
3971
+ * | `key` (single) | one write op (`put` / `update` / `delete`) |
3972
+ * | `keys[]` (N) | a **batched** write per the method's declared `batch` mode |
3973
+ *
3974
+ * The batched form is **declared by the contract** (proposal "Consistency with
3975
+ * the existing write surface"):
3976
+ *
3977
+ * - `'transact'` — one `TransactWriteItems`: the per-key write applied
3978
+ * **atomically** (all-or-nothing) and **condition-capable** ({@link ConditionInput},
3979
+ * `{ notExists }` / equality). DynamoDB caps `TransactWriteItems` at **25** and
3980
+ * the call is atomic, so an array of **>25 keys cannot be split** without
3981
+ * breaking atomicity — this runtime **rejects** it with a clear error rather
3982
+ * than silently truncating or chunking ({@link MAX_TRANSACT_ITEMS}).
3983
+ * - `'batchWrite'` — a `BatchWriteItem`: the per-key writes applied
3984
+ * **non-atomically**, **without conditions** (DynamoDB's `BatchWriteItem` has no
3985
+ * `ConditionExpression`). An array of any size is permitted; it is chunked per
3986
+ * the 25-items-per-request limit with `UnprocessedItems` retry, reusing the
3987
+ * existing batch machinery ({@link executeBatchWrite}).
3988
+ * - **undefined** — the method declared no batched form: an array is rejected (it
3989
+ * resolves only a single key).
3990
+ *
3991
+ * ## Reuse, not reinvention
3992
+ *
3993
+ * The recorded write op carries declarative sentinels ({@link ContractParamRef} /
3994
+ * {@link ContractKeyFieldRef}) at its value positions; {@link renderRecord} binds
3995
+ * them to the concrete `(key, params)` at execution time, producing the very same
3996
+ * `item` / `changes` / `key` / `condition` objects a hand-written `Model.putItem` /
3997
+ * `Model.updateItem` / `Model.deleteItem` would pass. Execution then delegates to the
3998
+ * proven write machinery:
3999
+ *
4000
+ * - single → {@link executePut} / {@link executeUpdate} / {@link executeDelete};
4001
+ * - transact → one `TransactWriteItems` built from `buildPutInput` /
4002
+ * `buildUpdateInput` / `buildDeleteInput` (the same inputs the imperative
4003
+ * {@link TransactionContext} uses), with the ≤25 limit enforced;
4004
+ * - batchWrite → {@link executeBatchWrite} (chunk ≤25, `UnprocessedItems` retry).
4005
+ *
4006
+ * The TS and Python runtimes produce identical effects for the same SSoT.
4007
+ */
4008
+
4009
+ /**
4010
+ * The **structural** shape of a resolved command contract the runtime executes.
4011
+ * It depends only on the facts the executor reads — the `command` discriminant
4012
+ * and the per-method `op` / `batch` / `result` — and is deliberately
4013
+ * **independent of the phantom Key / params / result types** carried by
4014
+ * {@link CommandModelContract} (those are invariant on the full interface, so a
4015
+ * concrete `CommandModelContract<{…}>` is not assignable to
4016
+ * `CommandModelContract<unknown, …>`). Every real command contract is
4017
+ * structurally assignable to this minimal type.
4018
+ */
4019
+ interface ExecutableCommandContract {
4020
+ readonly kind: 'command';
4021
+ readonly methods: Readonly<Record<string, {
4022
+ readonly op: ContractMethodOp;
4023
+ readonly result: CommandResultKind;
4024
+ /**
4025
+ * The #101 first-class per-call execution mode (`'transaction'` |
4026
+ * `'parallel'`). Drives the key-array bulk form directly (`'transaction'`
4027
+ * → atomic `TransactWriteItems`; `'parallel'` → non-atomic per-key fan-out
4028
+ * with partial success, coalescing unconditioned put/delete into a
4029
+ * `BatchWriteItem`).
4030
+ */
4031
+ readonly mode?: CommandMode;
4032
+ /**
4033
+ * The composed write ops of a **multi-fragment** `mutation`-derived method
4034
+ * (issue #90). When present (N≥2), a **single-key** call executes ALL of
4035
+ * them as **one atomic `TransactWriteItems`** (not sequential writes), so a
4036
+ * condition failure on any fragment rolls back the whole transaction. Absent
4037
+ * for a single-fragment / #83 / hand-written #64 method (which executes its
4038
+ * one {@link op}). The read-back stays keyed on the primary op ({@link op}).
4039
+ */
4040
+ readonly ops?: readonly ContractMethodOp[];
4041
+ /**
4042
+ * The referential-integrity assertions derived from the mutation's
4043
+ * `requires` effects (issue #84). When present (non-empty), a
4044
+ * **single-key** call composes the base write(s) **and** one read-only
4045
+ * `ConditionCheck` (`attribute_exists`) per assertion into **one atomic
4046
+ * `TransactWriteItems`** — so a single-op write is **promoted** to a
4047
+ * transaction. If any referenced entity is absent, its assertion fails and
4048
+ * the WHOLE transaction rolls back (the entity write never persists). Absent
4049
+ * on a mutation with no `requires` / a #64 hand-written method.
4050
+ */
4051
+ readonly conditionChecks?: readonly DerivedConditionCheck[];
4052
+ /**
4053
+ * The adjacency edge writes derived from the mutation's `edges` effects
4054
+ * (issue #85). When present, a **single-key** call composes each edge's
4055
+ * adjacency `Put` / `Delete` item(s) into the **same atomic
4056
+ * `TransactWriteItems`** as the entity write (and any ConditionChecks /
4057
+ * derived updates), so the edge is created / removed / moved atomically with
4058
+ * the entity. Absent on a mutation with no `edges` / a #64 hand-written method.
4059
+ */
4060
+ readonly edgeWrites?: readonly DerivedEdgeWrite[];
4061
+ /**
4062
+ * The derived counter updates from the mutation's `derive` effects (issue
4063
+ * #85): each an atomic `ADD` (`UpdateItem`) on a target counter (e.g.
4064
+ * `User.postCount += 1`). When present, a **single-key** call composes each
4065
+ * `ADD` into the same atomic `TransactWriteItems` as the entity write, so the
4066
+ * counter moves atomically (and never clobbers a concurrent increment — an
4067
+ * `ADD` needs no prior read). Absent on a mutation with no `derive`.
4068
+ */
4069
+ readonly derivedUpdates?: readonly DerivedUpdate[];
4070
+ /**
4071
+ * The uniqueness guards derived from the mutation's `unique` effects (issue
4072
+ * #86): each a marker-row `Put` (`attribute_not_exists`) / `Delete` /
4073
+ * `Delete(old)+Put(new)` swap. When present, a **single-key** call composes
4074
+ * the guard item(s) into the same atomic `TransactWriteItems` as the entity
4075
+ * write, so a duplicate scoped value (the guard `Put`'s `attribute_not_exists`
4076
+ * failing) rolls the WHOLE transaction back — the entity is never created.
4077
+ * Absent on a mutation with no `unique` / a #64 hand-written method.
4078
+ */
4079
+ readonly uniqueGuards?: readonly DerivedUniqueGuard[];
4080
+ /**
4081
+ * The outbox events derived from the mutation's `emits` effects (issue #87):
4082
+ * each a transactional-outbox `Put`. When present, a **single-key** call
4083
+ * composes each event's `Put` into the same atomic `TransactWriteItems` as the
4084
+ * entity write, so the event RECORD is written ATOMICALLY with the state it
4085
+ * announces (they can never diverge). The row is drainable from real
4086
+ * DynamoDB Streams (captured at the table layer regardless of code path);
4087
+ * delivery / handlers are OUT of scope. Since #94 the derived-command
4088
+ * transaction also feeds every committed item (including this outbox `Put`)
4089
+ * into the in-process `ChangeCaptureRegistry` seam, so the dev/test
4090
+ * `src/cdc/` emulator now observes this path too — production Streams and the
4091
+ * emulator agree. Absent on a mutation with no `emits`.
4092
+ */
4093
+ readonly outboxEvents?: readonly DerivedOutboxEvent[];
4094
+ /**
4095
+ * The client-token idempotency guard derived from the mutation's `idempotency`
4096
+ * effect (issue #87): a marker-row `Put` (`attribute_not_exists`). When present,
4097
+ * a **single-key** call composes the guard `Put` into the same atomic
4098
+ * `TransactWriteItems` as the entity write, so a same-token re-execution fails
4099
+ * the guard and rolls the WHOLE transaction back — no effect is double-applied.
4100
+ * Absent on a mutation with no `idempotency` / a #64 hand-written method.
4101
+ */
4102
+ readonly idempotencyGuard?: DerivedIdempotencyGuard;
4103
+ /**
4104
+ * The return projection of a `mutation`-derived method (issue #83). When
4105
+ * present, a **single-key** call performs the write then issues a
4106
+ * **consistent read-back** (`GetItem` with `ConsistentRead`) of the
4107
+ * written entity's primary key and returns the projected item via the
4108
+ * existing read-projection machinery. Absent on a #64 hand-written method.
4109
+ */
4110
+ readonly returnSelection?: Readonly<Record<string, boolean>>;
4111
+ }>>;
4112
+ }
4113
+ /** The params a command method consumes (the body's `params` argument). */
4114
+ type ContractCommandParams = Record<string, unknown>;
4115
+ /**
4116
+ * The projected read-back item a `mutation`-derived command method returns, or
4117
+ * `null` when the written row was absent at read-back (e.g. a `remove`). A plain
4118
+ * record of the projected fields.
4119
+ */
4120
+ type CommandReturn = Record<string, unknown> | null;
4121
+ /**
4122
+ * #101 Phase 3 — the per-op outcome of a `mode: 'parallel'` key-array bulk write.
4123
+ * A non-atomic fan-out reports each key's result independently: `{ ok: true }` on
4124
+ * success, `{ ok: false, error }` on a per-op failure (a failed condition / a
4125
+ * `BatchWriteItem` item that stayed unprocessed after retry). The array index
4126
+ * matches the input key index, and a per-op failure NEVER aborts the others.
4127
+ */
4128
+ interface ParallelOpResult$1 {
4129
+ readonly ok: boolean;
4130
+ readonly error?: string;
4131
+ }
4132
+ /** The partial-success return of a `mode: 'parallel'` key-array bulk write (#101). */
4133
+ interface CommandParallelReturn {
4134
+ readonly results: readonly ParallelOpResult$1[];
4135
+ }
4136
+ /**
4137
+ * Execute a command-contract method for a **single** key — one write op
4138
+ * (`put` / `update` / `delete`).
4139
+ *
4140
+ * @param contract The resolved command contract (a {@link CommandModelContract}).
4141
+ * @param methodName The method to execute.
4142
+ * @param key A single contract key (the mutation target).
4143
+ * @param params The mutation params (the body's `params` argument).
4144
+ */
4145
+ declare function executeCommandMethod(contract: ExecutableCommandContract, methodName: string, key: ContractKeyInput, params?: ContractCommandParams): Promise<void | CommandReturn>;
4146
+ /**
4147
+ * Execute a command-contract method for an **array** of keys — a batched write
4148
+ * per the method's declared {@link CommandBatchMode}:
4149
+ *
4150
+ * - `'transact'` → one atomic `TransactWriteItems` (≤25, condition-capable); a
4151
+ * >25-key array is **rejected** (an atomic transaction cannot be split).
4152
+ * - `'batchWrite'` → a `BatchWriteItem` (no conditions; chunked ≤25 with
4153
+ * `UnprocessedItems` retry).
4154
+ *
4155
+ * A method that declared no batched form rejects an array with a clear error.
4156
+ *
4157
+ * @param contract The resolved command contract.
4158
+ * @param methodName The method to execute.
4159
+ * @param keys An array of contract keys.
4160
+ * @param params The mutation params shared across every key.
4161
+ */
4162
+ declare function executeCommandMethod(contract: ExecutableCommandContract, methodName: string, keys: readonly ContractKeyInput[], params?: ContractCommandParams): Promise<void | CommandParallelReturn>;
4163
+
4164
+ /**
4165
+ * The **unified envelope** in-process execution engine (issue #101).
4166
+ *
4167
+ * `DDBModel.query(map)` and `DDBModel.mutate(map, { mode })` are the in-process,
4168
+ * multi-route counterparts of the single primitives `Model.query` /
4169
+ * `Model.putItem|updateItem|deleteItem`, sharing one descriptor shape (alias → descriptor)
4170
+ * with the declarative `mutation(...)` DSL (#108) and the public contract layer.
4171
+ *
4172
+ * - **READ** — `{ alias: { query | list: Model, key, select, options? } }`. Each
4173
+ * route runs **independently and in parallel** (`Promise.all`): a `query` route
4174
+ * delegates to {@link executeQuery}, a `list` route to {@link executeList}. The
4175
+ * nested selection / relation resolution / optimisation under a route is the
4176
+ * existing read path, reused verbatim. Cross-route consistency is NOT guaranteed
4177
+ * (each route is its own read).
4178
+ *
4179
+ * - **WRITE** — `{ alias: { create | update | remove: Model, key, input?,
4180
+ * condition?, result? } }` plus a call-level `{ mode }`:
4181
+ * - `mode: 'transaction'` (default) — every operation composes into ONE atomic
4182
+ * `TransactWriteItems` (all-or-nothing); a `condition` failure (or any other)
4183
+ * rolls the whole set back and the call throws. Over the 25-item cap is an
4184
+ * error (an atomic transaction is never split).
4185
+ * - `mode: 'parallel'` — non-atomic. Operations are ordered by their
4186
+ * cross-fragment data dependencies (`$.alias.field`) into dependency levels;
4187
+ * within a level they run in parallel. Each operation (by alias) reports
4188
+ * success / failure **individually** in the returned result object (the call
4189
+ * does NOT throw on a per-op failure).
4190
+ *
4191
+ * Both write modes funnel through the **same execution core** the public
4192
+ * contract runtime (`executeCommandMethod`) uses — a descriptor is converted into
4193
+ * an anonymous `mutation()` plan, compiled by `compileMutationPlan`, and committed
4194
+ * via the shared command-runtime primitives — so atomicity / conformance is
4195
+ * identical to the public boundary by construction (no second code path).
4196
+ */
4197
+
4198
+ /** A read route's options (`query`: consistency/depth; `list`: paging/filter). */
4199
+ interface ReadRouteOptions {
4200
+ readonly consistentRead?: boolean;
4201
+ readonly maxDepth?: number;
4202
+ readonly limit?: number;
4203
+ readonly after?: string;
4204
+ readonly order?: 'ASC' | 'DESC';
4205
+ readonly filter?: Record<string, unknown>;
4206
+ }
4207
+ /** One read route: a `query` (point/unique) or a `list` (partition) descriptor. */
4208
+ interface ReadRouteDescriptor {
4209
+ readonly query?: ModelRef;
4210
+ readonly list?: ModelRef;
4211
+ readonly key: Record<string, unknown>;
4212
+ readonly select: Record<string, unknown>;
4213
+ readonly options?: ReadRouteOptions;
4214
+ }
4215
+ /** The read envelope: alias → {@link ReadRouteDescriptor}. */
4216
+ type ReadEnvelope = Record<string, ReadRouteDescriptor>;
4217
+ /** The result of a read route — a `query` item (or `null`) or a `list` connection. */
4218
+ type ReadRouteResult = Record<string, unknown> | null | {
4219
+ items: Record<string, unknown>[];
4220
+ cursor: string | null;
4221
+ };
4222
+ /** The write execution mode: atomic transaction (default) or non-atomic parallel. */
4223
+ type MutateMode = 'transaction' | 'parallel';
4224
+ /** A read-back projection for a write op (the EXISTING query select shape). */
4225
+ interface WriteResultProjection {
4226
+ readonly select?: Record<string, unknown>;
4227
+ readonly options?: {
4228
+ readonly consistentRead?: boolean;
4229
+ readonly maxDepth?: number;
4230
+ };
4231
+ }
4232
+ /** One write descriptor (alias entry) for the in-process `DDBModel.mutate`. */
4233
+ interface InProcessWriteDescriptor {
4234
+ readonly create?: ModelRef;
4235
+ readonly update?: ModelRef;
4236
+ readonly remove?: ModelRef;
4237
+ /**
4238
+ * The target's primary-key binding. A single key object applies the op once; an
4239
+ * **array** of key objects (#101 `key: K | K[]`) applies the SAME op to every key
4240
+ * (bulk). Under `transaction` mode a bulk descriptor composes one atomic
4241
+ * `TransactWriteItems` over all keys; under `parallel` mode it runs each key
4242
+ * non-atomically with per-key partial success.
4243
+ */
4244
+ readonly key: Record<string, unknown> | readonly Record<string, unknown>[];
4245
+ readonly input?: Record<string, unknown>;
4246
+ readonly condition?: Record<string, unknown>;
4247
+ readonly result?: WriteResultProjection;
4248
+ }
4249
+ /** The write envelope: alias → {@link InProcessWriteDescriptor}. */
4250
+ type WriteEnvelope = Record<string, InProcessWriteDescriptor>;
4251
+ /** Options for {@link executeWriteEnvelope}. */
4252
+ interface MutateOptions {
4253
+ readonly mode?: MutateMode;
4254
+ }
4255
+ /** A per-alias result in `parallel` mode: success carries the read-back, else error. */
4256
+ type ParallelOpResult = {
4257
+ readonly ok: true;
4258
+ readonly value: CommandReturn | undefined;
4259
+ } | {
4260
+ readonly ok: false;
4261
+ readonly error: Error;
4262
+ };
4263
+
4264
+ /**
4265
+ * Type-level inference for the unified envelope (issue #101). Maps a read / write
4266
+ * envelope's descriptor map to its alias-keyed result type, deriving each route's
4267
+ * result from its intent discriminator (`query`/`list` ↔ `create`/`update`/`remove`)
4268
+ * and `select` projection — reusing the `Model.query` `ProjectionOf` machinery.
4269
+ */
4270
+
4271
+ /** The entity type a {@link ModelStatic} value (or model class) carries. */
4272
+ type EntityOf<M> = M extends ModelStatic<infer T, infer _C> ? T : M extends abstract new (...args: never[]) => infer T ? T extends DDBModel ? T : never : unknown;
4273
+ /** The result type of one read route, derived from `query` / `list` + `select`. */
4274
+ type ReadRouteResultOf<R> = R extends {
4275
+ readonly query: infer M;
4276
+ readonly select: infer S;
4277
+ } ? QueryResultFor<EntityOf<M>, S> | null : R extends {
4278
+ readonly list: infer M;
4279
+ readonly select: infer S;
4280
+ } ? {
4281
+ items: QueryResultFor<EntityOf<M>, S>[];
4282
+ cursor: string | null;
4283
+ } : unknown;
4284
+ /** The projected item type for an entity `T` and a select `S` (relation-nest aware). */
4285
+ type QueryResultFor<T, S> = T extends DDBModel ? S extends Record<string, unknown> ? QueryResult<T, ProjectionOf<T, S>> : Record<string, unknown> : Record<string, unknown>;
4286
+ /** The alias-keyed result of {@link DDBModel.query}. */
4287
+ type QueryEnvelopeResult<E extends ReadEnvelope> = {
4288
+ readonly [K in keyof E]: ReadRouteResultOf<E[K]>;
4289
+ };
4290
+ /** The projected read-back type of one write op (or `undefined` when no `result`). */
4291
+ type WriteReadBackOf<W> = W extends {
4292
+ readonly result: {
4293
+ readonly select: infer S;
4294
+ };
4295
+ } ? W extends {
4296
+ readonly create: infer M;
4297
+ } ? QueryResultFor<EntityOf<M>, S> | null : W extends {
4298
+ readonly update: infer M;
4299
+ } ? QueryResultFor<EntityOf<M>, S> | null : W extends {
4300
+ readonly remove: infer M;
4301
+ } ? QueryResultFor<EntityOf<M>, S> | null : CommandReturn : undefined;
4302
+ /**
4303
+ * #101 `key: K | K[]` — is the descriptor a BULK write (its `key` is an array)?
4304
+ * A bulk alias's result is an ARRAY (aligned to the keys); a single key's is scalar.
4305
+ */
4306
+ type IsBulk<W> = W extends {
4307
+ readonly key: readonly unknown[];
4308
+ } ? true : false;
4309
+ /** Wrap a per-alias result in an array when the descriptor declared a `key` array. */
4310
+ type BulkWrap<W, R> = IsBulk<W> extends true ? readonly R[] : R;
4311
+ /** The alias-keyed result of {@link DDBModel.mutate} in `transaction` mode. */
4312
+ type MutateTransactionResult<E extends WriteEnvelope> = {
4313
+ readonly [K in keyof E as E[K] extends {
4314
+ readonly result: unknown;
4315
+ } ? K : never]: BulkWrap<E[K], WriteReadBackOf<E[K]>>;
4316
+ };
4317
+ /** A per-alias `parallel`-mode result carrying the typed read-back on success. */
4318
+ type ParallelResultOf<W> = {
4319
+ readonly ok: true;
4320
+ readonly value: WriteReadBackOf<W>;
4321
+ } | {
4322
+ readonly ok: false;
4323
+ readonly error: Error;
4324
+ };
4325
+ /** The alias-keyed result of {@link DDBModel.mutate} in `parallel` mode. */
4326
+ type MutateParallelResult<E extends WriteEnvelope> = {
4327
+ readonly [K in keyof E]: BulkWrap<E[K], ParallelResultOf<E[K]>>;
4328
+ };
4329
+
4330
+ declare abstract class DDBModel {
4331
+ /** @internal Type brand — prevents primitives from structurally matching DDBModel. */
4332
+ private readonly __brand;
4333
+ static setClient(client: DynamoDBClient): void;
4334
+ static setTableMapping(mapping: Record<string, string>): void;
4335
+ static transaction(fn: (tx: TransactionContext) => void | Promise<void>): Promise<void>;
4336
+ /**
4337
+ * In-process multi-route READ (issue #101 — the unified envelope). Each alias
4338
+ * route (`{ query | list: Model, key, select, options? }`) runs **independently
4339
+ * and in parallel**; the result is an object keyed by the input aliases. A
4340
+ * `query` route delegates to the single-item read path, a `list` route to the
4341
+ * partition read path. Cross-route consistency is NOT guaranteed.
4342
+ */
4343
+ static query<const E extends ReadEnvelope>(envelope: E): Promise<QueryEnvelopeResult<E>>;
4344
+ /**
4345
+ * In-process multi-operation WRITE (issue #101 — the unified envelope).
4346
+ *
4347
+ * - `mode: 'transaction'` (default) — every op composes into ONE atomic
4348
+ * `TransactWriteItems`; a `condition` failure rolls the whole set back and the
4349
+ * call throws. Result: `{ alias: readback }` for ops that declared a `result`.
4350
+ * - `mode: 'parallel'` — non-atomic; ops report success / failure individually
4351
+ * (the call does not throw on a per-op failure). Result:
4352
+ * `{ alias: { ok:true, value } | { ok:false, error } }`.
4353
+ *
4354
+ * Both modes funnel through the SAME write-execution core the public contract
4355
+ * runtime uses, so atomicity / derived-effect semantics are identical.
4356
+ */
4357
+ static mutate<const E extends WriteEnvelope>(envelope: E, options?: {
4358
+ mode?: 'transaction';
4359
+ }): Promise<MutateTransactionResult<E>>;
4360
+ static mutate<const E extends WriteEnvelope>(envelope: E, options: {
4361
+ mode: 'parallel';
4362
+ }): Promise<MutateParallelResult<E>>;
4363
+ static batchGet(requests: BatchGetRequest[]): Promise<BatchGetResult>;
4364
+ static batchWrite(requests: BatchWriteRequest[]): Promise<void>;
4365
+ static asModel<C extends new (...args: never[]) => DDBModel>(this: C): ModelStatic<InstanceType<C>, C>;
4366
+ }
4367
+
4368
+ /**
4369
+ * Internal brand identifying a {@link Column} reference at runtime, used by the
4370
+ * `cond` raw escape hatch to recognize and alias embedded column references.
4371
+ */
4372
+ declare const COLUMN_REF: unique symbol;
4373
+ /**
4374
+ * A type-safe reference to a model field (column).
4375
+ *
4376
+ * Each column carries its declared value type as a phantom parameter so the
4377
+ * `cond` escape hatch can type-check embedded values against the column. The
4378
+ * `__model` phantom brands the column to its owning model, so passing a column
4379
+ * from a different model into another model's `cond` is a type error.
4380
+ *
4381
+ * @typeParam V - The declared field value type.
4382
+ * @typeParam M - The owning model (brand only; never read at runtime).
4383
+ */
4384
+ interface Column<V = unknown, M = unknown> {
4385
+ readonly [COLUMN_REF]: true;
4386
+ /** The underlying attribute (property) name. */
4387
+ readonly name: string;
4388
+ /** @internal phantom — value type carried for `cond` value checking. */
4389
+ readonly __value?: V;
4390
+ /** @internal phantom — owning-model brand. */
4391
+ readonly __model?: M;
4392
+ }
4393
+ /** Runtime guard: is this value a {@link Column} reference? */
4394
+ declare function isColumn(value: unknown): value is Column;
4395
+ /**
4396
+ * The `Model.col` map: one {@link Column} per scalar field of the entity, each
4397
+ * typed with the field's declared value type and branded to the model.
4398
+ *
4399
+ * @typeParam T - The Entity type.
4400
+ */
4401
+ type ColumnMap<T> = {
4402
+ [K in keyof T as T[K] extends Function ? never : NonNullable<T[K]> extends DDBModel ? never : T[K] extends Array<infer E> ? [E] extends [DDBModel] ? never : K : K]: Column<T[K], T>;
4403
+ };
4404
+
4405
+ declare const KEY_MARKER: unique symbol;
4406
+ /**
4407
+ * Brand identifying a {@link KeySegment} produced by the {@link k} tag.
4408
+ */
4409
+ declare const KEY_SEGMENT: unique symbol;
4410
+ /**
4411
+ * A single structured key segment — the output of one `` k`...` `` tagged
4412
+ * template. Its literal parts and the column references interpolated between
4413
+ * them are preserved verbatim (the column refs are NOT stringified), so the
4414
+ * planner / spec generator can derive `{field}` templates directly from the
4415
+ * structure rather than recovering them by symbolic evaluation.
4416
+ *
4417
+ * Exactly one `` k`...` `` = one segment = one truncation boundary for a
4418
+ * multi-segment sort key.
4419
+ */
4420
+ interface KeySegment {
4421
+ readonly [KEY_SEGMENT]: true;
4422
+ /** The literal string parts; always one longer than {@link columns}. */
4423
+ readonly literals: readonly string[];
4424
+ /** The interpolated column references, in order. */
4425
+ readonly columns: readonly Column[];
4426
+ }
4427
+ /** Runtime guard: is this value a {@link KeySegment}? */
4428
+ declare function isKeySegment(value: unknown): value is KeySegment;
4429
+ /**
4430
+ * The interpolation slot accepted by {@link k}: a typed column reference
4431
+ * (`c.field`). Bare strings are NOT accepted — a key field must be named via a
4432
+ * {@link Column} reference so the structure stays type-safe and refactor-safe.
4433
+ */
4434
+ type KeySlot = Column;
4435
+ /**
4436
+ * Tagged-template builder for a single key segment.
4437
+ *
4438
+ * The literal parts and the interpolated column references are kept structurally
4439
+ * (never stringified), mirroring the {@link cond} escape hatch. A segment such
4440
+ * as `` k`USER#${c.userId}` `` becomes `{ literals: ['USER#', ''], columns: [userId] }`,
4441
+ * which the planner turns into the template `USER#{userId}`.
4442
+ *
4443
+ * @example
4444
+ * ```ts
4445
+ * key((c) => ({ pk: k`USER#${c.userId}`, sk: k`PROFILE` }));
4446
+ * key((c) => ({
4447
+ * pk: k`REGION#${c.region}`,
4448
+ * sk: [k`CITY#${c.city}`, k`STORE#${c.store}`],
4449
+ * }));
4450
+ * ```
4451
+ */
4452
+ declare function k(literals: TemplateStringsArray, ...columns: KeySlot[]): KeySegment;
4453
+ /** A single segment, or an ordered list of segments (multi-segment SK). */
4454
+ type SegmentSpec = KeySegment | KeySegment[];
4455
+ /** The `{ pk, sk }` structure returned by a key builder. */
4456
+ interface KeyStructure {
4457
+ readonly pk: SegmentSpec;
4458
+ readonly sk?: SegmentSpec;
4459
+ }
4460
+ /**
4461
+ * The canonical, structured representation of a key (PK or SK as a list of
4462
+ * segments). This — not an opaque mapping closure — is the single source of
4463
+ * truth for a model's key.
4464
+ */
4465
+ interface SegmentedKey {
4466
+ /** Partition-key segments (all required at resolve time). */
4467
+ readonly pkSegments: readonly KeySegment[];
4468
+ /** Sort-key segments, evaluated in order with truncation. */
4469
+ readonly skSegments: readonly KeySegment[];
4470
+ }
4471
+ interface KeyDefinitionMarker<T extends Record<string, unknown> = Record<string, unknown>> {
4472
+ readonly [KEY_MARKER]: true;
4473
+ readonly segmented: SegmentedKey;
4474
+ readonly inputFieldNames: string[];
4475
+ /** @internal Phantom field for type-level input type extraction. */
4476
+ readonly _phantom?: T;
4477
+ }
4478
+ /**
4479
+ * Define a model's primary key from a **structured segment** builder.
4480
+ *
4481
+ * The builder receives a typed column accessor `c` (one `Column` per field) and
4482
+ * returns `{ pk, sk }`, where each of `pk` / `sk` is a single `` k`...` ``
4483
+ * segment or an array of them. Column references are captured structurally, so
4484
+ * the key template is recovered directly from the structure — no symbolic
4485
+ * evaluation of an opaque closure.
4486
+ *
4487
+ * @typeParam T - The key input type (drives compile-time key checking for
4488
+ * `query` / `list`). Supplied by the caller's parameter annotation on `c`.
4489
+ */
4490
+ declare function key<T extends Record<string, unknown>>(builder: (c: {
4491
+ readonly [K in keyof T]-?: Column<T[K], T>;
4492
+ }) => KeyStructure): KeyDefinitionMarker<T>;
4493
+
4494
+ /**
4495
+ * CDC emulator types (issue #72). These types are **cdc-module-owned**; core
4496
+ * never references them. The {@link ChangeEvent} shape mirrors DynamoDB Streams
4497
+ * (`NEW_AND_OLD_IMAGES`) so a consumer written against the emulator runs
4498
+ * unchanged against real Streams in production (spec §4).
4499
+ */
4500
+ /** Stream view type. Fixed to `NEW_AND_OLD_IMAGES` — incremental aggregation
4501
+ * needs both images (spec §4). */
4502
+ type StreamViewType = 'NEW_AND_OLD_IMAGES';
4503
+ /** Stream event kind, matching DynamoDB Streams. */
4504
+ type ChangeEventName = 'INSERT' | 'MODIFY' | 'REMOVE';
4505
+ /**
4506
+ * A single change event. Within one shard, `sequenceNumber` is monotonically
4507
+ * increasing; across shards order is independent (spec §6).
4508
+ */
4509
+ interface ChangeEvent<T = Record<string, unknown>> {
4510
+ eventName: ChangeEventName;
4511
+ table: string;
4512
+ /** Resolved model name when known. */
4513
+ model?: string;
4514
+ keys: {
4515
+ pk: string;
4516
+ sk: string;
4517
+ };
4518
+ /** Present for MODIFY / REMOVE. */
4519
+ oldImage?: T;
4520
+ /** Present for INSERT / MODIFY. */
4521
+ newImage?: T;
4522
+ /** Deterministic-clock-derived creation time (ISO 8601). */
4523
+ approximateCreationTime: string;
4524
+ /** Monotonic within a shard (zero-padded for lexicographic ordering). */
4525
+ sequenceNumber: string;
4526
+ /** Partition = hash(pk). */
4527
+ shardId: string;
4528
+ }
4529
+ /** A delivered batch. Records of the same shard are in `sequenceNumber` order. */
4530
+ interface ChangeBatch<T = Record<string, unknown>> {
4531
+ records: ChangeEvent<T>[];
4532
+ }
4533
+ /**
4534
+ * The consumer's response to a batch (spec §6, `ReportBatchItemFailures`
4535
+ * equivalent). Returning the `sequenceNumber`s of records that failed lets the
4536
+ * emulator advance the checkpoint past the successful prefix and redeliver only
4537
+ * the failures.
4538
+ */
4539
+ interface BatchResult {
4540
+ /** `sequenceNumber`s of records the consumer failed to process. */
4541
+ batchItemFailures?: string[];
4542
+ }
4543
+ /** The consumer handler. */
4544
+ type ChangeHandler = (batch: ChangeBatch) => Promise<BatchResult | void>;
4545
+ /** Unsubscribe handle. */
4546
+ type Unsubscribe = () => void;
4547
+ /** Where a fresh subscriber begins reading. */
4548
+ type StartingPosition = 'TRIM_HORIZON' | 'LATEST';
4549
+ /** Clock mode. `virtual` makes time deterministic via `advanceClock`. */
4550
+ type ClockMode = 'real' | 'virtual';
4551
+ /** Delivery mode (spec §7). */
4552
+ type CdcMode = 'inline' | 'queued' | 'record' | 'replay';
4553
+ /** Deterministic fault-injection spec (spec §8). All probabilities are seeded. */
4554
+ interface FaultSpec {
4555
+ /** Probability [0,1] a delivered record is also re-delivered (duplicate). */
4556
+ duplicate?: number;
4557
+ /** Shuffle within-shard order on delivery (invariant-violation testing). */
4558
+ reorder?: boolean;
4559
+ /** Per-record virtual-time delivery delay range (ms). Requires queued mode. */
4560
+ delay?: {
4561
+ min: number;
4562
+ max: number;
4563
+ };
4564
+ /** Probability [0,1] a record is dropped on first delivery then redelivered. */
4565
+ dropThenRedeliver?: number;
4566
+ /** Probability [0,1] a record is reported as a partial-batch failure. */
4567
+ partialBatchFailure?: number;
4568
+ }
4569
+ /** A reference whose recompute should be forced to race a mark (spec §8, §7). */
4570
+ interface ConcurrentRecomputeRef {
4571
+ /** Opaque node ref the harness recomputes concurrently with marking. */
4572
+ ref: string;
4573
+ }
4574
+ /** A persisted event log (spec §10, record/replay). */
4575
+ interface EventLog {
4576
+ seed: number;
4577
+ events: ChangeEvent[];
4578
+ }
4579
+ /** Replay options (spec §10). */
4580
+ interface ReplayOptions {
4581
+ /** Deliver events in a deterministic shuffled order (any-order replay). */
4582
+ shuffle?: boolean;
4583
+ /** Probability [0,1] each event is delivered twice during replay. */
4584
+ duplicate?: number;
4585
+ }
4586
+ /** Constructor options (spec §11). */
4587
+ interface CdcEmulatorOptions {
4588
+ /** Stream-enabled model classes (opt-in; default none). */
4589
+ models?: Function[];
4590
+ /** Delivery mode. Default `inline`. */
4591
+ mode?: CdcMode;
4592
+ /** Clock mode. Default `virtual` for queued, `real` otherwise. */
4593
+ clock?: ClockMode;
4594
+ /** Records per delivered batch. Default 100. */
4595
+ batchSize?: number;
4596
+ /** Max redelivery attempts before a record goes to the DLQ. Default 3. */
4597
+ maxRetries?: number;
4598
+ /** Where a subscriber starts. Default `TRIM_HORIZON`. */
4599
+ startingPosition?: StartingPosition;
4600
+ /** Seed for fault injection / shard assignment determinism. Default 1. */
4601
+ seed?: number;
4602
+ /** Number of shards events are partitioned across. Default 8. */
4603
+ shardCount?: number;
4604
+ }
4605
+ type ShardId = string;
4606
+
4607
+ export { type AnyOperationDefinition as $, type StrictSelectSpec as A, type BatchGetExecInput as B, type ChangeEvent as C, type DynamoDBOperation as D, type Executor as E, type FaultSpec as F, type EntityInput as G, type UniqueQueryKeyOf as H, type EntityRef as I, type ConditionInput as J, type QueryMethodSpec as K, type CommandModelContract as L, type ModelStatic as M, type CommandMethodSpec as N, type OperationDefinition as O, type PutInput as P, type QueryModelContract as Q, type RawCondition as R, type SelectableOf as S, type TransactWriteExecItem as T, type UpdateInput as U, type ContractSpec as V, type WriteExecOptions as W, type QuerySpec as X, type CommandSpec as Y, type ContextSpec as Z, type OperationsDocument as _, type ExecutorResult as a, type InProcessWriteDescriptor as a$, type BridgeBundle as a0, type ConditionSpec as a1, type BatchDeleteRequest as a2, type BatchGetRequest as a3, BatchGetResult as a4, type BatchPutRequest as a5, type BatchResult as a6, type BatchWriteRequest as a7, CONTRACT_RANGE_FANOUT_CONCURRENCY as a8, type CdcMode as a9, type ContractKeyInput as aA, type ContractKeyRef as aB, type ContractKeySpec as aC, type ContractKind as aD, type ContractMethodOp as aE, type ContractParamRef as aF, type ContractQueryParams as aG, type ContractResolution as aH, type DeleteOptions as aI, type DeriveEffect as aJ, type DerivedEdgeWrite as aK, type DerivedUpdate as aL, type DescriptorBinding as aM, ENTITY_WRITES_MARKER as aN, type EdgeEffect as aO, type EffectPath as aP, type EmitEffect as aQ, type EntityWritesDefinition as aR, type EntityWritesShape as aS, type ExecutableCommandContract as aT, type ExecutableQueryContract as aU, type FilterInput as aV, type FilterSpec as aW, type FragmentInput as aX, type GsiDefinitionMarker as aY, type GsiOptions as aZ, type IdempotencyEffect as a_, type ChangeBatch as aa, type ChangeEventName as ab, type ClockMode as ac, type Column as ad, type ColumnMap as ae, type CommandContractMethodSpec as af, type CommandInputShape as ag, type CommandMethod as ah, type CommandPlan as ai, type CommandResolutionTarget as aj, type CommandResultKind as ak, type CommandSelectShape as al, type CompiledFragment as am, type ComposeSpec as an, type CondSlot as ao, type ConditionCheckInput as ap, type Connection as aq, type ContractCallSignature as ar, type ContractCardinality as as, type ContractCommandParams as at, type ContractCommandResult as au, type ContractComposeNode as av, type ContractFromRef as aw, type ContractInputArity as ax, type ContractItem as ay, type ContractKeyFieldRef as az, type WriteResult as b, type UniqueEffect as b$, type InputArity as b0, type KeyDefinitionMarker as b1, type KeySegment as b2, type KeySlot as b3, type KeyStructure as b4, type KeyedResult as b5, LIFECYCLE_CONTRACT_MARKER as b6, type LifecycleContract as b7, type LifecycleEffects as b8, type LiteralParam as b9, type PutOptions as bA, type QueryContractMethodSpec as bB, type QueryEnvelopeResult as bC, type QueryKeyOf as bD, type QueryMethod as bE, type QueryResult as bF, type RangeConditionSpec as bG, type ReadEnvelope as bH, type ReadOperationType as bI, type ReadRouteDescriptor as bJ, type ReadRouteOptions as bK, type ReadRouteResult as bL, type RecordedCompose as bM, type RelationBuilder as bN, type RelationSelect as bO, type RelationSpec as bP, type RequiresEffect as bQ, type Resolution as bR, SPEC_VERSION as bS, type SegmentSpec as bT, type SelectBuilder as bU, type SelectOf as bV, type StartingPosition as bW, type StreamViewType as bX, type StringParam as bY, TransactionContext as bZ, type TransactionItemType as b_, type ManifestEntity as ba, type ManifestField as bb, type ManifestFieldType as bc, type ManifestGsi as bd, type ManifestKey as be, type ManifestRelation as bf, type ManifestTable as bg, type ModelRef as bh, type MutateMode as bi, type MutateOptions as bj, type MutateParallelResult as bk, type MutateTransactionResult as bl, type MutationBody as bm, type MutationDescriptorMap as bn, type MutationFragment as bo, type MutationInputProxy as bp, type MutationInputRef as bq, type MutationIntent as br, type NumberParam as bs, type OperationKind as bt, type OperationSpec as bu, type ParallelOpResult as bv, type ParamKind as bw, type ParamSpec as bx, type ParamStructure as by, type PlannedCommandMethod as bz, type DeleteInput as c, type Updatable as c0, type UpdateOptions as c1, type WhenSpec as c2, type WriteDescriptor as c3, type WriteEnvelope as c4, type WriteLifecyclePhase as c5, type WriteOperationType as c6, type WriteRecorder as c7, type WriteResultProjection as c8, attachModelClass as c9, isContractComposeNode as cA, isContractFromRef as cB, isContractKeyFieldRef as cC, isContractKeyRef as cD, isContractParamRef as cE, isEntityWritesDefinition as cF, isKeySegment as cG, isLifecycleContract as cH, isMutationFragment as cI, isMutationInputRef as cJ, isParam as cK, isPlannedCommandMethod as cL, isQueryModelContract as cM, k as cN, key as cO, lifecyclePhaseForIntent as cP, mintContractKeyFieldRef as cQ, mintContractParamRef as cR, mutation as cS, param as cT, publicCommandModel as cU, publicQueryModel as cV, query as cW, resolveLifecycle as cX, wholeKeysSentinel as cY, buildDeleteInput as ca, buildPutInput as cb, buildUpdateInput as cc, compileFragment as cd, compileMutationPlan as ce, compileSingleFragmentPlan as cf, cond as cg, contractOfMethodSpec as ch, definePlan as ci, entityWrites as cj, executeBatchGet as ck, executeBatchWrite as cl, executeCommandMethod as cm, executeDelete as cn, executeKeyedBatchGet as co, executePut as cp, executeQueryMethod as cq, executeRangeFanout as cr, executeTransaction as cs, executeUpdate as ct, from as cu, getEntityWrites as cv, gsi as cw, isColumn as cx, isCommandModelContract as cy, isCommandPlan as cz, type BatchWriteExecItem as d, DDBModel as e, type PrimaryKeyOf as f, type ExecutionPlanSpec as g, type SegmentedKey as h, type SelectBuilderSpec as i, type TransactionSpec as j, type Manifest as k, type ExecutionPlan as l, type ResolvedKey as m, type CdcEmulatorOptions as n, type ChangeHandler as o, type Unsubscribe as p, type ConcurrentRecomputeRef as q, type EventLog as r, type ReplayOptions as s, type ShardId as t, type TransactionItemSpec as u, type Param as v, type ParamDescriptor as w, type DefinitionMap as x, type WriteDefinitionOptions as y, type PartialQueryKeyOf as z };