graphddb 0.7.9 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +6 -6
  2. package/dist/cdc/index.d.ts +389 -4
  3. package/dist/cdc/index.js +4 -3
  4. package/dist/{chunk-ZPNRLOKA.js → chunk-GS4C5VGO.js} +4 -6
  5. package/dist/chunk-HNY2EJPV.js +1184 -0
  6. package/dist/{chunk-NYM7K2ST.js → chunk-I4LEJ4TF.js} +3812 -6724
  7. package/dist/{chunk-PFFPLD4B.js → chunk-L2NEDS7U.js} +725 -2112
  8. package/dist/chunk-L4QRCHRQ.js +278 -0
  9. package/dist/chunk-LGHSZIEE.js +187 -0
  10. package/dist/chunk-N4NWYNGZ.js +1987 -0
  11. package/dist/{chunk-PDUVTYC5.js → chunk-XTWXMOHD.js} +0 -1
  12. package/dist/cli.js +63 -252
  13. package/dist/index.d.ts +23 -1548
  14. package/dist/index.js +100 -1778
  15. package/dist/internal/index.d.ts +84 -0
  16. package/dist/internal/index.js +701 -0
  17. package/dist/{maintenance-view-adapter-BATUh_I8.d.ts → key-DR7_lpyk.d.ts} +538 -2975
  18. package/dist/linter/index.d.ts +39 -6
  19. package/dist/linter/index.js +22 -4
  20. package/dist/{registry-CXhP4TaE.d.ts → linter-C-vypgut.d.ts} +22 -22
  21. package/dist/prepared-artifact-BpPgkXEo.d.ts +281 -0
  22. package/dist/spec/index.d.ts +506 -4
  23. package/dist/spec/index.js +36 -17
  24. package/dist/testing/index.d.ts +2 -2
  25. package/dist/testing/index.js +4 -3
  26. package/dist/transform/index.d.ts +460 -1
  27. package/dist/transform/index.js +2085 -2
  28. package/dist/types-2PMXEn5x.d.ts +1205 -0
  29. package/dist/types-BXLzIcQD.d.ts +450 -0
  30. package/docs/cdc-projection.md +5 -5
  31. package/docs/class-hydration.md +1 -1
  32. package/docs/cqrs-contract.md +28 -20
  33. package/docs/design-patterns.md +5 -5
  34. package/docs/docs-generation.md +6 -6
  35. package/docs/middleware.md +15 -15
  36. package/docs/mutation-command-derivation.md +52 -42
  37. package/docs/prepared-statements.md +14 -14
  38. package/docs/python-bridge.md +113 -66
  39. package/docs/spec.md +153 -124
  40. package/docs/testing.md +9 -8
  41. package/package.json +20 -5
  42. package/dist/chunk-MMVHOUM4.js +0 -24
  43. package/dist/from-change-DanwjE5b.d.ts +0 -327
  44. package/dist/index-CtPJSMrc.d.ts +0 -934
  45. package/dist/relation-depth-Dg3yhl7S.d.ts +0 -36
@@ -1,4 +1,4 @@
1
- import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
1
+ import { a1 as Param, E as ExpressionSpec, a2 as ParamKind, T as TransactionSpec, X as TransactionItemSpec } from './types-2PMXEn5x.js';
2
2
 
3
3
  /**
4
4
  * CDC emulator types (issue #72). These types are **cdc-module-owned**; core
@@ -113,132 +113,6 @@ interface CdcEmulatorOptions {
113
113
  }
114
114
  type ShardId = string;
115
115
 
116
- /**
117
- * App-level throttle / transient-error retry for single-op sends (issue #111).
118
- *
119
- * GraphDDB's single-item ops (GetItem / Query / Put / Update / Delete) and the
120
- * read fan-out (relation resolution) used to throw on throttling
121
- * (`ProvisionedThroughputExceededException` / `ThrottlingException`) and rely
122
- * solely on the AWS SDK v3 default retry, so the LIBRARY itself guaranteed no
123
- * throttle-resilience. The batch ops already retry partial completion
124
- * (`UnprocessedKeys` / `UnprocessedItems`, see `src/operations/batch-retry.ts`);
125
- * this module is the SEPARATE, library-owned ERROR retry that wraps every
126
- * `docClient.send()` for the single ops via {@link RetryingExecutor}.
127
- *
128
- * The two retry layers are DISTINCT and must not be merged:
129
- * - (A) partial-batch retry (`batch-retry.ts`) — DynamoDB accepted the request
130
- * but could not process every key/item; the request itself did NOT error.
131
- * - (B) error retry (this module) — `send()` REJECTED with a throttle / transient
132
- * error and is retried whole.
133
- * They share {@link computeBackoffDelay} so backoff is consistent across both,
134
- * but they are different mechanisms.
135
- *
136
- * The default policy is ENABLED out of the box (see {@link DEFAULT_RETRY_POLICY}):
137
- * even with zero configuration a sensible exponential-backoff-with-jitter,
138
- * bounded-attempts retry applies. Users tune it globally via
139
- * `DDBModel.setRetryPolicy(...)` or per-call via the `retry?:` option on the
140
- * operation option bags; `retry: false` disables retry for that one call.
141
- *
142
- * NOTE on the AWS SDK's OWN retry: now that the library owns retry, a
143
- * `DynamoDBClient` left at the SDK default `maxAttempts: 3` retries
144
- * MULTIPLICATIVELY underneath this layer. Users should construct their client
145
- * with `maxAttempts: 1` so the library is the single source of truth for retry
146
- * (documented at `DDBModel.setClient` and in the README). The library does NOT
147
- * silently mutate a user-provided client.
148
- */
149
- /** The single-op kind a retry is wrapping — surfaced to {@link RetryPolicy.onRetry}. */
150
- type RetryOperationKind = 'get' | 'query' | 'batchGet' | 'put' | 'update' | 'delete' | 'batchWrite' | 'transactWrite';
151
- /** The context handed to {@link RetryPolicy.onRetry} before each backoff sleep. */
152
- interface RetryInfo {
153
- /** 1-based attempt number that just FAILED and is about to be retried. */
154
- readonly attempt: number;
155
- /** The throttle / transient error that triggered this retry. */
156
- readonly error: unknown;
157
- /** The backoff delay (ms) about to be slept before the next attempt. */
158
- readonly delayMs: number;
159
- /** Which single-op kind is being retried. */
160
- readonly operation: RetryOperationKind;
161
- }
162
- /**
163
- * A retry policy for the single-op error-retry layer (issue #111). Every field is
164
- * optional and resolved INDEPENDENTLY, in layers (see {@link resolveRetryPolicy}):
165
- * a per-call override field wins, else the globally-configured policy's field
166
- * (from {@link import('../model/DDBModel.js').DDBModel.setRetryPolicy}), else
167
- * {@link DEFAULT_RETRY_POLICY}. So a partial per-call override (e.g.
168
- * `{ maxAttempts: 3 }`) overrides ONLY that field and INHERITS the rest from the
169
- * global policy — a global `onRetry` / `jitter` is NOT dropped. A policy value of
170
- * `false` on an option bag disables retry entirely for that call.
171
- */
172
- interface RetryPolicy {
173
- /**
174
- * Maximum number of attempts (the FIRST try counts as attempt 1). `1` disables
175
- * retry (a single try, no backoff). Default {@link DEFAULT_MAX_ATTEMPTS}.
176
- */
177
- readonly maxAttempts?: number;
178
- /**
179
- * Compute the base backoff delay (ms) for a 1-based retry attempt, BEFORE jitter.
180
- * Defaults to {@link computeBackoffDelay} (`min(1000, 50*2^(attempt-1))`), the
181
- * same ramp the batch partial-retry layer uses, so backoff is consistent across
182
- * the two layers.
183
- */
184
- readonly computeDelay?: (attempt: number) => number;
185
- /**
186
- * Jitter strategy applied to the base delay:
187
- * - `'full'` (default) — uniform random in `[0, base]` (AWS "full jitter"),
188
- * the recommended strategy to de-correlate retries across clients.
189
- * - `'none'` — use the base delay verbatim (deterministic; handy in tests).
190
- */
191
- readonly jitter?: 'full' | 'none';
192
- /**
193
- * Classify whether an error is retryable. Defaults to {@link isRetryableError}
194
- * (throttle + 5xx / transient). A custom classifier fully replaces the default —
195
- * it should still avoid retrying validation / conditional-check failures.
196
- */
197
- readonly isRetryable?: (error: unknown) => boolean;
198
- /**
199
- * Observability hook (issue #111 §8): invoked BEFORE each backoff sleep with the
200
- * attempt number, the triggering error, the computed (post-jitter) delay, and the
201
- * operation kind. Never invoked on the terminal success or the terminal failure.
202
- */
203
- readonly onRetry?: (info: RetryInfo) => void;
204
- }
205
- /** Default cap on attempts: the first try plus up to 9 retries (mirrors the batch cap). */
206
- declare const DEFAULT_MAX_ATTEMPTS = 10;
207
- /**
208
- * The default, always-on retry policy (issue #111 §3). Exponential backoff (the
209
- * shared {@link computeBackoffDelay} ramp) + full jitter, capped at
210
- * {@link DEFAULT_MAX_ATTEMPTS} attempts, retrying throttle / 5xx / transient
211
- * errors only (never validation / conditional-check failures).
212
- */
213
- declare const DEFAULT_RETRY_POLICY: Required<Pick<RetryPolicy, 'maxAttempts' | 'computeDelay' | 'jitter' | 'isRetryable'>>;
214
- /**
215
- * Default error classifier (issue #111 §5). Retryable when the error is a known
216
- * throttle / transient fault, an AWS SDK error flagged `$retryable`, or a 5xx /
217
- * 429 status. NEVER retryable for validation / conditional-check / not-found /
218
- * access-denied — those are deterministic and would just burn the attempt budget.
219
- *
220
- * NOTE: a {@link https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactWriteItems.html
221
- * TransactionCanceledException} is handled SEPARATELY by
222
- * {@link isRetryableTransactionCancellation}; this function treats it as
223
- * non-retryable so the transact path can inspect its `CancellationReasons` first.
224
- */
225
- declare function isRetryableError(error: unknown): boolean;
226
- /**
227
- * Classify a `TransactionCanceledException` (issue #111 §6). Inspects the
228
- * `CancellationReasons`:
229
- * - ANY `ConditionalCheckFailed` reason ⇒ NOT retryable (a deterministic
230
- * condition failure; retrying would just fail again) — return `false`.
231
- * - Otherwise, when EVERY non-`None` reason is a throttle / conflict code
232
- * (`ThrottlingError` / `ProvisionedThroughputExceeded` / `TransactionConflict`)
233
- * ⇒ retryable — return `true`.
234
- * - A cancellation with no inspectable reasons, or any other reason code, is
235
- * treated conservatively as NOT retryable.
236
- *
237
- * Only meaningful for an error whose name is `TransactionCanceledException`; the
238
- * caller checks that first.
239
- */
240
- declare function isRetryableTransactionCancellation(error: unknown): boolean;
241
-
242
116
  interface RangeCondition {
243
117
  operator: 'begins_with';
244
118
  key: string;
@@ -279,149 +153,6 @@ type DynamoDBOperation = GetItemOperation | QueryOperation | BatchGetItemOperati
279
153
  interface ExecutionPlan {
280
154
  operations: DynamoDBOperation[];
281
155
  }
282
- type ResolvedKey = {
283
- type: 'pk';
284
- partial: boolean;
285
- inputFieldNames: string[];
286
- } | {
287
- type: 'gsi';
288
- indexName: string;
289
- unique: boolean;
290
- partial: boolean;
291
- inputFieldNames: string[];
292
- };
293
-
294
- /**
295
- * Parameter placeholders for the parameterized query / command definition DSL
296
- * (issue #41, Python-bridge Phase 0a).
297
- *
298
- * A `Param<T>` is a **branded placeholder** standing in for a value that is not
299
- * known at definition time but is supplied later (at execution, e.g. from
300
- * Python). It carries:
301
- *
302
- * - the **TypeScript value type** `T` it represents (`string`, `number`, or a
303
- * string-literal union), preserved through the IR and the inferred return
304
- * types of `defineQueries` / `defineCommands`; and
305
- * - a **runtime descriptor** (`kind` + optional `literals`) that the static
306
- * planner (#42) and the code generator read to emit `operations.json`.
307
- *
308
- * Placeholders are created with `param.string()`, `param.number()`, and
309
- * `param.literal(...)`. They are *only* legal at value positions inside a
310
- * parameterized key / changes structure passed to the `define*` entry points —
311
- * never to the live `Model.query` / `Model.putItem` runtime API, whose types remain
312
- * uncontaminated by params.
313
- */
314
- /** Brand tag preventing a plain object from structurally matching a {@link Param}. */
315
- declare const PARAM_BRAND: unique symbol;
316
- /** Runtime discriminant for a parameter placeholder. */
317
- type ParamKind = 'string' | 'number' | 'literal' | 'array';
318
- /**
319
- * A branded parameter placeholder representing a value of TypeScript type `T`
320
- * that is bound at execution time rather than definition time.
321
- *
322
- * The brand (`[PARAM_BRAND]: T`) keeps `Param<string>` and `Param<number>`
323
- * distinct and prevents a bare value from being mistaken for a placeholder.
324
- *
325
- * @typeParam T - The value type this placeholder stands in for.
326
- */
327
- interface Param<out T> {
328
- /** @internal Type brand carrying the represented value type. */
329
- readonly [PARAM_BRAND]: T;
330
- /** Runtime discriminant: `'string'` | `'number'` | `'literal'` | `'array'`. */
331
- readonly kind: ParamKind;
332
- /**
333
- * For `param.literal(...)`, the allowed literal values (preserved at runtime
334
- * for the generator). `undefined` for `string` / `number`.
335
- */
336
- readonly literals?: readonly T[];
337
- /**
338
- * For `param.array({...})`, the descriptor of each element's fields. Lets the
339
- * transaction planner (#46) type `forEach` element references and emit the
340
- * `{item.<field>}` templates. `undefined` for scalar params.
341
- */
342
- readonly element?: Readonly<Record<string, Param<unknown>>>;
343
- /**
344
- * Optional human-readable description of the parameter (issue #154), supplied
345
- * via `param.string({ description })` etc. Pure documentation metadata — it does
346
- * NOT affect the runtime descriptor (`kind` / `literals`) the planner reads for
347
- * execution. When present it is propagated to the param's {@link
348
- * import('./ir.js').ParamDescriptor} and then to the serialized
349
- * {@link import('../spec/types.js').ParamSpec} in `operations.json`. Absent →
350
- * unchanged output (backward compatible).
351
- */
352
- readonly description?: string;
353
- }
354
- /** A `Param` whose represented value type is `string`. */
355
- type StringParam = Param<string>;
356
- /** A `Param` whose represented value type is `number`. */
357
- type NumberParam = Param<number>;
358
- /** A `Param` whose represented value type is the literal union `L`. */
359
- type LiteralParam<L extends string | number> = Param<L>;
360
- /** The element-field descriptor shape accepted by {@link param.array}. */
361
- type ArrayElementShape = Record<string, Param<unknown>>;
362
- /**
363
- * The TypeScript element type implied by an {@link ArrayElementShape} `E`: each
364
- * field carries the value type its placeholder represents.
365
- */
366
- type ElementOf<E extends ArrayElementShape> = {
367
- [K in keyof E]: E[K] extends Param<infer V> ? V : never;
368
- };
369
- /** A `Param` standing in for an **array** whose elements have shape `E`. */
370
- type ArrayParam<E extends ArrayElementShape> = Param<ElementOf<E>[]> & {
371
- readonly element: E;
372
- };
373
- /** Allowed scalar value types a placeholder may stand in for. */
374
- type ParamValue = string | number;
375
- /**
376
- * Options accepted by the placeholder factories (issue #154). Currently carries
377
- * only the optional human-readable {@link Param.description} — pure documentation
378
- * metadata that does not affect the runtime descriptor. The object form is
379
- * additive: `param.string()` (no options) is unchanged.
380
- */
381
- interface ParamOptions {
382
- /** Optional human-readable description, propagated to `operations.json`. */
383
- readonly description?: string;
384
- }
385
- /**
386
- * Placeholder factory. Each call returns a branded {@link Param} carrying both
387
- * the TypeScript value type and a runtime descriptor. Each scalar factory accepts
388
- * an optional {@link ParamOptions} (issue #154) carrying a `description` — pure
389
- * documentation propagated to `operations.json`; the no-argument call is unchanged.
390
- */
391
- declare const param: {
392
- /** A placeholder for a `string` value. */
393
- readonly string: (options?: ParamOptions) => StringParam;
394
- /** A placeholder for a `number` value. */
395
- readonly number: (options?: ParamOptions) => NumberParam;
396
- /**
397
- * A placeholder constrained to one of the given literal values. The value
398
- * type of the resulting {@link Param} is the **union of the literals**, so it
399
- * is preserved precisely in the IR and the inferred definition types. A final
400
- * plain-object argument is read as {@link ParamOptions} (a `description`),
401
- * distinguished from the `string` / `number` literal values.
402
- *
403
- * @example
404
- * ```ts
405
- * param.literal('active', 'disabled'); // Param<'active' | 'disabled'>
406
- * param.literal('active', 'disabled', { description: 'Account state.' });
407
- * ```
408
- */
409
- readonly literal: <const L extends readonly [ParamValue, ...ParamValue[]]>(...valuesAndOptions: [...L] | [...L, ParamOptions]) => LiteralParam<L[number]>;
410
- /**
411
- * A placeholder for an **array** parameter whose elements have the given field
412
- * shape. Used by `defineTransaction`'s `tx.forEach(p.<arrayParam>, …)` to bind
413
- * each element's fields to `{item.<field>}` templates.
414
- *
415
- * @example
416
- * ```ts
417
- * param.array({ userId: param.string(), role: param.string() });
418
- * // Param<{ userId: string; role: string }[]>
419
- * ```
420
- */
421
- readonly array: <const E extends ArrayElementShape>(element: E, options?: ParamOptions) => ArrayParam<E>;
422
- };
423
- /** Runtime type guard: is `value` a parameter placeholder? */
424
- declare function isParam(value: unknown): value is Param<unknown>;
425
156
 
426
157
  /**
427
158
  * Internal brand identifying a {@link RawCondition} produced by {@link cond}.
@@ -598,27 +329,6 @@ interface WriteOptions<T> {
598
329
  readonly condition?: WriteCondition<T>;
599
330
  }
600
331
 
601
- /**
602
- * Internal brand symbol identifying a compiled select builder at runtime.
603
- * Consumers should not depend on this; it is used by the execution layer to
604
- * distinguish a builder instance from a plain select object.
605
- */
606
- declare const SELECT_BUILDER: unique symbol;
607
- /**
608
- * The normalized spec a select builder resolves to. This is the shape the
609
- * planner / traversal layer consumes. `filter` is the declarative server-side
610
- * DynamoDB FilterExpression input.
611
- *
612
- * @internal
613
- */
614
- interface SelectBuilderSpec {
615
- readonly [SELECT_BUILDER]: true;
616
- select: Record<string, unknown>;
617
- filter?: Record<string, unknown>;
618
- limit?: number;
619
- after?: string;
620
- order?: 'ASC' | 'DESC';
621
- }
622
332
  /**
623
333
  * Top-level select builder produced by `Model.project(...)`.
624
334
  *
@@ -958,65 +668,6 @@ type AtLeastOneKey<T> = {
958
668
  */
959
669
  type PartialQueryKeyOf<C> = QueryKeyOf<C> extends infer U ? U extends Record<string, unknown> ? AtLeastOneKey<U> : never : never;
960
670
 
961
- interface PutOptions<T = unknown> {
962
- condition?: WriteCondition<T>;
963
- /**
964
- * Per-call throttle / transient-error retry override (issue #111). A
965
- * {@link RetryPolicy} replaces the global policy for this call; `false` disables
966
- * retry for this call. Omit to use the configured (or built-in default) policy.
967
- */
968
- retry?: RetryOverride;
969
- /**
970
- * Host-injected per-call write context (issue #50 / #139) — exposed to every
971
- * write hook (W1–W5) as `ctx.context`. A host-language value, NEVER serialized
972
- * into the SSoT / `operations.json` (the bridge #48 is unaffected). Absent ⇒ `{}`.
973
- */
974
- context?: RequestContext;
975
- }
976
- interface UpdateOptions<T = unknown> {
977
- condition?: WriteCondition<T>;
978
- /**
979
- * Per-call throttle / transient-error retry override (issue #111). A
980
- * {@link RetryPolicy} replaces the global policy for this call; `false` disables
981
- * retry for this call.
982
- */
983
- retry?: RetryOverride;
984
- /**
985
- * How to re-derive a GSI key whose composing fields are changed but **not all**
986
- * available from `{ ...key, ...changes }` (issue #115).
987
- *
988
- * - **unset (default)** — refuse with an explicit error rather than let the
989
- * index silently rot. The error names the index, the changed field, and the
990
- * missing field(s). The happy path (all composing fields available) re-derives
991
- * in the SAME `UpdateExpression` and is unaffected by this option.
992
- * - `'read-modify-write'` — read the current item, merge `{ ...item, ...changes }`,
993
- * re-derive every affected GSI key from the merged image, and write back under
994
- * an optimistic condition (the item must still exist / be unchanged). This costs
995
- * one extra read and is NOT atomic with the original update, but always re-derives
996
- * correctly.
997
- */
998
- rederive?: 'read-modify-write';
999
- /**
1000
- * Host-injected per-call write context (issue #50 / #139) — exposed to every
1001
- * write hook (W1–W5) as `ctx.context`. NEVER serialized. Absent ⇒ `{}`.
1002
- */
1003
- context?: RequestContext;
1004
- }
1005
- interface DeleteOptions<T = unknown> {
1006
- condition?: WriteCondition<T>;
1007
- /**
1008
- * Per-call throttle / transient-error retry override (issue #111). A
1009
- * {@link RetryPolicy} replaces the global policy for this call; `false` disables
1010
- * retry for this call.
1011
- */
1012
- retry?: RetryOverride;
1013
- /**
1014
- * Host-injected per-call write context (issue #50 / #139) — exposed to every
1015
- * write hook (W1–W5) as `ctx.context`. NEVER serialized. Absent ⇒ `{}`.
1016
- */
1017
- context?: RequestContext;
1018
- }
1019
-
1020
671
  interface PutInput {
1021
672
  TableName: string;
1022
673
  Item: Record<string, unknown>;
@@ -1024,8 +675,6 @@ interface PutInput {
1024
675
  ExpressionAttributeNames?: Record<string, string>;
1025
676
  ExpressionAttributeValues?: Record<string, unknown>;
1026
677
  }
1027
- declare function buildPutInput(modelClass: Function, item: Record<string, unknown>, options?: PutOptions): PutInput;
1028
- declare function executePut(modelClass: Function, item: Record<string, unknown>, options?: PutOptions): Promise<void>;
1029
678
 
1030
679
  interface UpdateInput {
1031
680
  TableName: string;
@@ -1035,8 +684,6 @@ interface UpdateInput {
1035
684
  ExpressionAttributeValues?: Record<string, unknown>;
1036
685
  ConditionExpression?: string;
1037
686
  }
1038
- declare function buildUpdateInput(modelClass: Function, entity: Record<string, unknown>, changes: Record<string, unknown>, options?: UpdateOptions): UpdateInput;
1039
- declare function executeUpdate(modelClass: Function, entity: Record<string, unknown>, changes: Record<string, unknown>, options?: UpdateOptions): Promise<void>;
1040
687
 
1041
688
  interface DeleteInput {
1042
689
  TableName: string;
@@ -1045,8 +692,96 @@ interface DeleteInput {
1045
692
  ExpressionAttributeNames?: Record<string, string>;
1046
693
  ExpressionAttributeValues?: Record<string, unknown>;
1047
694
  }
1048
- declare function buildDeleteInput(modelClass: Function, keyObj: Record<string, unknown>, options?: DeleteOptions): DeleteInput;
1049
- declare function executeDelete(modelClass: Function, keyObj: Record<string, unknown>, options?: DeleteOptions): Promise<void>;
695
+
696
+ /**
697
+ * App-level throttle / transient-error retry for single-op sends (issue #111).
698
+ *
699
+ * GraphDDB's single-item ops (GetItem / Query / Put / Update / Delete) and the
700
+ * read fan-out (relation resolution) used to throw on throttling
701
+ * (`ProvisionedThroughputExceededException` / `ThrottlingException`) and rely
702
+ * solely on the AWS SDK v3 default retry, so the LIBRARY itself guaranteed no
703
+ * throttle-resilience. The batch ops already retry partial completion
704
+ * (`UnprocessedKeys` / `UnprocessedItems`, see `src/operations/batch-retry.ts`);
705
+ * this module is the SEPARATE, library-owned ERROR retry that wraps every
706
+ * `docClient.send()` for the single ops via {@link RetryingExecutor}.
707
+ *
708
+ * The two retry layers are DISTINCT and must not be merged:
709
+ * - (A) partial-batch retry (`batch-retry.ts`) — DynamoDB accepted the request
710
+ * but could not process every key/item; the request itself did NOT error.
711
+ * - (B) error retry (this module) — `send()` REJECTED with a throttle / transient
712
+ * error and is retried whole.
713
+ * They share {@link computeBackoffDelay} so backoff is consistent across both,
714
+ * but they are different mechanisms.
715
+ *
716
+ * The default policy is ENABLED out of the box (see {@link DEFAULT_RETRY_POLICY}):
717
+ * even with zero configuration a sensible exponential-backoff-with-jitter,
718
+ * bounded-attempts retry applies. Users tune it globally via
719
+ * `graphddb.config.retry(...)` or per-call via the `retry?:` option on the
720
+ * operation option bags; `retry: false` disables retry for that one call.
721
+ *
722
+ * NOTE on the AWS SDK's OWN retry: now that the library owns retry, a
723
+ * `DynamoDBClient` left at the SDK default `maxAttempts: 3` retries
724
+ * MULTIPLICATIVELY underneath this layer. Users should construct their client
725
+ * with `maxAttempts: 1` so the library is the single source of truth for retry
726
+ * (documented at `graphddb.config.client` and in the README). The library does NOT
727
+ * silently mutate a user-provided client.
728
+ */
729
+ /** The single-op kind a retry is wrapping — surfaced to {@link RetryPolicy.onRetry}. */
730
+ type RetryOperationKind = 'get' | 'query' | 'batchGet' | 'put' | 'update' | 'delete' | 'batchWrite' | 'transactWrite';
731
+ /** The context handed to {@link RetryPolicy.onRetry} before each backoff sleep. */
732
+ interface RetryInfo {
733
+ /** 1-based attempt number that just FAILED and is about to be retried. */
734
+ readonly attempt: number;
735
+ /** The throttle / transient error that triggered this retry. */
736
+ readonly error: unknown;
737
+ /** The backoff delay (ms) about to be slept before the next attempt. */
738
+ readonly delayMs: number;
739
+ /** Which single-op kind is being retried. */
740
+ readonly operation: RetryOperationKind;
741
+ }
742
+ /**
743
+ * A retry policy for the single-op error-retry layer (issue #111). Every field is
744
+ * optional and resolved INDEPENDENTLY, in layers (see {@link resolveRetryPolicy}):
745
+ * a per-call override field wins, else the globally-configured policy's field
746
+ * (from `graphddb.config.retry`), else
747
+ * {@link DEFAULT_RETRY_POLICY}. So a partial per-call override (e.g.
748
+ * `{ maxAttempts: 3 }`) overrides ONLY that field and INHERITS the rest from the
749
+ * global policy — a global `onRetry` / `jitter` is NOT dropped. A policy value of
750
+ * `false` on an option bag disables retry entirely for that call.
751
+ */
752
+ interface RetryPolicy {
753
+ /**
754
+ * Maximum number of attempts (the FIRST try counts as attempt 1). `1` disables
755
+ * retry (a single try, no backoff). Default {@link DEFAULT_MAX_ATTEMPTS}.
756
+ */
757
+ readonly maxAttempts?: number;
758
+ /**
759
+ * Compute the base backoff delay (ms) for a 1-based retry attempt, BEFORE jitter.
760
+ * Defaults to {@link computeBackoffDelay} (`min(1000, 50*2^(attempt-1))`), the
761
+ * same ramp the batch partial-retry layer uses, so backoff is consistent across
762
+ * the two layers.
763
+ */
764
+ readonly computeDelay?: (attempt: number) => number;
765
+ /**
766
+ * Jitter strategy applied to the base delay:
767
+ * - `'full'` (default) — uniform random in `[0, base]` (AWS "full jitter"),
768
+ * the recommended strategy to de-correlate retries across clients.
769
+ * - `'none'` — use the base delay verbatim (deterministic; handy in tests).
770
+ */
771
+ readonly jitter?: 'full' | 'none';
772
+ /**
773
+ * Classify whether an error is retryable. Defaults to {@link isRetryableError}
774
+ * (throttle + 5xx / transient). A custom classifier fully replaces the default —
775
+ * it should still avoid retrying validation / conditional-check failures.
776
+ */
777
+ readonly isRetryable?: (error: unknown) => boolean;
778
+ /**
779
+ * Observability hook (issue #111 §8): invoked BEFORE each backoff sleep with the
780
+ * attempt number, the triggering error, the computed (post-jitter) delay, and the
781
+ * operation kind. Never invoked on the terminal success or the terminal failure.
782
+ */
783
+ readonly onRetry?: (info: RetryInfo) => void;
784
+ }
1050
785
 
1051
786
  /** Result of a read operation (GetItem / Query / BatchGetItem). */
1052
787
  interface ExecutorResult {
@@ -1188,7 +923,7 @@ interface Executor {
1188
923
  * registry / runtime machinery the read hooks (#138) introduced.
1189
924
  *
1190
925
  * A write {@link Middleware} hook is host-only, runtime-registered (via
1191
- * `DDBModel.use`), and **never serialized** — like the read hooks it lives only on
926
+ * `graphddb.config.use`), and **never serialized** — like the read hooks it lives only on
1192
927
  * the {@link import('../client/ClientManager.js').ClientManager} host-runtime
1193
928
  * singleton and never touches the planner / spec generator / `operations.json`, so
1194
929
  * the TS↔Python bridge (#48) is unaffected. It is **unrestricted by intent**: a
@@ -1341,7 +1076,7 @@ interface WriteMiddleware {
1341
1076
  * module implements the **read** hook points R1–R5 and their context objects.
1342
1077
  *
1343
1078
  * A {@link Middleware} is a host-only, runtime-registered object (registered via
1344
- * `DDBModel.use`) that runs at fixed seams in the read pipeline. Hooks are
1079
+ * `graphddb.config.use`) that runs at fixed seams in the read pipeline. Hooks are
1345
1080
  * **never serialized** — they live only on the {@link import('../client/ClientManager.js').ClientManager}
1346
1081
  * host-runtime singleton and never touch the planner / spec generator /
1347
1082
  * `operations.json`, so the TS↔Python bridge (#48) is unaffected. Hooks are
@@ -1525,7 +1260,7 @@ type ProjectionOf<T, Sel> = Sel extends SelectableOf<T> ? Sel : SelectOf<Sel>;
1525
1260
  * usages keep compiling, while the precise inference is available when the
1526
1261
  * constructor type is supplied (as it is from `DDBModel.asModel()`).
1527
1262
  */
1528
- type AnyModelClass$1 = abstract new (...args: any[]) => DDBModel;
1263
+ type AnyModelClass = abstract new (...args: any[]) => DDBModel;
1529
1264
  /**
1530
1265
  * The accepted PK / GSI key type for `list()` / `explain()`.
1531
1266
  *
@@ -1535,24 +1270,24 @@ type AnyModelClass$1 = abstract new (...args: any[]) => DDBModel;
1535
1270
  * {@link QueryKey}. Derived from the model class when available, otherwise
1536
1271
  * permissive.
1537
1272
  */
1538
- type ListKey<C> = [C] extends [AnyModelClass$1] ? PartialQueryKeyOf<C> : Record<string, unknown>;
1273
+ type ListKey<C> = [C] extends [AnyModelClass] ? PartialQueryKeyOf<C> : Record<string, unknown>;
1539
1274
  /**
1540
1275
  * The accepted key type for `query()` — only keys guaranteed to return a
1541
1276
  * single item (PK + unique GSIs). Derived from the model class when available.
1542
1277
  */
1543
- type QueryKey<C> = [C] extends [AnyModelClass$1] ? UniqueQueryKeyOf<C> : Record<string, unknown>;
1278
+ type QueryKey<C> = [C] extends [AnyModelClass] ? UniqueQueryKeyOf<C> : Record<string, unknown>;
1544
1279
  /**
1545
1280
  * The accepted key type for `delete()` (PK only). Derived from the model
1546
1281
  * class when available.
1547
1282
  */
1548
- type DeleteKey<C> = [C] extends [AnyModelClass$1] ? PrimaryKeyOf<C> : Record<string, unknown>;
1283
+ type DeleteKey<C> = [C] extends [AnyModelClass] ? PrimaryKeyOf<C> : Record<string, unknown>;
1549
1284
  /**
1550
1285
  * The accepted *explicit* key type for `update()` — the base-table primary key
1551
1286
  * only. DynamoDB `UpdateItem` can target an item solely by its base-table PK
1552
1287
  * (GSI-based updates are impossible), so this is `PrimaryKeyOf<C>`, not
1553
1288
  * `UniqueQueryKeyOf<C>`. Derived from the model class when available.
1554
1289
  */
1555
- type UpdateExplicitKey<C> = [C] extends [AnyModelClass$1] ? PrimaryKeyOf<C> : Record<string, unknown>;
1290
+ type UpdateExplicitKey<C> = [C] extends [AnyModelClass] ? PrimaryKeyOf<C> : Record<string, unknown>;
1556
1291
  /**
1557
1292
  * Options accepted by `list()` (third argument). The projection is supplied as
1558
1293
  * the separate second `select` argument, symmetric with `query()`.
@@ -1726,145 +1461,6 @@ interface ModelStatic<T extends DDBModel, C = unknown> {
1726
1461
  deleteItem(key: DeleteKey<C>, options?: WriteOptions<T>): Promise<void>;
1727
1462
  }
1728
1463
 
1729
- type TransactWriteItemInput = {
1730
- Put: PutInput;
1731
- } | {
1732
- Update: UpdateInput;
1733
- } | {
1734
- Delete: DeleteInput;
1735
- } | {
1736
- ConditionCheck: ConditionCheckInput;
1737
- };
1738
- declare function attachModelClass<T extends DDBModel>(modelStatic: ModelStatic<T>, modelClass: new (...args: unknown[]) => T): ModelStatic<T>;
1739
- /**
1740
- * Per-item capture descriptor recorded alongside each transact item, so the
1741
- * write-capture seam (issue #72) can emit a labelled raw record after the
1742
- * transaction commits. `TransactWriteItems` returns no images (spec §14), so
1743
- * these carry the keys + the new item for Put/Update and keys only for Delete.
1744
- */
1745
- interface TransactCaptureMeta {
1746
- modelName?: string;
1747
- op: 'put' | 'update' | 'delete';
1748
- table: string;
1749
- keys: {
1750
- pk: string;
1751
- sk: string;
1752
- };
1753
- newItem?: Record<string, unknown>;
1754
- }
1755
- /**
1756
- * One recorded logical write op of an imperative {@link TransactionContext}
1757
- * (issue #139). Captured at `tx.put/update/delete` call time so the write hooks
1758
- * W1 (`write.before`) can run on each op — and rebuild its physical item from the
1759
- * possibly-mutated input — before the batch composes / commits. A `conditionCheck`
1760
- * records NO logical op (it is a read-only assertion, not a logical write).
1761
- */
1762
- interface LogicalWriteOp {
1763
- kind: WriteKind;
1764
- modelClass: Function;
1765
- item?: Record<string, unknown>;
1766
- key?: Record<string, unknown>;
1767
- changes?: Record<string, unknown>;
1768
- options?: PutOptions & UpdateOptions & DeleteOptions;
1769
- }
1770
- declare class TransactionContext {
1771
- private readonly items;
1772
- private readonly captureMeta;
1773
- /**
1774
- * The logical write ops, parallel to {@link items} but EXCLUDING `conditionCheck`
1775
- * entries (those are read-only assertions, recorded only in `items`). Used by the
1776
- * write-hook path (#139) to run W1 on each logical op and recompose the batch.
1777
- * A `null` entry marks an `items` slot that has no logical op (a ConditionCheck),
1778
- * so the eager and logical arrays can be re-aligned at commit.
1779
- */
1780
- private readonly logicalOps;
1781
- get itemCount(): number;
1782
- put(model: ModelStatic<DDBModel>, item: Record<string, unknown>, options?: PutOptions): void;
1783
- update(model: ModelStatic<DDBModel>, entity: Record<string, unknown>, changes: Record<string, unknown>, options?: UpdateOptions): void;
1784
- delete(model: ModelStatic<DDBModel>, key: Record<string, unknown>, options?: DeleteOptions): void;
1785
- /**
1786
- * Add a read-only `ConditionCheck` assertion on a keyed item (issue #81). The
1787
- * item is **not** mutated; the `options.condition` (e.g.
1788
- * `{ attributeExists: 'PK' }` to require the row exists) is asserted, and a
1789
- * failed assertion cancels the **whole** `TransactWriteItems` atomically. This
1790
- * is the foundation for referential-integrity derivation (proposal: `requires
1791
- * <Entity> exists`). A ConditionCheck emits no change-capture record (it writes
1792
- * nothing).
1793
- *
1794
- * @throws if `options.condition` is missing — a ConditionCheck without an
1795
- * assertion is meaningless.
1796
- */
1797
- conditionCheck(model: ModelStatic<DDBModel>, key: Record<string, unknown>, options: {
1798
- condition: Record<string, unknown>;
1799
- }): void;
1800
- /** @internal */
1801
- getTransactItems(): TransactWriteItemInput[];
1802
- /** @internal — the recorded logical write ops (issue #139 W1), `null` per ConditionCheck slot. */
1803
- getLogicalOps(): readonly (LogicalWriteOp | null)[];
1804
- /** @internal — capture descriptors for the write-capture seam (issue #72). */
1805
- getCaptureMeta(): TransactCaptureMeta[];
1806
- private assertWithinLimit;
1807
- }
1808
- declare function executeTransaction(fn: (tx: TransactionContext) => void | Promise<void>, options?: {
1809
- context?: RequestContext;
1810
- }): Promise<void>;
1811
-
1812
- interface BatchGetRequest {
1813
- model: ModelStatic<DDBModel>;
1814
- keys: Record<string, unknown>[];
1815
- }
1816
- /**
1817
- * Per-call options for {@link DDBModel.batchGet} (issue #142).
1818
- */
1819
- interface BatchGetOptions {
1820
- /**
1821
- * Host-injected per-call read context (issue #50 / #138 / #142) — exposed to
1822
- * every read hook (R1/R4/R5 at the `batchGet` request level, R2/R3/R5 on each
1823
- * underlying `BatchGetItem` op) as `ctx.context`, and threaded UNCHANGED to
1824
- * every physical op so a global hook (e.g. a tenant-scope R2 FilterExpression
1825
- * or an observability probe) applies across the whole multi-key batch read. A
1826
- * host-language value, NEVER serialized into the SSoT / `operations.json` (the
1827
- * bridge #48 is unaffected). Absent ⇒ `{}`.
1828
- */
1829
- context?: RequestContext;
1830
- }
1831
- interface BatchPutRequest {
1832
- type: 'put';
1833
- model: ModelStatic<DDBModel>;
1834
- item: Record<string, unknown>;
1835
- options?: PutOptions;
1836
- }
1837
- interface BatchDeleteRequest {
1838
- type: 'delete';
1839
- model: ModelStatic<DDBModel>;
1840
- key: Record<string, unknown>;
1841
- options?: DeleteOptions;
1842
- }
1843
- type BatchWriteRequest = BatchPutRequest | BatchDeleteRequest;
1844
- declare class BatchGetResult {
1845
- private readonly groups;
1846
- constructor(groups: Map<Function, Record<string, unknown>[]>);
1847
- get<T extends DDBModel>(model: ModelStatic<T>): Partial<T>[];
1848
- }
1849
- /**
1850
- * The multi-key, multi-model batch-read core (issue #142). Wraps the request
1851
- * entry in the request-level read hooks R1 (`read.before`) / R4
1852
- * (`read.afterFetch`) / R5 (`read.onError`) with request kind `'batchGet'`, and
1853
- * each underlying physical `BatchGetItem` op (per table/chunk) in R2/R3/R5 via
1854
- * {@link executeBatchGetForTable}. The read middleware runtime is built ONCE here
1855
- * from `options.context` and threaded UNCHANGED to every op, so a global hook
1856
- * (e.g. a tenant-scope R2 FilterExpression) applies across the whole batch.
1857
- *
1858
- * R1 sees the mutable batch `requests` on `ctx.params.requests`; the library
1859
- * re-reads them after R1 runs, so a hook may add / drop / mutate requests before
1860
- * any op is issued and may `throw` to cancel the whole batch. R4 may transform /
1861
- * replace the assembled {@link BatchGetResult}; R5 may recover by returning one.
1862
- * With no middleware registered the shared `NO_MIDDLEWARE` runtime keeps the hot
1863
- * path unchanged (the zero-overhead fast path).
1864
- */
1865
- declare function executeBatchGet(requests: BatchGetRequest[], options?: BatchGetOptions): Promise<BatchGetResult>;
1866
- declare function executeBatchWrite(requests: BatchWriteRequest[]): Promise<void>;
1867
-
1868
1464
  /**
1869
1465
  * Intermediate representation (IR) produced by the parameterized definition DSL
1870
1466
  * (issue #41). This is the **in-memory, typed** representation consumed by the
@@ -1952,6 +1548,8 @@ type ConditionInput<X = never> = {
1952
1548
  readonly attributeExists: string;
1953
1549
  } | {
1954
1550
  readonly attributeNotExists: string;
1551
+ } | {
1552
+ readonly scpExpr: ExpressionSpec;
1955
1553
  } | RawCondition | ConditionTree<X>;
1956
1554
  /** A recursive declarative condition tree (field clauses + logical groups). */
1957
1555
  interface ConditionTree<X = never> {
@@ -1960,26 +1558,6 @@ interface ConditionTree<X = never> {
1960
1558
  readonly not?: ConditionInput<X>;
1961
1559
  readonly [field: string]: ConditionLeaf<X> | ConditionOperatorObject<X> | readonly ConditionInput<X>[] | ConditionInput<X> | undefined;
1962
1560
  }
1963
- /** Options accepted by the write `define*` entry points (issue #46). */
1964
- interface WriteDefinitionOptions {
1965
- /** Optional declarative write condition (subset). */
1966
- readonly condition?: ConditionInput;
1967
- /**
1968
- * Optional human-readable description of the command use case (issue #154). Pure
1969
- * documentation — propagated to `operations.json` and the generated Python
1970
- * repository-method docstring. Omit for unchanged output.
1971
- */
1972
- readonly description?: string;
1973
- }
1974
- /** Options accepted by the read `define*` entry points (issue #154). */
1975
- interface ReadDefinitionOptions {
1976
- /**
1977
- * Optional human-readable description of the query use case (issue #154). Pure
1978
- * documentation — propagated to `operations.json` and the generated Python
1979
- * repository-method docstring. Omit for unchanged output.
1980
- */
1981
- readonly description?: string;
1982
- }
1983
1561
  /**
1984
1562
  * Identifies the entity an operation targets. `name` is the model class name
1985
1563
  * (entity name); `modelClass` is retained so the planner can resolve metadata
@@ -2025,8 +1603,8 @@ type ParamStructure = {
2025
1603
  /**
2026
1604
  * The typed IR for one definition. Generic over the entity, the parameterized
2027
1605
  * structure(s), the (optional) select projection, and the collected params map
2028
- * so that all type information survives into the value returned by
2029
- * `defineQueries` / `defineCommands`.
1606
+ * so that all type information survives into the operation IR the contract layer
1607
+ * (`publishQuery` / `publishCommand`) lowers to via `opToDefinition`.
2030
1608
  *
2031
1609
  * Field presence by `operation`:
2032
1610
  *
@@ -2066,7 +1644,7 @@ interface OperationDefinition<T extends DDBModel, Op extends OperationKind, K, S
2066
1644
  readonly condition?: ConditionInput;
2067
1645
  /**
2068
1646
  * Optional human-readable description of the definition (issue #154), supplied
2069
- * via the entry-point options (`defineQuery(..., { description })` etc.). Pure
1647
+ * via a contract method's `description` field. Pure
2070
1648
  * documentation — propagated to the serialized {@link
2071
1649
  * import('../spec/types.js').QuerySpec.description} /
2072
1650
  * {@link import('../spec/types.js').CommandSpec.description} and to the generated
@@ -2080,62 +1658,7 @@ interface OperationDefinition<T extends DDBModel, Op extends OperationKind, K, S
2080
1658
  }
2081
1659
  /** Any operation definition node, regardless of its type parameters. */
2082
1660
  type AnyOperationDefinition = OperationDefinition<any, OperationKind, unknown, unknown, unknown, Record<string, ParamDescriptor>>;
2083
- /**
2084
- * A map of definition name → {@link OperationDefinition}, as accepted by
2085
- * `defineQueries` / `defineCommands` and returned (unchanged in type) from them.
2086
- */
2087
- type DefinitionMap = Record<string, AnyOperationDefinition>;
2088
1661
 
2089
- /**
2090
- * Model write-semantics — the reusable *save contract* `entityWrites` (issue #83,
2091
- * Epic #80; spec `docs/mutation-command-derivation.md` §2).
2092
- *
2093
- * `Model.writes` declares the **write-side invariants / effects required for this
2094
- * entity to be consistent on DynamoDB** — a *reusable save contract*, NOT a
2095
- * mutation and NOT business logic. It is a per-lifecycle map
2096
- * (`{ create?, update?, remove? }`) whose values are {@link LifecycleContract}s
2097
- * built with `w.lifecycle({...})`; each carries the §2 effect arrays
2098
- * (`requires` / `unique` / `edges` / `derive` / `emits` / `idempotency`).
2099
- *
2100
- * ## What #83 does — and deliberately does NOT — with the effects
2101
- *
2102
- * The mutation compiler (#83) resolves a fragment's lifecycle from this save
2103
- * contract (the fragment's intent — `m.create` / `m.update` / `m.remove` — picks
2104
- * `create` / `update` / `remove`) and emits the **base write op** for that
2105
- * lifecycle (create → `PutItem` with `attribute_not_exists(PK)`, update →
2106
- * `UpdateItem`, remove → `DeleteItem`). The §2 effect arrays are **stored
2107
- * opaquely** here and consumed by NONE of #83: deriving `ConditionCheck`
2108
- * (requires, #84), uniqueness guards (#86), adjacency edge `Put` / `Delete` from
2109
- * `edges` (#85), derived counter `UpdateItem`s (#85), outbox events (#87), and
2110
- * the idempotency guard (#87) are each their own follow-up issue. #83 only shapes
2111
- * the declaration and exposes the {@link LifecycleContract.effects} a later issue
2112
- * reads — the **per-fragment hook** the compiler leaves empty (see
2113
- * `compileFragment` in `src/spec/mutation-command.ts`).
2114
- *
2115
- * ## Coexistence with edge writes (`edgeWrites`, #82)
2116
- *
2117
- * This is a **distinct** construct from {@link edgeWrites} (#82, the adjacency
2118
- * edge-only `writes` member). `edgeWrites` declares *only* the edge write side of
2119
- * an adjacency entity; `entityWrites` is the full per-lifecycle save contract a
2120
- * mutation fragment adopts. Both are recognized by their own brand, so a model
2121
- * may carry either form on its static `writes` member without collision; the
2122
- * mutation compiler reads {@link getEntityWrites} (this marker), the edge
2123
- * derivation reads its own.
2124
- *
2125
- * ## Trivial base op when a model declares no `writes`
2126
- *
2127
- * A target model that declares **no** `entityWrites` save contract has no
2128
- * lifecycle to resolve; the fragment then compiles the **trivial base op** for
2129
- * its intent (create → `Put` `attribute_not_exists(PK)`, update → `Update`,
2130
- * remove → `Delete`) directly against the plain model. So
2131
- * `{ create: () => PostModel, key, input }` works on a bare model — see
2132
- * {@link resolveLifecycle} in `src/spec/mutation-command.ts`.
2133
- */
2134
- /**
2135
- * The lifecycle phase a save contract entry declares. The mutation fragment's
2136
- * intent (`m.create` / `m.update` / `m.remove`) selects the matching entry.
2137
- */
2138
- type WriteLifecyclePhase = 'create' | 'update' | 'remove';
2139
1662
  /**
2140
1663
  * A path-rooted leaf value in a save-contract effect (proposal §2): every value
2141
1664
  * binds to an explicit path root so its source is unambiguous —
@@ -2255,18 +1778,6 @@ declare const MAINTAIN_TRIGGER_BRAND: unique symbol;
2255
1778
  type MaintainTrigger = `${string}.${MaintainEvent}` & {
2256
1779
  readonly [MAINTAIN_TRIGGER_BRAND]: true;
2257
1780
  };
2258
- /**
2259
- * Construct a validated {@link MaintainTrigger} from an `Entity.event` string.
2260
- * Rejects a value that is not a non-empty entity name followed by `.created` /
2261
- * `.updated` / `.removed`, so an invalid trigger is refused at construction time
2262
- * (the AC's "invalid trigger string is rejected"). The brand is a compile-time
2263
- * marker only; the returned value is the input string verbatim.
2264
- *
2265
- * @throws if `value` is not a well-formed `Entity.event` trigger string.
2266
- */
2267
- declare function maintainTrigger(value: string): MaintainTrigger;
2268
- /** Runtime guard: is `value` a well-formed {@link MaintainTrigger} string? */
2269
- declare function isMaintainTrigger(value: unknown): value is MaintainTrigger;
2270
1781
  /**
2271
1782
  * A projection transform op (issue #120, 論点3 = 関数形): the function-form
2272
1783
  * `w.transform(path, op, ...args)` is the **primary** authoring surface (there is
@@ -2631,8 +2142,6 @@ interface LifecycleContract {
2631
2142
  /** The §2 effect arrays, stored opaquely by #83. */
2632
2143
  readonly effects: LifecycleEffects;
2633
2144
  }
2634
- /** Runtime guard: is `value` a {@link LifecycleContract} (a `w.lifecycle(...)`)? */
2635
- declare function isLifecycleContract(value: unknown): value is LifecycleContract;
2636
2145
  /**
2637
2146
  * Marker carried by a model's static `writes` member when it is an
2638
2147
  * {@link EntityWritesDefinition} (an `entityWrites(...)` save contract), so it is
@@ -2655,8 +2164,6 @@ interface EntityWritesDefinition {
2655
2164
  /** The remove-lifecycle save contract, if declared. */
2656
2165
  readonly remove?: LifecycleContract;
2657
2166
  }
2658
- /** Runtime guard: is `value` an {@link EntityWritesDefinition}? */
2659
- declare function isEntityWritesDefinition(value: unknown): value is EntityWritesDefinition;
2660
2167
  /**
2661
2168
  * The recorder handed to the {@link entityWrites} builder. `w.lifecycle({...})`
2662
2169
  * accepts the §2 effect arrays and brands them into a {@link LifecycleContract}.
@@ -2783,8 +2290,6 @@ declare function entityWrites<M = unknown>(builder: (w: WriteRecorder) => Entity
2783
2290
  * collision (a model carries one or the other; the marker disambiguates).
2784
2291
  */
2785
2292
  declare function getEntityWrites(modelClass: new (...args: unknown[]) => unknown): EntityWritesDefinition | undefined;
2786
- /** The lifecycle phase a mutation fragment intent selects. */
2787
- declare function lifecyclePhaseForIntent(intent: 'create' | 'update' | 'remove'): WriteLifecyclePhase;
2788
2293
 
2789
2294
  /**
2790
2295
  * Internal write-plan composition DSL — `mutation` / `definePlan` (issue #83,
@@ -2793,7 +2298,7 @@ declare function lifecyclePhaseForIntent(intent: 'create' | 'update' | 'remove')
2793
2298
  * A **mutation is INTERNAL** — it is *not* a public API. It is a *write-plan
2794
2299
  * composition language* used as the **implementation** of a Command: it declares
2795
2300
  * a set of write **fragments** that must execute atomically. The only externally
2796
- * exposed surface is the fixed Command IF (`publicCommandModel(...).plan(...)`,
2301
+ * exposed surface is the fixed Command IF (`publishCommand(...).plan(...)`,
2797
2302
  * `src/define/contract.ts`); the mutation document is never exposed.
2798
2303
  *
2799
2304
  * The authoring form (issue #108) is a **descriptor map**: the body returns an
@@ -2844,11 +2349,18 @@ declare function lifecyclePhaseForIntent(intent: 'create' | 'update' | 'remove')
2844
2349
 
2845
2350
  /**
2846
2351
  * The intent of a write fragment (proposal §3): `create` / `update` / `remove`
2847
- * (`remove`, **not** `del`). The intent selects the lifecycle from the target's
2848
- * save contract, and the base write op (create → `PutItem`, update →
2849
- * `UpdateItem`, remove → `DeleteItem`).
2850
- */
2851
- type MutationIntent = 'create' | 'update' | 'remove';
2352
+ * / `upsert` (`remove`, **not** `del`). The intent selects the lifecycle from the
2353
+ * target's save contract, and the base write op (create → `PutItem`, update →
2354
+ * `UpdateItem`, remove → `DeleteItem`, upsert → `PutItem`).
2355
+ *
2356
+ * `upsert` is a `PutItem` with **no** `attribute_not_exists(PK)` guard — an
2357
+ * unconditional overwrite (the semantics of the removed `definePut`). It shares
2358
+ * `create`'s base op (`put`) and lifecycle phase (a Put's derived effects are the
2359
+ * same), but the trivial base op carries no existence guard: an author-supplied
2360
+ * `condition` is honored verbatim (not ANDed with a `notExists`). That absence of
2361
+ * the guard is exactly what distinguishes an `upsert` from a `create`.
2362
+ */
2363
+ type MutationIntent = 'create' | 'update' | 'remove' | 'upsert';
2852
2364
  declare const INPUT_REF_BRAND: unique symbol;
2853
2365
  /**
2854
2366
  * A faithful reference to a mutation **input** field, minted by reading `$.<field>`
@@ -2865,8 +2377,6 @@ interface MutationInputRef {
2865
2377
  /** The template token this reference renders to, e.g. `{title}`. */
2866
2378
  readonly token: string;
2867
2379
  }
2868
- /** Runtime guard: is `value` a {@link MutationInputRef} (a `$.<field>` read)? */
2869
- declare function isMutationInputRef(value: unknown): value is MutationInputRef;
2870
2380
  declare const ENTITY_REF_BRAND: unique symbol;
2871
2381
  /**
2872
2382
  * A faithful **cross-fragment reference** (`$.entity[i].field`): the value a later
@@ -2888,25 +2398,6 @@ interface MutationEntityRef {
2888
2398
  /** The source path, e.g. `$.entity[0].postId` (documentary; surfaces in errors). */
2889
2399
  readonly path: string;
2890
2400
  }
2891
- /**
2892
- * The placeholder proxy handed to a mutation body as `$`. In the descriptor-map
2893
- * authoring form (issue #108) a top-level read `$.<x>` is **ambiguous** until used:
2894
- *
2895
- * - `$.role` placed at a `key` / `input` leaf is an **input-field reference**
2896
- * (a {@link MutationInputRef}, token `{role}`) — bound from the command input;
2897
- * - `$.membership.role` is a **cross-fragment reference** — the `role` field
2898
- * written by the fragment named `membership` (an alias). It mints an
2899
- * {@link AliasFieldRef}, which the {@link mutation} assembler rewrites into the
2900
- * legacy `$.entity[<index>].<field>` string the compiler resolves (the alias →
2901
- * declaration-order index is known once every alias key is seen).
2902
- *
2903
- * So each top-level read returns a value that is **both** a faithful
2904
- * {@link MutationInputRef} (when used directly as a leaf) and supports one further
2905
- * `.<field>` access (when used as an alias root). Any other access / coercion
2906
- * throws — the binding stays declarative (only a direct `$.field` / `$.alias.field`
2907
- * reference or a literal is representable, never a transform).
2908
- */
2909
- type MutationInputProxy = Record<string, MutationInputRef>;
2910
2401
  declare const FRAGMENT_BRAND: unique symbol;
2911
2402
  /**
2912
2403
  * The per-field input binding of a fragment: model field name → a faithful
@@ -2915,14 +2406,6 @@ declare const FRAGMENT_BRAND: unique symbol;
2915
2406
  * field they key, never a transform.
2916
2407
  */
2917
2408
  type FragmentInput = Readonly<Record<string, MutationInputRef | string | number | boolean | Date | null>>;
2918
- /**
2919
- * A descriptor `key` / `input` value as authored in the descriptor-map form
2920
- * (issue #108): a `$.field` input reference, a `$.alias.field` cross-fragment
2921
- * reference (an opaque alias-field ref the assembler rewrites), or a literal.
2922
- * Structurally `unknown` at the leaf because the alias-field ref is internal; the
2923
- * assembler validates every leaf via {@link assertFaithfulInput}.
2924
- */
2925
- type DescriptorBinding = Readonly<Record<string, unknown>>;
2926
2409
  /** A concrete scalar admissible at a condition operand leaf. */
2927
2410
  type ConditionScalar = MutationInputRef | string | number | boolean | Date | null;
2928
2411
  /** A declarative operator object on one condition field (the #114-A subset). */
@@ -2942,8 +2425,8 @@ interface FragmentConditionOperatorObject {
2942
2425
  }
2943
2426
  /**
2944
2427
  * A declarative write **condition** authored on a {@link WriteDescriptor} (issue
2945
- * #242, Phase 2) — the same #114-A subset the #101 public write descriptor and
2946
- * `defineCommands` support, but with {@link MutationInputRef} (`$.field`) leaves
2428
+ * #242, Phase 2) — the same #114-A subset the #101 public write descriptor
2429
+ * supports, but with {@link MutationInputRef} (`$.field`) leaves
2947
2430
  * (the mutation authoring form's placeholder) rather than `param.*` / concrete
2948
2431
  * values. It is a recursive tree of field clauses (bare equality or an operator
2949
2432
  * object) and `and` / `or` / `not` logical groups. The compiler translates each
@@ -2996,8 +2479,6 @@ interface MutationFragment {
2996
2479
  */
2997
2480
  readonly condition?: FragmentCondition;
2998
2481
  }
2999
- /** Runtime guard: is `value` a {@link MutationFragment}? */
3000
- declare function isMutationFragment(value: unknown): value is MutationFragment;
3001
2482
  /**
3002
2483
  * A model reference in a write descriptor (issue #108): either the model class /
3003
2484
  * `.asModel()` value **directly** (`{ create: GroupMembership }`) or a **thunk**
@@ -3007,60 +2488,6 @@ declare function isMutationFragment(value: unknown): value is MutationFragment;
3007
2488
  * `m.create(() => Model, …)` recorder required.
3008
2489
  */
3009
2490
  type ModelRef = ModelStatic<DDBModel> | (new (...args: unknown[]) => unknown) | (() => ModelStatic<DDBModel> | (new (...args: unknown[]) => unknown));
3010
- /**
3011
- * One **write descriptor** in the descriptor-map authoring form (issue #108) —
3012
- * the declarative twin of an in-process `DDBModel.mutate` entry (#101). The intent
3013
- * key (`create` / `update` / `remove`) is the **discriminator** and carries the
3014
- * target {@link ModelRef} (direct or thunk). The remaining fields are declarative
3015
- * bindings (`$.field` references or literals):
3016
- *
3017
- * - `key` — the target row's primary-key binding (identifies the row);
3018
- * - `input` — the non-key field binding (the body / changed fields). Optional for
3019
- * a `remove` (a delete writes no fields);
3020
- * - `condition` — an optional declarative write gate (issue #242, Phase 2). It is
3021
- * the #114-A condition subset (bare equality, operator objects `{ gt }` / `{ in }`
3022
- * / `{ between }` / …, and `and` / `or` / `not` groups) whose operand leaves are
3023
- * `$.field` input references or literals. The declaration-DSL compiler **consumes**
3024
- * it: each `$.field` leaf becomes a {@link import('./contract.js').ContractParamRef}
3025
- * and the tree is attached to the compiled base op's `condition`, so it is
3026
- * serialized (into the operation spec) and evaluated at runtime exactly as a #101
3027
- * public write descriptor's / `defineCommands` condition is (single-op, in-process
3028
- * transaction, and the Python bridge). A conditioned `update` / `remove` therefore
3029
- * fails a CAS-style write when the gate does not hold, rather than silently
3030
- * committing;
3031
- * - `result` — an optional read-back projection `{ select, options? }` (likewise
3032
- * accepted for authoring parity; the public read-back projection of a
3033
- * `command(...).plan(...)` method is still declared on `command({ select })`).
3034
- *
3035
- * Exactly one intent key must be present.
3036
- */
3037
- interface WriteDescriptor {
3038
- readonly create?: ModelRef;
3039
- readonly update?: ModelRef;
3040
- readonly remove?: ModelRef;
3041
- /** The target row's primary-key binding (`{ field: $.field | literal }`). */
3042
- readonly key: DescriptorBinding;
3043
- /** The non-key field binding; optional (a `remove` writes no fields). */
3044
- readonly input?: DescriptorBinding;
3045
- /**
3046
- * Optional declarative write gate — the #114-A condition subset (issue #242,
3047
- * Phase 2). Compiled, serialized, and evaluated at runtime; see the interface doc.
3048
- */
3049
- readonly condition?: FragmentCondition;
3050
- /** Optional read-back projection `{ select, options? }` (accepted for #101 parity). */
3051
- readonly result?: {
3052
- readonly select?: unknown;
3053
- readonly options?: unknown;
3054
- };
3055
- /**
3056
- * Optional custom save contract to adopt (the **whole** {@link
3057
- * EntityWritesDefinition}). Omit to default to the target model's own `writes`.
3058
- * Per the `use:` rule this is never a single lifecycle — the intent picks it.
3059
- */
3060
- readonly use?: EntityWritesDefinition;
3061
- }
3062
- /** The descriptor map a {@link mutation} body returns: alias → {@link WriteDescriptor}. */
3063
- type MutationDescriptorMap = Record<string, WriteDescriptor>;
3064
2491
  declare const COMMAND_PLAN_BRAND: unique symbol;
3065
2492
  /**
3066
2493
  * The recorded **CommandPlan** — a mutation's IR (proposal §3). It carries the
@@ -3076,54 +2503,187 @@ interface CommandPlan {
3076
2503
  /** The declared write fragments, in declaration order (the mutation IR). */
3077
2504
  readonly fragments: readonly MutationFragment[];
3078
2505
  }
3079
- /** Runtime guard: is `value` a {@link CommandPlan} (a `mutation(...)` result)? */
3080
- declare function isCommandPlan(value: unknown): value is CommandPlan;
2506
+
3081
2507
  /**
3082
- * The body of a {@link mutation} in the descriptor-map authoring form (issue #108):
3083
- * `$ => ({ alias: descriptor, … })`. It receives the placeholder proxy `$` and
3084
- * returns an **alias {@link WriteDescriptor}** map. The map's **insertion order**
3085
- * is the fragments' declaration (execution) order, and each alias is the name a
3086
- * later descriptor's `$.alias.field` cross-fragment reference resolves against.
2508
+ * Declarative transaction definition DSL (issue #46, Python-bridge Phase 4).
2509
+ *
2510
+ * `defineTransaction(params, (tx, p) => { })` captures a **single source of
2511
+ * truth** for a `TransactWriteItems` batch: a parameter map plus a declarative
2512
+ * instruction list (`tx.put` / `tx.update` / `tx.delete`, plus `tx.forEach` over
2513
+ * an array param). The callback is evaluated at definition time with sentinel
2514
+ * proxies in place of params / loop elements, so the recorded instructions carry
2515
+ * **field-reference templates** (`{groupId}`, `{item.userId}`) rather than
2516
+ * concrete values — exactly the shape the static planner (#42) turns into a
2517
+ * serializable `transaction` op the TS and Python runtimes expand identically.
2518
+ *
2519
+ * ## Declarativity boundary (enforced — same soundness as #42 keys)
2520
+ *
2521
+ * The callback body may only:
2522
+ * - call `tx.put/update/delete(Model, …)` with **field references** (from `p`
2523
+ * or a `forEach` element) or **concrete literals** at scalar leaves;
2524
+ * - call `tx.forEach(p.<arrayParam>, (el) => { … }, { when? })` whose body is
2525
+ * itself only `tx.*` writes;
2526
+ * - attach a declarative `when` (a small comparison over field refs) and/or a
2527
+ * `condition` (the `{ notExists } | equality` subset, as for single writes).
2528
+ *
2529
+ * The same hardening that protects **key** positions (#42 `src/spec/symbolic.ts`)
2530
+ * is applied to **value** positions (item / changes / condition values, `when`
2531
+ * right-hand side). Value positions are in fact held to a *stricter* boundary
2532
+ * than keys: a key may legitimately interpolate a field (`` `USER#${id}` ``), so
2533
+ * coercion is allowed there and the differential template check is the boundary;
2534
+ * a value leaf may **only** be a *direct* field reference or a concrete literal,
2535
+ * so at a value position a faithful reference is **never coerced**. That single
2536
+ * fact is the soundness boundary:
2537
+ *
2538
+ * 1. **Throwing sentinel Proxy.** Each param / element placeholder is a Proxy
2539
+ * whose `get` trap whitelists only primitive coercion
2540
+ * (`Symbol.toPrimitive` / `toString` / `valueOf`) and the brand / `token` /
2541
+ * `origin` reads the planner needs, and **throws on every other property or
2542
+ * method access**. So a *direct* transform on a value placeholder —
2543
+ * `p.role.length`, `p.role[0]`, `p.role.toUpperCase()` — fails at the access
2544
+ * site, exactly like a key sentinel. (Same first-line defense as #42.)
2545
+ * 2. **Per-access coercion ledger (primary boundary).** Every minted field ref
2546
+ * records each time it is coerced to a primitive (`Symbol.toPrimitive` /
2547
+ * `toString` / `valueOf`). Because a faithful value reference is stored as
2548
+ * the Proxy *object* and never coerced, **any** coercion of a value ref is
2549
+ * positive proof the callback consumed the parameter into a transform, a
2550
+ * branch, an interpolation, or a comparison — and the build is rejected. This
2551
+ * is *access-site* accurate: it fires even when the same field is *also*
2552
+ * referenced faithfully elsewhere in the item (so its token still surfaces),
2553
+ * and it catches transforms that **converge to a constant or a shared marker
2554
+ * fragment** — `String(p.role).slice(0,0)` → `''`, `.slice(0,3)` → a shared
2555
+ * prefix, `[0]` / `.charAt(0)`, `.includes('Tx')` → a constant boolean, any
2556
+ * `… + 'CONST'` built atop them — which a purely output-comparing check could
2557
+ * not (the dropped field is masked by its faithful sibling, and the converged
2558
+ * value is byte-identical across passes).
2559
+ * 3. **Differential evaluation (secondary).** The callback is evaluated **twice**,
2560
+ * each run delivering placeholders that coerce to a *different* per-field
2561
+ * marker (the two markers share no common prefix and no common substring of
2562
+ * length ≥ 2, and differ in length). Every recorded value leaf must be either
2563
+ * (a) a faithful field reference (the Proxy object survived intact in *both*
2564
+ * runs) or (b) a concrete literal byte-identical across both runs. This is a
2565
+ * defense-in-depth layer behind the coercion ledger.
2566
+ * 4. **Consumed-field check.** A placeholder field that the callback *accessed*
2567
+ * but that never reached the recorded output — e.g. `(p.role === 'admin') ?
2568
+ * 'A' : 'B'`, where `===` does **not** coerce (so the ledger does not fire),
2569
+ * the comparison is `false`, and only a literal is emitted — is rejected: the
2570
+ * param was silently dropped and a branch burned into the spec. (Same
2571
+ * consumed-field accounting #42 uses for `kind === 'admin' ? … : …`.)
2572
+ *
2573
+ * Arbitrary JS, reads, value branching, arithmetic, coercion, or string
2574
+ * transforms on a param / element placeholder are therefore rejected at build
2575
+ * time, in **both** key and value positions — **never silently emitted as a
2576
+ * wrong spec**. Unlike key positions, value positions have **no residual class**:
2577
+ * because a faithful value ref is never coerced, even an identity-shaped
2578
+ * transform (`String(p.x).slice(0)`, `.normalize()`, `.trim()`, `.replace('§',…)`
2579
+ * on an absent substring) is rejected (it coerced the ref). The only operations
2580
+ * that *do not* coerce a ref are (a) storing it directly and (b) the brand read;
2581
+ * (a) is the supported faithful reference and (b) is internal — so the ledger has
2582
+ * no false positives and no escape.
3087
2583
  */
3088
- type MutationBody = ($: MutationInputProxy) => MutationDescriptorMap;
2584
+
2585
+ declare const TX_REF_BRAND: unique symbol;
3089
2586
  /**
3090
- * Declare an internal **mutation** a write-plan composition in the descriptor-map
3091
- * authoring form (issue #108). The body returns an **alias {@link WriteDescriptor}**
3092
- * map; each descriptor names its target via the intent key (`create` / `update` /
3093
- * `remove`: a model or a `() => Model` thunk) and binds `key` / `input` with `$.field`
3094
- * placeholders. Insertion order is execution order, and a later descriptor may read
3095
- * an earlier one's written field via `$.alias.field`.
3096
- *
3097
- * ```ts
3098
- * const JoinGroup = mutation($ => ({
3099
- * membership: { create: GroupMembership, key: { groupId: $.groupId, userId: $.userId }, input: { role: $.role } },
3100
- * permission: { create: Permission, key: { id: $.permissionId }, input: { groupId: $.membership.groupId } },
3101
- * }));
3102
- * ```
3103
- *
3104
- * A leading **name** is optional (it surfaces only in compiler error messages — the
3105
- * alias map already names each fragment): `mutation('JoinGroup', $ => ({ … }))`.
3106
- *
3107
- * The result is a branded {@link CommandPlan} (the mutation IR) — *not* public; it
3108
- * implements a Command IF via `publicCommandModel(...).plan(mutation)`. The IR shape
3109
- * (`{ name, fragments }`) is unchanged from the legacy recorder form, so the compiler,
3110
- * serializer, and every runtime consume it identically.
3111
- *
3112
- * @throws if the body returns a non-object / empty map, a descriptor has no (or
3113
- * multiple) intent key(s), a binding is non-declarative, or a `$.alias.field`
3114
- * reference names an unknown alias.
2587
+ * A captured reference to a parameter (`{name}`) or a `forEach` element field
2588
+ * (`{item.<field>}`). Branded so the planner can distinguish a reference from a
2589
+ * concrete literal, and so a stray reference cannot be silently coerced.
3115
2590
  */
3116
- declare function mutation(body: MutationBody): CommandPlan;
3117
- declare function mutation(name: string, body: MutationBody): CommandPlan;
2591
+ interface TransactionRef {
2592
+ readonly [TX_REF_BRAND]: true;
2593
+ /** The template token this reference renders to, e.g. `{groupId}`. */
2594
+ readonly token: string;
2595
+ /** Human-readable origin (for error messages), e.g. `param 'groupId'`. */
2596
+ readonly origin: string;
2597
+ }
2598
+ /** A declarative comparison used by `when` (the only branching allowed). */
2599
+ type WhenComparison = {
2600
+ readonly op: 'eq';
2601
+ readonly left: TransactionRef;
2602
+ readonly right: unknown;
2603
+ } | {
2604
+ readonly op: 'ne';
2605
+ readonly left: TransactionRef;
2606
+ readonly right: unknown;
2607
+ };
3118
2608
  /**
3119
- * Alias of {@link mutation} (proposal naming: the internal DSL is `mutation` /
3120
- * `definePlan`). Identical behavior; provided so a producer may name the plan
3121
- * declaration `definePlan(…)` when that reads more clearly at the call site.
2609
+ * A single captured instruction inside a transaction. The three write operations
2610
+ * (`put` / `update` / `delete`) mutate an item; `conditionCheck` (issue #81) is a
2611
+ * **read-only assertion** on another keyed item it carries a `key` and a
2612
+ * **required** `condition` and never mutates, but its failure rolls the whole
2613
+ * transaction back. It is the foundation for referential-integrity derivation
2614
+ * (`requires <Entity> exists` → a `conditionCheck` with `attributeExists`).
3122
2615
  */
3123
- declare const definePlan: typeof mutation;
2616
+ interface TxWriteInstruction {
2617
+ readonly kind: 'write';
2618
+ readonly operation: 'put' | 'update' | 'delete' | 'conditionCheck';
2619
+ readonly entity: EntityRef;
2620
+ /** Put: the item structure (field → ref / literal). */
2621
+ readonly item?: Record<string, unknown>;
2622
+ /** Update / delete / conditionCheck: the key structure. */
2623
+ readonly key?: Record<string, unknown>;
2624
+ /** Update: the changes structure. */
2625
+ readonly changes?: Record<string, unknown>;
2626
+ /**
2627
+ * The write / assertion condition (subset). Optional on `put` / `update` /
2628
+ * `delete`; **required** on a `conditionCheck` (the assertion it makes).
2629
+ *
2630
+ * A tx condition operand may be a `p.*` field reference ({@link
2631
+ * TransactionRef}) — the serializer renders it to a `{ $param }` marker — so
2632
+ * the recorded leaf type is widened with `TransactionRef` in the tx context
2633
+ * (#246), matching what the recorder captures from {@link TxWriteOptions}.
2634
+ */
2635
+ readonly condition?: ConditionInput<TransactionRef>;
2636
+ /** Optional declarative guard (skip this item when it does not hold). */
2637
+ readonly when?: WhenComparison;
2638
+ /**
2639
+ * Optional SCP expression guard (issue #261, Phase 3 S1): a behavior-contracts
2640
+ * Expression IR `{ exprVersion: 1, expr }` envelope, carried beside the legacy
2641
+ * `when`. Validated + canonicalized by the spec serializer; a bundle carrying
2642
+ * one is stamped spec version 1.2 and loud-rejected by runtimes whose
2643
+ * expression evaluation has not landed yet (fail-closed).
2644
+ */
2645
+ readonly guard?: ExpressionSpec;
2646
+ }
2647
+ /** A `forEach` block expanding `body` once per element of an array param. */
2648
+ interface TxForEachInstruction {
2649
+ readonly kind: 'forEach';
2650
+ /** The array-param name iterated. */
2651
+ readonly source: string;
2652
+ /** Optional per-element declarative guard. */
2653
+ readonly when?: WhenComparison;
2654
+ /** The (write-only) instructions emitted per element. */
2655
+ readonly body: TxWriteInstruction[];
2656
+ }
2657
+ type TxInstruction = TxWriteInstruction | TxForEachInstruction;
2658
+ /** A small comparison helper: `when.eq(ref, value)` / `when.ne(ref, value)`. */
2659
+ declare const when: {
2660
+ readonly eq: (left: TransactionRef, right: unknown) => WhenComparison;
2661
+ readonly ne: (left: TransactionRef, right: unknown) => WhenComparison;
2662
+ };
2663
+ /** The typed IR node produced by {@link defineTransaction}. */
2664
+ interface TransactionDefinition {
2665
+ /** @internal Marks this object as a transaction definition IR node. */
2666
+ readonly __isTransactionDefinition: true;
2667
+ /** Collected parameters: name → descriptor (array params carry `element`). */
2668
+ readonly params: Readonly<Record<string, ParamDescriptor>>;
2669
+ /** The captured, declarative instruction list. */
2670
+ readonly instructions: readonly TxInstruction[];
2671
+ /**
2672
+ * The **atomicity mode** the author declared, sourced from the authoring call
2673
+ * — the `mutate(body, { mode })` alias records it here. **Never defaulted**
2674
+ * (Phase 4 S3.5): graphddb does not decide/default atomicity; whether a
2675
+ * command body is a single `TransactWriteItems` is up to the model definition
2676
+ * (whether its `mutate` asked for `{ mode: 'transaction' }`). `defineTransaction`
2677
+ * / `defineScpTransaction` leave it `undefined`; the `mutate` alias stamps it
2678
+ * from the authoring `{ mode }` option; `publishCommand`'s SCP-body branch reads
2679
+ * it to decide the wire `single` resolution (undefined ⇒ non-atomic, atomicity
2680
+ * being the sole home of `{ mode: 'transaction' }`).
2681
+ */
2682
+ readonly mode?: MutateMode;
2683
+ }
3124
2684
 
3125
2685
  /**
3126
- * Contract DSL — `publicQueryModel` / `publicCommandModel` (issue #58, CQRS
2686
+ * Contract DSL — `publishQuery` / `publishCommand` (issue #58, CQRS
3127
2687
  * Contract layer, Epic #57; spec `docs/cqrs-contract.md`).
3128
2688
  *
3129
2689
  * A **QueryModel / CommandModel** is the public, storage-independent interface
@@ -3132,15 +2692,15 @@ declare const definePlan: typeof mutation;
3132
2692
  * `get` + `summary` over the same key, differing only in projection). The Key is
3133
2693
  * the access pattern; the Method is the use case.
3134
2694
  *
3135
- * This module sits **on top of** the existing definition DSL
3136
- * (`defineQuery` / `defineList` / `definePut` / `defineUpdate` / `defineDelete`,
3137
- * `src/define/define.ts`) and shares its internal representation
3138
- * ({@link OperationDefinition}). A contract method body resolves to a single
2695
+ * This module is the SOLE read/write authoring surface (#251: the define*
2696
+ * single-op DSL was removed). It builds on the shared operation IR
2697
+ * ({@link OperationDefinition}) that the spec builders (`buildQuerySpec` /
2698
+ * `buildCommandSpec`) consume. A contract method body resolves to a single
3139
2699
  * declarative operation on the underlying model; the closure is **never**
3140
2700
  * serialized — only the resolved declaration is captured into the IR.
3141
2701
  *
3142
2702
  * ```ts
3143
- * const ArticleById = publicQueryModel<ArticleIdKey>()({
2703
+ * const ArticleById = publishQuery<ArticleIdKey>()({
3144
2704
  * get: (keys, params) =>
3145
2705
  * Article.query(keys, { articleId: true, title: true, body: true }, params),
3146
2706
  * });
@@ -3276,7 +2836,6 @@ type QueryMethod<TKey, TParams, TResult> = (key: TKey | readonly TKey[], params:
3276
2836
  */
3277
2837
  type CommandMethod<TKey, TParams, TResult> = (key: TKey | readonly TKey[], params: TParams) => TResult;
3278
2838
  declare const KEY_REF_BRAND: unique symbol;
3279
- declare const KEY_FIELD_REF_BRAND: unique symbol;
3280
2839
  /**
3281
2840
  * The captured `keys` argument of a contract method body. A branded sentinel that
3282
2841
  * supports the two faithful uses the proposal's examples need:
@@ -3295,80 +2854,7 @@ declare const KEY_FIELD_REF_BRAND: unique symbol;
3295
2854
  interface ContractKeyRef {
3296
2855
  readonly [KEY_REF_BRAND]: true;
3297
2856
  }
3298
- /**
3299
- * A captured reference to a single field of the method's `keys` argument
3300
- * (`keys.<field>`), minted when a range body rebuilds a partition key. Branded so
3301
- * the verifier can distinguish a faithful key-field reference from a literal, and
3302
- * so it cannot be silently coerced.
3303
- */
3304
- interface ContractKeyFieldRef {
3305
- readonly [KEY_FIELD_REF_BRAND]: true;
3306
- /** The key field name, e.g. `categoryId`. */
3307
- readonly field: string;
3308
- /** The template token this reference renders to, e.g. `{key.categoryId}`. */
3309
- readonly token: string;
3310
- }
3311
- /** Runtime guard: is `value` a {@link ContractKeyRef} (the whole key sentinel)? */
3312
- declare function isContractKeyRef(value: unknown): value is ContractKeyRef;
3313
- /**
3314
- * Mint a faithful {@link ContractKeyFieldRef} for a named key field — a plain
3315
- * (non-Proxy) reference rendering the `{key.field}` differential token but, at a
3316
- * **value** position (a `put` item field), serialized as `{field}` (see
3317
- * `templateLeaf`, `src/spec/operations.ts`). Used by the **mutation compiler**
3318
- * (#83) to mark which of a `create` fragment's item fields are the model's
3319
- * primary-key (contract Key) fields, so the existing serializer recovers the
3320
- * contract Key from the put item (`keyFieldsOf`) exactly as it does for a
3321
- * hand-written #64 `put` that binds `keys.<field>` into the item. Mirrors
3322
- * {@link mintContractParamRef}.
3323
- *
3324
- * @param field The key field name (rendered `{field}` at a value position).
3325
- */
3326
- declare function mintContractKeyFieldRef(field: string): ContractKeyFieldRef;
3327
- /**
3328
- * The whole-`keys` sentinel value an `update` / `delete` op records in its key
3329
- * slot when the body passed `keys` whole into the op's key position. Exposed so
3330
- * the **mutation compiler** (#83) can build an equivalent planned `update` /
3331
- * `delete` op without re-entering the hardening machinery (a mutation has no
3332
- * `keys` argument — its key fields are bound from `$.input.*`, captured via
3333
- * {@link ContractMethodOp.keyFields}).
3334
- */
3335
- declare function wholeKeysSentinel(): ContractKeyRef;
3336
- /** Runtime guard: is `value` a {@link ContractKeyFieldRef}? */
3337
- declare function isContractKeyFieldRef(value: unknown): value is ContractKeyFieldRef;
3338
- declare const PARAM_REF_BRAND$1: unique symbol;
3339
- /**
3340
- * A captured reference to a field of the method's `params` argument (`{<field>}`).
3341
- * Branded so the recorder can distinguish a faithful reference from a concrete
3342
- * literal, and so a stray reference cannot be silently coerced.
3343
- */
3344
- interface ContractParamRef {
3345
- readonly [PARAM_REF_BRAND$1]: true;
3346
- /** The template token this reference renders to, e.g. `{limit}`. */
3347
- readonly token: string;
3348
- /** The field name on `params`, e.g. `limit`. */
3349
- readonly field: string;
3350
- }
3351
- /** Runtime guard: is `value` a {@link ContractParamRef}? */
3352
- declare function isContractParamRef(value: unknown): value is ContractParamRef;
3353
- /**
3354
- * Mint a faithful {@link ContractParamRef} for a named field — a concrete
3355
- * (non-Proxy) reference rendering the `{field}` token. Unlike the hardening
3356
- * sentinels {@link makeParamFieldRef} builds during a contract method body (which
3357
- * throw on any transform to detect non-declarative use), this is a *plain* ref
3358
- * used by the **mutation compiler** (#83, `src/spec/mutation-command.ts`): a
3359
- * mutation fragment's `input` binding is captured as a {@link MutationInputRef}
3360
- * (its own declarative sentinel), and the compiler translates each one into this
3361
- * `ContractParamRef` so the **existing** contract serializer
3362
- * ({@link opToDefinition} → {@link buildCommandSpec}) and TS runtime
3363
- * ({@link renderLeaf}) consume it with **zero** new template / param machinery —
3364
- * the param name and `{token}` are the input field name, exactly as a hand-written
3365
- * `params.field` reference would render.
3366
- *
3367
- * @param field The param / input field name (the rendered token is `{field}`).
3368
- */
3369
- declare function mintContractParamRef(field: string): ContractParamRef;
3370
2857
  declare const FROM_REF_BRAND: unique symbol;
3371
- declare const COMPOSE_NODE_BRAND: unique symbol;
3372
2858
  /**
3373
2859
  * A parent-result source path produced by {@link from} (proposal "External
3374
2860
  * Query"). It names the field of the **parent** method's result that supplies a
@@ -3386,91 +2872,6 @@ interface ContractFromRef {
3386
2872
  /** The `$`-rooted source path on the parent result, e.g. `$.billingAccountId`. */
3387
2873
  readonly path: string;
3388
2874
  }
3389
- /** Runtime guard: is `value` a {@link ContractFromRef} (a `from(...)` binding)? */
3390
- declare function isContractFromRef(value: unknown): value is ContractFromRef;
3391
- /**
3392
- * A resolved External Query composition node produced by {@link query} and placed
3393
- * at a `select` property of a contract method body (proposal "External Query"):
3394
- *
3395
- * ```ts
3396
- * Account.query(keys, {
3397
- * accountId: true,
3398
- * billing: query(BillingPlanQueries.get, { accountId: from("$.billingAccountId") }),
3399
- * })
3400
- * ```
3401
- *
3402
- * It carries a faithful reference to the **referenced method spec** (so the
3403
- * serializer can recover the referenced contract's *name* by identity against the
3404
- * contract map, and its derived `resolution` / `cardinality` facts) plus the
3405
- * declarative child-key binding (child key field → a {@link ContractFromRef}).
3406
- * The owning `select` key becomes the composition's `as` property at record time
3407
- * (it is not known to {@link query} itself).
3408
- */
3409
- interface ContractComposeNode {
3410
- readonly [COMPOSE_NODE_BRAND]: true;
3411
- /** The referenced query method spec (`OtherContract.method`). */
3412
- readonly method: QueryMethodSpec<unknown, unknown, unknown>;
3413
- /** The child-key binding: child key field → parent-result `from` path. */
3414
- readonly bind: Readonly<Record<string, ContractFromRef>>;
3415
- }
3416
- /** Runtime guard: is `value` a {@link ContractComposeNode} (a `query(...)` node)? */
3417
- declare function isContractComposeNode(value: unknown): value is ContractComposeNode;
3418
- /**
3419
- * Declare a parent-result source path for an External Query child key binding
3420
- * (proposal "External Query"). Used **only** inside a {@link query} binding:
3421
- *
3422
- * ```ts
3423
- * query(BillingPlanQueries.get, { accountId: from("$.billingAccountId") })
3424
- * ```
3425
- *
3426
- * The path is `$`-rooted (the parent record) and dotted; the bound child-key
3427
- * field is read from that path of each resolved parent record at execution time.
3428
- * The binding is **declarative** — `from` accepts only a literal path string and
3429
- * rejects any other shape, so no arbitrary JS can sneak into a key binding.
3430
- *
3431
- * @param path A `$`-rooted dotted path, e.g. `"$.billingAccountId"` or `"$.a.b"`.
3432
- * @throws if `path` is not a non-empty `$`-rooted dotted field path.
3433
- */
3434
- declare function from(path: string): ContractFromRef;
3435
- /**
3436
- * Reference another query contract's method as an **External Query** composition
3437
- * child, bound to the parent result by a declarative `from` mapping (proposal
3438
- * "Query Composition" / "External Query"). Place the result at a `select`
3439
- * property of a contract method body; the property name becomes the composed
3440
- * value's `as`:
3441
- *
3442
- * ```ts
3443
- * export const AccountAccess = publicQueryModel<AccountKey>()({
3444
- * get: (keys, params) => Account.query(keys, {
3445
- * accountId: true,
3446
- * billing: query(BillingPlanQueries.get, { accountId: from("$.billingAccountId") }),
3447
- * }, params),
3448
- * });
3449
- * ```
3450
- *
3451
- * This is **build-time-resolved, in-process** contract chaining — the relation
3452
- * primitive extended to point at another contract's method instead of a model
3453
- * relation. There is no protocol / transport: the runtime collects every bound
3454
- * key produced by the parent step and resolves the referenced contract **once,
3455
- * batched** (proposal "External Query"). The child MUST be `point` (the parent
3456
- * may yield N records, so a `range` child would be an N+1 fan-out) — that rule is
3457
- * owned by the N+1 checker (#60), which rejects a `range` child at build time.
3458
- *
3459
- * @param method The referenced query method (`OtherContract.method`), a resolved
3460
- * {@link QueryMethodSpec}.
3461
- * @param bind The child-key binding: child key field → a {@link from} path on
3462
- * the parent result. Must be non-empty and every value a `from(...)` ref.
3463
- * @throws if `method` is not a query method spec, or `bind` is empty / carries a
3464
- * non-`from` value.
3465
- */
3466
- declare function query(method: QueryMethodSpec<any, any, any>, bind: Record<string, ContractFromRef>): ContractComposeNode;
3467
- /**
3468
- * The {@link QueryModelContract} that owns a resolved query method spec, or
3469
- * `undefined` if the spec was not produced by {@link publicQueryModel} (so it has
3470
- * no registered owner). Used by the serializer to resolve a composition's
3471
- * referenced contract by identity.
3472
- */
3473
- declare function contractOfMethodSpec(method: QueryMethodSpec<unknown, unknown, unknown>): QueryModelContract<unknown, Record<string, QueryMethodSpec<unknown, unknown, unknown>>> | undefined;
3474
2875
  /**
3475
2876
  * The declarative internal operation a contract method body resolves to — the
3476
2877
  * resolved declaration captured into the IR (the closure itself is discarded).
@@ -3497,7 +2898,7 @@ interface ContractMethodOp<Op extends OperationKind = OperationKind> {
3497
2898
  readonly keys: ContractKeyRef | Readonly<Record<string, unknown>>;
3498
2899
  /**
3499
2900
  * The **explicit contract Key field names** captured from the factory's
3500
- * key-field argument (`publicQueryModel<EmailKey>(['email'])(...)`, issue #71).
2901
+ * key-field argument (`publishQuery<EmailKey>(['email'])(...)`, issue #71).
3501
2902
  * Present only for a **whole-`keys`** op (the {@link keys} slot is the whole
3502
2903
  * {@link ContractKeyRef} sentinel) when the author supplied a field list, or
3503
2904
  * when the model-derived factory form defaulted it to the model's primary-key
@@ -3509,7 +2910,7 @@ interface ContractMethodOp<Op extends OperationKind = OperationKind> {
3509
2910
  *
3510
2911
  * Absent when the body rebuilds a partition key from `keys.<field>` references
3511
2912
  * (the range form already captures field names in the {@link keys} record) or
3512
- * when no explicit list was given to a `publicQueryModel<TKey>()` whole-key form
2913
+ * when no explicit list was given to a `publishQuery<TKey>()` whole-key form
3513
2914
  * (the legacy backward-compatible path, which then defaults to the primary key).
3514
2915
  */
3515
2916
  readonly keyFields?: readonly string[];
@@ -3536,6 +2937,15 @@ interface ContractMethodOp<Op extends OperationKind = OperationKind> {
3536
2937
  * recorded operation, key, or runtime behaviour.
3537
2938
  */
3538
2939
  readonly description?: string;
2940
+ /**
2941
+ * Optional per-param descriptions (issue #154), keyed by param / key-field name,
2942
+ * captured from a descriptor leaf's `param.*({ description })`. Pure documentation
2943
+ * — carried on the op so the serializer ({@link import('../spec/contracts.js')})
2944
+ * can seat each into the flattened definition's `ParamDescriptor.description`, so a
2945
+ * documented contract param surfaces in `operations.json` (`params.field.description`)
2946
+ * exactly as the removed define* param did. Never affects the recorded operation.
2947
+ */
2948
+ readonly paramDescriptions?: Readonly<Record<string, string>>;
3539
2949
  }
3540
2950
  /**
3541
2951
  * A recorded External Query composition edge on a contract method op (#63). The
@@ -3554,33 +2964,6 @@ interface RecordedCompose {
3554
2964
  /** The child-key binding: child key field → parent-result `from` path. */
3555
2965
  readonly bind: Readonly<Record<string, ContractFromRef>>;
3556
2966
  }
3557
- /**
3558
- * The **call signature** a contract method exposes to a caller, narrowed by its
3559
- * decided {@link InputArity} — the type-level half of N+1 rule (a) ("array into a
3560
- * `range` method", #60). Given a method's `inputArity`:
3561
- *
3562
- * - `'single'` (a `range` method) — the key argument is a **single** `TKey` only;
3563
- * passing an array (`readonly TKey[]`) is a **type error** by construction, so a
3564
- * `range` method's array overload does not type-check.
3565
- * - `'either'` (a `point` read / known-key write) — a single key **or** an array
3566
- * (the array form coalesces to one `BatchGetItem` / batched write).
3567
- * - `'array'` — an array only (reserved; not produced by the current resolvers).
3568
- *
3569
- * This mirrors the proposal's "the generated binding's types encode `inputArity`
3570
- * (single → bare argument, array → array argument)". It is the type-level narrowing
3571
- * used by the runtime call surface (`executeQueryMethod`, `src/runtime/contract-runtime.ts`):
3572
- * the executor keys its key-argument type on the named method's **literal** arity
3573
- * `A` (carried on {@link QueryMethodSpec} when the body is tagged with
3574
- * {@link point} / {@link range}), so feeding an array into a `'single'` method is a
3575
- * `tsc` compile error — the **primary** N+1 rule (a) layer, complementing the
3576
- * build-time SSoT check ({@link assertContractN1Safe}) and the runtime backstop.
3577
- *
3578
- * @typeParam A - The decided input arity (a literal `InputArity`).
3579
- * @typeParam TKey - The contract Key.
3580
- * @typeParam TParams - The method's params type.
3581
- * @typeParam TResult - The method's result type.
3582
- */
3583
- type ContractCallSignature<A extends InputArity, TKey, TParams, TResult> = (key: A extends 'single' ? TKey : A extends 'array' ? readonly TKey[] : TKey | readonly TKey[], params: TParams) => TResult;
3584
2967
  /**
3585
2968
  * The resolved IR of one **query** method. Carries the formal method type at the
3586
2969
  * type level (`QueryMethod<TKey, TParams, TResult>`) and, at runtime, the resolved
@@ -3638,8 +3021,29 @@ interface QueryMethodSpec<TKey, TParams, TResult, A extends InputArity = InputAr
3638
3021
  interface CommandMethodSpec<TKey, TParams, TResult> {
3639
3022
  /** @internal Marks this object as a command method spec. */
3640
3023
  readonly __methodKind: 'command';
3641
- /** The resolved internal write operation (closure discarded). */
3642
- readonly op: ContractMethodOp;
3024
+ /**
3025
+ * The resolved internal write operation (closure discarded). Present on every
3026
+ * descriptor- / mutation-authored method (the entity the method is keyed by /
3027
+ * reads back from). **Absent** on an SCP-transaction-authored method (Phase 4
3028
+ * S1, issue #278): a native-syntax multi-write body has no single primary
3029
+ * entity, so it carries a pre-built {@link transaction} instead — the
3030
+ * serializer branches on `transaction` before ever reading `op`.
3031
+ */
3032
+ readonly op?: ContractMethodOp;
3033
+ /**
3034
+ * The pre-built {@link TransactionSpec} of an **SCP-transaction-authored**
3035
+ * command method (Phase 4 S1, issue #278). When a `publishCommand` method
3036
+ * body is a {@link TransactionDefinition} (produced by `defineScpTransaction`
3037
+ * — whose native `$`/`?:`/`&&`/`.map` body the `transformScpTransactionSource`
3038
+ * pass has already lowered onto the recorder seams), it is compiled here by the
3039
+ * SAME {@link import('../spec/transaction.js').buildTransactionSpec} the
3040
+ * standalone `defineScpTransaction` path uses, so the emitted `TransactionSpec`
3041
+ * is **byte-identical** to the equivalent standalone transaction. Present only
3042
+ * for this authoring form; a descriptor / mutation method omits it (and carries
3043
+ * {@link op} instead). The atomicity **mode** flows through {@link mode}
3044
+ * (default `'transaction'`); the full verb/mode consolidation is Phase 4 S6.
3045
+ */
3046
+ readonly transaction?: TransactionSpec;
3643
3047
  /**
3644
3048
  * Derived input arity: a single key maps to one write op; an array maps to a
3645
3049
  * batched write (a transaction / `BatchWriteItem`). Writes accept `'either'`.
@@ -3821,10 +3225,6 @@ interface CommandModelContract<TKey, M extends Record<string, CommandMethodSpec<
3821
3225
  /** @internal Phantom carrier retaining the contract Key type `TKey`. */
3822
3226
  readonly __keyType?: (value: TKey) => void;
3823
3227
  }
3824
- /** Runtime guard: is `value` a {@link QueryModelContract}? */
3825
- declare function isQueryModelContract(value: unknown): value is QueryModelContract<unknown, Record<string, QueryMethodSpec<unknown, unknown, unknown>>>;
3826
- /** Runtime guard: is `value` a {@link CommandModelContract}? */
3827
- declare function isCommandModelContract(value: unknown): value is CommandModelContract<unknown, Record<string, CommandMethodSpec<unknown, unknown, unknown>>>;
3828
3228
  /** The entity type a descriptor {@link ModelRef} (model class / `.asModel()` / thunk) targets. */
3829
3229
  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;
3830
3230
  /** The projected item type for an entity `T` and a select `S` (relation-nest aware). */
@@ -3835,6 +3235,82 @@ type LeafValueOf<L> = L extends Param<infer V> ? V : L;
3835
3235
  type KeyOf<KeyRec> = {
3836
3236
  readonly [F in keyof KeyRec]: LeafValueOf<KeyRec[F]>;
3837
3237
  };
3238
+ /** String-like scalar leaf types that accept a `Param<string>` placeholder (a Date /
3239
+ * binary field crosses the param boundary as a string — `param` has no `datetime`
3240
+ * builder, so it is authored via `param.string()`). */
3241
+ type StringLike = Date | Uint8Array;
3242
+ /** Scalar leaf types (a `param.*` placeholder replaces the leaf, never recursed). */
3243
+ type ScalarLeaf = string | number | boolean | bigint | symbol | StringLike;
3244
+ /** `null` / `undefined` — peeled off an optional leaf before the scalar test. */
3245
+ type Nullish = null | undefined;
3246
+ /**
3247
+ * The relaxed leaf type for a scalar `V`: the concrete value plus a matching
3248
+ * {@link Param} placeholder. A `string` / `number` / literal-union leaf `V` yields
3249
+ * `V | Param<V>` (tuple-wrapped below so a literal union is NOT distributed — the
3250
+ * whole-union `Param<'a' | 'b'>` fits, the #246 fix). A `Date` / `Uint8Array` leaf
3251
+ * additionally admits a `string` / `Param<string>` (its param-boundary form).
3252
+ */
3253
+ type ParamLeaf<V> = [V] extends [StringLike] ? V | string | Param<string> : V | Param<V>;
3254
+ /**
3255
+ * Relaxes a real input type `K` so each scalar leaf additionally accepts a
3256
+ * {@link Param} standing in for that leaf's value type, while field names and value
3257
+ * types stay checked. The scalar test uses a **tuple wrap** so it does NOT distribute
3258
+ * over a literal-union leaf — a `'a' | 'b'` field yields the whole-union placeholder
3259
+ * `Param<'a' | 'b'>` (what `param.literal('a','b')` produces), the #246 fix. `Param`
3260
+ * covariance still admits a single-member subset placeholder, while a value outside
3261
+ * the union / a wider placeholder / a wrong scalar kind stays a type error.
3262
+ */
3263
+ type Parameterize<K> = [Exclude<K, Nullish>] extends [ScalarLeaf] ? [Exclude<K, Nullish>] extends [never] ? K : ParamLeaf<Exclude<K, Nullish>> | Extract<K, Nullish> : K extends object ? {
3264
+ [P in keyof K]: Parameterize<K[P]>;
3265
+ } : K;
3266
+ /** Union of all keys across every member of a (possibly union) `Real` type. */
3267
+ type KnownKeyOf<Real> = Real extends object ? keyof Real : never;
3268
+ /** The value type of key `P` across the members of a (possibly union) `Real`, non-nullable. */
3269
+ type NonNullableMember<Real, P extends PropertyKey> = NonNullable<Real extends object ? (P extends keyof Real ? Real[P] : never) : never>;
3270
+ /**
3271
+ * Call-site excess-property guard for a parameterized structure (issue #41): maps
3272
+ * every key of the inferred `K` that is NOT a known key of `Real` to `never`, so a
3273
+ * stray field forces a compile error. Applied as `input & ExactParam<Real, input>`.
3274
+ */
3275
+ type ExactParam<Real, K> = Real extends object ? {
3276
+ [P in keyof K]: P extends KnownKeyOf<Real> ? K[P] extends Param<unknown> ? K[P] : K[P] extends object ? K[P] & ExactParam<NonNullableMember<Real, P>, K[P]> : K[P] : never;
3277
+ } : K;
3278
+ /**
3279
+ * The model-aware constraint for a write descriptor's `input` given the descriptor
3280
+ * `D` — `Parameterize<Partial<EntityInput<Entity>>>` + `ExactParam` when `D` targets
3281
+ * a model via `create` / `update` / `remove`, else the inferred `input` unchanged. A
3282
+ * `Partial` because a write descriptor's `input` may set only a subset of fields (an
3283
+ * update patch / a create with defaulted fields).
3284
+ */
3285
+ type WriteInputConstraint<D> = D extends {
3286
+ readonly input: infer I;
3287
+ } ? Parameterize<Partial<EntityInput<WriteDescriptorEntity<D>>>> & ExactParam<Partial<EntityInput<WriteDescriptorEntity<D>>>, I> : unknown;
3288
+ /** The entity a write descriptor targets, from its `create` / `update` / `remove` / `upsert` model ref. */
3289
+ type WriteDescriptorEntity<D> = D extends {
3290
+ readonly create: infer M;
3291
+ } ? EntityOfRef<M> : D extends {
3292
+ readonly update: infer M;
3293
+ } ? EntityOfRef<M> : D extends {
3294
+ readonly remove: infer M;
3295
+ } ? EntityOfRef<M> : D extends {
3296
+ readonly upsert: infer M;
3297
+ } ? EntityOfRef<M> : unknown;
3298
+ /**
3299
+ * The per-method validation overlay for {@link publishCommand} (issue #246): for
3300
+ * each **write descriptor** method it re-asserts `input` against the target model
3301
+ * (via {@link WriteInputConstraint}); non-write-descriptor methods (a bare
3302
+ * `mutation(...)` plan, a `PublicComposeDescriptor`, an SCP `TransactionDefinition`)
3303
+ * carry no model-checkable `input` here and pass through. Intersected into the
3304
+ * `publishCommand` generic constraint so the model narrowing bites at the call site
3305
+ * without widening the inferred `M`.
3306
+ */
3307
+ type ValidateWriteDescriptors<M> = {
3308
+ readonly [K in keyof M]: M[K] extends PublicWriteDescriptor ? Omit<M[K], 'input'> & (M[K] extends {
3309
+ readonly input: unknown;
3310
+ } ? {
3311
+ readonly input: WriteInputConstraint<M[K]>;
3312
+ } : Record<never, never>) : M[K];
3313
+ };
3838
3314
  /** The params a descriptor method accepts: the union of its `key` + `input` + `condition` leaf value types. */
3839
3315
  type DescriptorParamsOf<D> = (D extends {
3840
3316
  readonly key: infer K;
@@ -3921,7 +3397,7 @@ type WriteDescriptorKeyOf<D> = D extends {
3921
3397
  } ? KeyOf<K> : unknown;
3922
3398
  /**
3923
3399
  * The query-method map accepted by the #101 **direct** form
3924
- * (`publicQueryModel({ get: descriptor, … })`): each value is a
3400
+ * (`publishQuery({ get: descriptor, … })`): each value is a
3925
3401
  * {@link PublicReadDescriptor}. The contract Key and per-method result are inferred
3926
3402
  * from each descriptor's `key` / `select`.
3927
3403
  */
@@ -3932,12 +3408,13 @@ type ResolvedReadDescriptors<M extends ReadDescriptorMap> = QueryModelContract<M
3932
3408
  }>;
3933
3409
  /**
3934
3410
  * The command-method map accepted by the #101 **direct** form
3935
- * (`publicCommandModel({ create: descriptor, … })`): each value is a
3411
+ * (`publishCommand({ create: descriptor, … })`): each value is a
3936
3412
  * {@link PublicWriteDescriptor}, a {@link PublicComposeDescriptor} (a `mutation()`
3937
- * wrapped with `input` / `result` / `mode`), or a bare composite `mutation()`
3938
- * {@link CommandPlan}.
3413
+ * wrapped with `input` / `result` / `mode`), a bare composite `mutation()`
3414
+ * {@link CommandPlan}, or an SCP native-syntax `defineScpTransaction(...)`
3415
+ * multi-write body ({@link TransactionDefinition}, Phase 4 S1 / issue #278).
3939
3416
  */
3940
- type WriteDescriptorMap = Record<string, PublicWriteDescriptor | PublicComposeDescriptor | CommandPlan>;
3417
+ type WriteDescriptorMap = Record<string, PublicWriteDescriptor | PublicComposeDescriptor | CommandPlan | TransactionDefinition>;
3941
3418
  /** Resolve a write-descriptor map to its {@link CommandModelContract}. */
3942
3419
  type ResolvedWriteDescriptors<M extends WriteDescriptorMap> = CommandModelContract<M[keyof M] extends infer D ? WriteDescriptorKeyOf<D> : unknown, {
3943
3420
  readonly [K in keyof M]: CommandMethodSpec<WriteDescriptorKeyOf<M[K]>, DescriptorParamsOf<M[K]>, WriteDescriptorResultOf<M[K]>>;
@@ -3946,7 +3423,7 @@ type ResolvedWriteDescriptors<M extends WriteDescriptorMap> = CommandModelContra
3946
3423
  * Create a **QueryModel** from a **descriptor map** (issue #101, the only form):
3947
3424
  *
3948
3425
  * ```ts
3949
- * export const UserQueries = publicQueryModel({
3426
+ * export const UserQueries = publishQuery({
3950
3427
  * get: { query: User, key: { userId: param.string() }, select: { name: true } },
3951
3428
  * list: { list: GroupMembership, key: { groupId: param.string() }, select: { role: true } },
3952
3429
  * });
@@ -3955,7 +3432,7 @@ type ResolvedWriteDescriptors<M extends WriteDescriptorMap> = CommandModelContra
3955
3432
  * The single argument is the method map itself; the contract Key and each method's
3956
3433
  * result type are **inferred** from the descriptors' `key` / `select` — there is no
3957
3434
  * generic key parameter, no curried factory call, and no explicit Key-field list
3958
- * (the legacy `publicQueryModel<TKey>(['email'])` / model-derived overloads were
3435
+ * (the legacy `publishQuery<TKey>(['email'])` / model-derived overloads were
3959
3436
  * withdrawn in #101). A GSI-keyed point read is expressed by its descriptor `key`
3960
3437
  * (e.g. `key: { email: param.string() }`); the build resolves the access pattern
3961
3438
  * from the model metadata, exactly as the curried key-field list once did. Each
@@ -3963,12 +3440,12 @@ type ResolvedWriteDescriptors<M extends WriteDescriptorMap> = CommandModelContra
3963
3440
  * a `select` is the existing query projection (relation-nest aware), and may carry
3964
3441
  * External Query `query(...)` nodes.
3965
3442
  */
3966
- declare function publicQueryModel<const M extends ReadDescriptorMap>(methods: M): ResolvedReadDescriptors<M>;
3443
+ declare function publishQuery<const M extends ReadDescriptorMap>(methods: M): ResolvedReadDescriptors<M>;
3967
3444
  /**
3968
3445
  * Create a **CommandModel** from a **descriptor map** (issue #101, the only form):
3969
3446
  *
3970
3447
  * ```ts
3971
- * export const MembershipCommands = publicCommandModel({
3448
+ * export const MembershipCommands = publishCommand({
3972
3449
  * create: { create: GroupMembership, key: { groupId: param.string(), userId: param.string() },
3973
3450
  * input: { role: param.string() }, result: { select: { role: true } } },
3974
3451
  * addMany: { create: GroupMembership, key: { … }, input: { … }, mode: 'parallel' },
@@ -3979,53 +3456,46 @@ declare function publicQueryModel<const M extends ReadDescriptorMap>(methods: M)
3979
3456
  * The single argument is the method map itself; the contract Key and each method's
3980
3457
  * read-back result are **inferred** from the descriptors' `key` / `result.select` —
3981
3458
  * there is no generic key parameter, no curried factory call, and no explicit
3982
- * Key-field list (the legacy `publicCommandModel<TKey>()` / model-derived overloads
3459
+ * Key-field list (the legacy `publishCommand<TKey>()` / model-derived overloads
3983
3460
  * were withdrawn in #101). A descriptor's `mode` declares the per-call execution
3984
3461
  * mode (`'transaction'` default | `'parallel'`); `result` presence drives whether
3985
3462
  * the method reads back and returns the written entity.
3986
3463
  */
3987
- declare function publicCommandModel<const M extends WriteDescriptorMap>(methods: M): ResolvedWriteDescriptors<M>;
3988
- declare const PLANNED_COMMAND_BRAND: unique symbol;
3989
- /**
3990
- * The input param shape a {@link command} declares: a record of named
3991
- * {@link Param} placeholders (`param.string()` / `param.number()` /
3992
- * `param.literal(...)`). These are the **external** params of the public Command
3993
- * IF the method's caller-supplied input. The mutation fragment binds them by
3994
- * name into the written entity (`$.<field>`), so each input field name must match
3995
- * a fragment `$.field` reference / a model field the fragment writes.
3996
- */
3997
- type CommandInputShape = Readonly<Record<string, Param<unknown>>>;
3998
- /**
3999
- * The return projection a {@link command} declares (proposal §3: "return = read
4000
- * projection"): a JSON-safe boolean field map applied to the written entity as a
4001
- * **consistent read-back** after the write commits. Omit for a fire-and-forget
4002
- * command (no projected item is returned).
4003
- */
4004
- type CommandSelectShape = Readonly<Record<string, boolean>>;
4005
- /**
4006
- * A **planned command method** (#83): the branded result of
4007
- * `command({ input, select }).plan(mutation)`. It carries the captured fragment
4008
- * IR (the {@link CommandPlan}), the public input param shape, and the return
4009
- * `select`. {@link buildCommandContract} detects the brand and routes it to the
4010
- * single-fragment mutation compiler (NOT through #64's closure path).
4011
- *
4012
- * The external surface is **params in / result out only** — the internal mutation
4013
- * document is never exposed.
4014
- *
4015
- * @typeParam TInput - The input param shape (`{ field: Param<…> }`).
4016
- * @typeParam TSelect - The return projection (`{ field: boolean }`), or `undefined`.
4017
- */
4018
- interface PlannedCommandMethod<TInput extends CommandInputShape = CommandInputShape, TSelect extends CommandSelectShape | undefined = CommandSelectShape | undefined> {
4019
- readonly [PLANNED_COMMAND_BRAND]: true;
4020
- /** The captured mutation IR (the fragment list). */
4021
- readonly plan: CommandPlan;
4022
- /** The public input param shape. */
4023
- readonly input: TInput;
4024
- /** The return projection (consistent read-back), or `undefined`. */
4025
- readonly select: TSelect;
3464
+ declare function publishCommand<const M extends WriteDescriptorMap & ValidateWriteDescriptors<M>>(methods: M): ResolvedWriteDescriptors<M>;
3465
+ /** Options accepted by the {@link mutate} authoring alias. */
3466
+ interface MutateAuthoringOptions {
3467
+ /**
3468
+ * The **atomicity mode** for this write body. This is the ONLY home of
3469
+ * atomicity (Phase 4 S3.5): `'transaction'` composes the body's writes into one
3470
+ * atomic `TransactWriteItems`; `'parallel'` runs them non-atomically. Omitted
3471
+ * non-atomic graphddb never defaults atomicity, so a multi-write body with no
3472
+ * `mode` is a definition-time error (see the fail-closed guard).
3473
+ */
3474
+ readonly mode?: MutateMode;
4026
3475
  }
4027
- /** Runtime guard: is `value` a {@link PlannedCommandMethod} (#83)? */
4028
- declare function isPlannedCommandMethod(value: unknown): value is PlannedCommandMethod;
3476
+ /**
3477
+ * The **`mutate` authoring alias** (Phase 4 S3.5) the authoring twin of the
3478
+ * runtime `DDBModel.mutate` / `mutate({ mode })`, usable as a `publishCommand`
3479
+ * method value. It wraps an existing recorder / SCP-lowered write body (a
3480
+ * {@link TransactionDefinition} produced by `defineScpTransaction` /
3481
+ * `defineTransaction`, or a {@link CommandPlan} produced by `mutation(...)`) and
3482
+ * records the authoring `{ mode }` onto the produced IR — the SOLE place
3483
+ * atomicity is decided.
3484
+ *
3485
+ * It adds **no new lowering path**: the transform still recognizes only
3486
+ * `tx.put/update/delete/forEach`, and the body flows through the SAME
3487
+ * `buildTransactionSpec` path as the standalone form. The name + `{ mode }`
3488
+ * option deliberately mirror the runtime `mutate`; the runtime/authoring fold is
3489
+ * Phase 4 S6 (this only makes the authoring alias exist and carry `mode`).
3490
+ *
3491
+ * - `mutate(txDef, { mode })` → the same {@link TransactionDefinition} with
3492
+ * `mode` stamped (undefined ⇒ non-atomic; a multi-write body then throws in
3493
+ * `publishCommand`'s SCP-body branch — declare a mode).
3494
+ * - `mutate(plan, { mode })` → a {@link PublicComposeDescriptor} (`{ command,
3495
+ * mode }`), the closure-free composite form.
3496
+ */
3497
+ declare function mutate(body: TransactionDefinition, options?: MutateAuthoringOptions): TransactionDefinition;
3498
+ declare function mutate(body: CommandPlan, options?: MutateAuthoringOptions): PublicComposeDescriptor;
4029
3499
  /** The per-call execution mode of a command method (#101): replaces `batch`. */
4030
3500
  type CommandMode = 'transaction' | 'parallel';
4031
3501
  /** A public **read** descriptor (issue #101): `{ query | list: Model, key, select, options? }`. */
@@ -4043,11 +3513,21 @@ interface PublicReadDescriptor {
4043
3513
  */
4044
3514
  readonly description?: string;
4045
3515
  }
4046
- /** A public **write** descriptor (issue #101): `{ create | update | remove: Model, key, input?, condition?, result?, mode? }`. */
3516
+ /** A public **write** descriptor (issue #101): `{ create | update | remove | upsert: Model, key, input?, condition?, result?, mode? }`. */
4047
3517
  interface PublicWriteDescriptor {
4048
3518
  readonly create?: ModelRef;
4049
3519
  readonly update?: ModelRef;
4050
3520
  readonly remove?: ModelRef;
3521
+ /**
3522
+ * `upsert` — a `PutItem` with **no** `attribute_not_exists(PK)` guard (an
3523
+ * unconditional overwrite; the semantics of the removed `definePut`). Unlike
3524
+ * `create` (always guarded against clobbering an existing row), an `upsert` writes
3525
+ * whether or not the row exists. An author-supplied `condition` is honored VERBATIM
3526
+ * — it is NOT ANDed with a `notExists` guard (that absence is what distinguishes an
3527
+ * upsert from a create). The compiled base op is a `Put` whose only condition is the
3528
+ * author's (or none), i.e. the ORIGINAL `definePut` wire shape.
3529
+ */
3530
+ readonly upsert?: ModelRef;
4051
3531
  readonly key: Readonly<Record<string, unknown>>;
4052
3532
  readonly input?: Readonly<Record<string, unknown>>;
4053
3533
  readonly condition?: Readonly<Record<string, unknown>> | RawCondition<any, any>;
@@ -4203,963 +3683,36 @@ interface MaintainItem {
4203
3683
  * {@link destinationRowKey} write the same row — the material #125/#126 uses to
4204
3684
  * enforce 論点2 = b ("1 mutation × 1 target row = 1 effect"; multiple is a
4205
3685
  * reject). Built deterministically (sorted), so it is a directly comparable
4206
- * string.
4207
- */
4208
- readonly destinationRowKey: string;
4209
- /** The A-defined maintenance IR this relation lowers to. */
4210
- readonly effect: MaintainEffect;
4211
- }
4212
- /**
4213
- * The maintenance graph: the trigger → effects index plus the validation surface.
4214
- *
4215
- * `byTrigger` is the core index: a `MaintainTrigger` (`"Post.created"`) maps to
4216
- * every {@link MaintainItem} that fires on it, in a stable order (owner entity,
4217
- * then relation property). Empty (no entry) for a trigger nothing maintains on.
4218
- */
4219
- interface MaintenanceGraph {
4220
- /** Every maintenance item, flattened, in deterministic order. */
4221
- readonly items: readonly MaintainItem[];
4222
- /** The core index: trigger → the effects it fires. */
4223
- readonly byTrigger: ReadonlyMap<MaintainTrigger, readonly MaintainItem[]>;
4224
- /** Look up the effects fired by a trigger (empty array if none). */
4225
- effectsFor(trigger: MaintainTrigger): readonly MaintainItem[];
4226
- /**
4227
- * Items that, under the SAME trigger, write the SAME target row — grouped by
4228
- * `"<trigger><targetRowKey>"`. Only groups with **more than one** item are
4229
- * present, so an empty map means no trigger has a multi-maintainer collision.
4230
- * This is the detection material for 論点2 = b; #124 surfaces it, #125/#126
4231
- * decide the reject. (Build-time we do NOT reject here — a model may legitimately
4232
- * declare overlapping shapes that a later phase reconciles; #124's contract is
4233
- * to make the collision *detectable*.)
4234
- */
4235
- readonly multiMaintainerTargets: ReadonlyMap<string, readonly MaintainItem[]>;
4236
- }
4237
- /**
4238
- * Build the {@link MaintenanceGraph} from every registered model's relation-side
4239
- * maintenance declarations (`MetadataRegistry.getAll()`), validating each as it is
4240
- * collected (round-trip, unresolved trigger) and the whole graph (cycle) before
4241
- * returning. Throws a loud, actionable error on any invalid declaration.
4242
- *
4243
- * @param registry The models to scan. Defaults to `MetadataRegistry.getAll()`;
4244
- * an explicit map is accepted for tests / scoped builds.
4245
- */
4246
- declare function buildMaintenanceGraph(registry?: ReadonlyMap<Function, EntityMetadata>, views?: readonly ViewDefinition[]): MaintenanceGraph;
4247
-
4248
- /**
4249
- * Serializable static operation specs and manifest (issue #42, Python-bridge
4250
- * Phase 0b).
4251
- *
4252
- * Everything in this module is **JSON-serializable** by construction (only
4253
- * strings / numbers / booleans / arrays / plain objects — no functions, classes,
4254
- * `Date`, `Set`, or `undefined`-valued fields are emitted). These types mirror
4255
- * the `graphddb_manifest.json` / `graphddb_operations.json` shapes sketched in
4256
- * `docs/python-bridge.md`. The generator (#43) writes them to disk; the
4257
- * Python runtime (#44+) interprets them.
4258
- *
4259
- * ## Templates and placeholders
4260
- *
4261
- * Key-condition / range / item / change *values* are **template strings** with
4262
- * two placeholder forms:
4263
- *
4264
- * - `{paramName}` — bound from the caller-supplied params at execution time.
4265
- * - `{result.sourceField}` — bound from a field of the **prior** operation's
4266
- * result item(s), used to chain relation operations.
4267
- *
4268
- * A value with no `{...}` is a literal (e.g. a fixed sort-key discriminator
4269
- * `PROFILE`).
4270
- */
4271
-
4272
- /**
4273
- * The schema version emitted in every manifest / operations document.
4274
- *
4275
- * `1.1` (issue #208, B案): the operation IR gained the **list fan-out binding
4276
- * form** ({@link SourceListSpec} on a `BatchGetItem` relation op), lifting the
4277
- * #197 `refs` loud-reject. The op vocabulary (the physical DynamoDB API surface)
4278
- * is unchanged — only the dataflow (source→key) binding grammar grew. Purely
4279
- * additive: every `1.0` document is a valid `1.1` document, and a document that
4280
- * uses no `refs` relation serializes byte-identically apart from this version.
4281
- */
4282
- declare const SPEC_VERSION: "1.1";
4283
- /** Field type as carried in the manifest (derived from the DynamoDB type). */
4284
- type ManifestFieldType = 'string' | 'number' | 'boolean' | 'binary' | 'stringSet' | 'numberSet' | 'list' | 'map';
4285
- interface ManifestField {
4286
- readonly type: ManifestFieldType;
4287
- /**
4288
- * Semantic format for `string` fields that carry a serialized temporal value.
4289
- * `datetime` → ISO 8601 instant, `date` → `YYYY-MM-DD`. Absent for plain
4290
- * fields. The Python runtime uses this to restore `datetime` on hydration,
4291
- * mirroring the TS hydrator's `format`-driven `Date` reconstruction.
4292
- */
4293
- readonly format?: 'datetime' | 'date';
4294
- /**
4295
- * Optional human-readable description of the field (issue #154), from a field
4296
- * decorator option (`@string({ description })`). Pure documentation — absent
4297
- * unless declared, so a field with no description serializes byte-identically
4298
- * to the pre-#154 manifest. Surfaces as a field comment in the generated Python.
4299
- */
4300
- readonly description?: string;
4301
- }
4302
- /** A key (PK or GSI) template descriptor in the manifest. */
4303
- interface ManifestKey {
4304
- /** Input field names the key is composed from (in declaration order). */
4305
- readonly inputFields: readonly string[];
4306
- /** Template for the partition-key value, e.g. `EMAIL#{email}`. */
4307
- readonly pkTemplate: string;
4308
- /** Template for the sort-key value, or `null` when the key has no sort key. */
4309
- readonly skTemplate: string | null;
4310
- }
4311
- interface ManifestGsi extends ManifestKey {
4312
- readonly indexName: string;
4313
- readonly unique: boolean;
4314
- /**
4315
- * Optional human-readable description of the index (issue #166, follow-up of #154),
4316
- * from `gsi(name, key, { description })`. Pure documentation — absent unless declared,
4317
- * so a GSI with no description serializes byte-identically to the pre-#166 manifest.
4318
- * The Python codegen surfaces it as the docstring of a generated query method that
4319
- * reads through this index.
4320
- */
4321
- readonly description?: string;
4322
- }
4323
- interface ManifestRelation {
4324
- readonly type: 'hasMany' | 'hasOne' | 'belongsTo' | 'refs';
4325
- /** Target entity (model class) name. */
4326
- readonly target: string;
4327
- /** target field → source field (on this entity). */
4328
- readonly keyBinding: Readonly<Record<string, string>>;
4329
- /**
4330
- * List-valued source descriptor for a `refs` relation (issue #197). Present IFF
4331
- * `type === 'refs'`: `from` is the parent LIST attribute holding the inline
4332
- * reference elements (e.g. `'tagRefs'`), `key` the field read off each element
4333
- * (e.g. `'tagId'`). Absent for every scalar-keyed relation (byte-identical to
4334
- * the pre-#197 manifest). Carried so a manifest consumer can reconstruct where a
4335
- * `refs` relation's BatchGet keys come from.
4336
- */
4337
- readonly refs?: Readonly<{
4338
- from: string;
4339
- key: string;
4340
- }>;
4341
- /**
4342
- * Optional human-readable description of the relation (issue #166, follow-up of
4343
- * #154), from `@hasMany/@belongsTo/@hasOne(..., { description })`. Pure documentation
4344
- * — absent unless declared, so a relation with no description serializes
4345
- * byte-identically to the pre-#166 manifest. The Python codegen surfaces it as a
4346
- * trailing `# …` comment on the relation's field in the generated result type.
4347
- */
4348
- readonly description?: string;
4349
- }
4350
- interface ManifestEntity {
4351
- /** Declared (logical) table name. */
4352
- readonly table: string;
4353
- /** Physical table name (after `TableMapping` resolution). */
4354
- readonly physicalName: string;
4355
- /** PK prefix, e.g. `USER#`. */
4356
- readonly prefix: string;
4357
- readonly fields: Readonly<Record<string, ManifestField>>;
4358
- /** Primary key, or `null` when the entity has none. */
4359
- readonly key: ManifestKey | null;
4360
- readonly gsis: readonly ManifestGsi[];
4361
- readonly relations: Readonly<Record<string, ManifestRelation>>;
4362
- /**
4363
- * Optional human-readable description of the entity (issue #154), from
4364
- * `@model({ description })`. Pure documentation — absent unless declared, so an
4365
- * entity with no description serializes byte-identically to the pre-#154
4366
- * manifest. Surfaces as the generated Python class docstring.
4367
- */
4368
- readonly description?: string;
4369
- /**
4370
- * The physical attribute name of the model's DynamoDB **Time-To-Live** field,
4371
- * when one is declared via `@ttl` (issue #172, Epic #167 — CFn generator C4).
4372
- * Unlike the maintenance/stream signals (a maintenance-IR concern kept OFF the
4373
- * manifest), TTL is a **physical schema** fact, and the CloudFormation emitter
4374
- * consumes the manifest — so the TTL attribute name flows here and the emitter
4375
- * renders `TimeToLiveSpecification { AttributeName: <this>, Enabled: true }`. The
4376
- * attribute is deliberately NOT added to the table's key `AttributeDefinitions`
4377
- * (that list holds ONLY key/index attributes; `TimeToLiveSpecification` legitimately
4378
- * names a non-key attribute). **Absent** when the model declares no `@ttl`, so a
4379
- * TTL-free model serializes byte-identically to the pre-#172 manifest (backward
4380
- * compatible). DynamoDB permits exactly one TTL attribute per physical table; the
4381
- * single-per-table rule is enforced at manifest build.
4382
- */
4383
- readonly ttlAttribute?: string;
4384
- }
4385
- interface ManifestTable {
4386
- readonly physicalName: string;
4387
- }
4388
- interface Manifest {
4389
- readonly version: typeof SPEC_VERSION;
4390
- readonly tables: Readonly<Record<string, ManifestTable>>;
4391
- readonly entities: Readonly<Record<string, ManifestEntity>>;
4392
- }
4393
- interface ParamSpec {
4394
- readonly type: ParamKind;
4395
- readonly required: boolean;
4396
- /** Allowed literal values for `literal` params; omitted otherwise. */
4397
- readonly literals?: readonly (string | number)[];
4398
- /**
4399
- * For an `array` param (transaction `forEach` source), the per-element field
4400
- * descriptors (field name → element param spec). Omitted for scalar params.
4401
- */
4402
- readonly element?: Readonly<Record<string, ParamSpec>>;
4403
- /**
4404
- * Optional human-readable description of the parameter (issue #154), from a
4405
- * `param.*({ description })` placeholder. Pure documentation — absent unless
4406
- * declared, so a param with no description serializes byte-identically to the
4407
- * pre-#154 spec. The Python runtime never reads it for execution.
4408
- */
4409
- readonly description?: string;
4410
- }
4411
- /** A `begins_with` range condition on a sort key (templated value). */
4412
- interface RangeConditionSpec {
4413
- readonly operator: 'begins_with';
4414
- readonly key: string;
4415
- /** Template value, e.g. `GROUP#` or `GROUP#{result.groupId}`. */
4416
- readonly value: string;
4417
- }
4418
- /** Server-side declarative filter, carried verbatim for the runtime to compile. */
4419
- interface FilterSpec {
4420
- /** The declarative {@link FilterInput} tree (JSON-safe). */
4421
- readonly declarative: unknown;
4422
- }
4423
- type ReadOperationType = 'Query' | 'GetItem' | 'BatchGetItem';
4424
- /**
4425
- * A single static read operation. Relation chains are expressed as multiple
4426
- * operations whose `resultPath` / templated key conditions wire them together.
4427
- */
4428
- interface OperationSpec {
4429
- readonly type: ReadOperationType;
4430
- readonly tableName: string;
4431
- readonly indexName?: string;
4432
- /**
4433
- * Key condition: attribute name → template value. For a `BatchGetItem` this is
4434
- * the per-key shape (one key per resolved source item at runtime).
4435
- */
4436
- readonly keyCondition: Readonly<Record<string, string>>;
4437
- readonly rangeCondition?: RangeConditionSpec;
4438
- /** Projected fields (entity field names). */
4439
- readonly projection: readonly string[];
4440
- readonly limit?: number;
4441
- readonly filter?: FilterSpec;
4442
- /**
4443
- * Where the result of this operation is placed in the assembled result.
4444
- * `$` is the root; `$.groups.items` a relation connection's items, etc.
4445
- */
4446
- readonly resultPath: string;
4447
- /**
4448
- * For relation operations: the source field on the prior result whose value(s)
4449
- * drive this operation's `{result.*}` placeholders. Absent on the root op.
4450
- */
4451
- readonly sourceField?: string;
4452
- /**
4453
- * The **list fan-out binding form** (issue #208, B案 — the shared-IR
4454
- * generalization that lifted the #197 `refs` loud-reject). Present only on a
4455
- * `BatchGetItem` relation operation that resolves a `refs` relation: instead of
4456
- * reading ONE scalar `parent[sourceField]`, the runtime reads the parent **list
4457
- * attribute** {@link SourceListSpec.from} and binds `{result.<sourceField>}`
4458
- * once per element — `element[key]` for an object element, the element itself
4459
- * for a bare scalar — skipping null/undefined refs. All element keys across all
4460
- * parents fan into ONE deduped `BatchGetItem` (the existing chunk/retry
4461
- * machinery), and the operation's `resultPath` (`…<prop>.items`) receives a
4462
- * connection `{ items, cursor: null }` whose items are the resolved child
4463
- * bodies in **first-seen element order**, deduped, with missing bodies dropped
4464
- * — byte-matching the TS in-process `fetchRefsList` semantics. Absent on every
4465
- * scalar-bound relation op (a pre-#208 document is byte-identical).
4466
- */
4467
- readonly sourceList?: SourceListSpec;
4468
- }
4469
- /**
4470
- * The list-valued source descriptor of a fan-out `BatchGetItem` operation (issue
4471
- * #208 B案; the operation-IR mirror of the manifest's `ManifestRelation.refs`).
4472
- */
4473
- interface SourceListSpec {
4474
- /** The parent LIST attribute holding the inline reference elements (e.g. `tagRefs`). */
4475
- readonly from: string;
4476
- /**
4477
- * The field read off each element (e.g. `tagId`). Always equals the operation's
4478
- * `sourceField` (the `{result.<sourceField>}` token name); carried explicitly so
4479
- * the op is self-describing.
4480
- */
4481
- readonly key: string;
4482
- /**
4483
- * `true` when the parent list attribute was NOT selected by the query and was
4484
- * projected onto the parent operation **solely** to drive this fan-out. The
4485
- * runtime must then delete `from` from every parent node after ALL relation
4486
- * operations are applied — mirroring the TS hydrator, which projects implicit
4487
- * relation-source fields but strips them from the assembled result. Absent when
4488
- * the query's select projects the attribute itself (it stays in the result).
4489
- */
4490
- readonly implicit?: boolean;
4491
- }
4492
- /**
4493
- * The **execution plan** for a multi-operation read (issue #70a). It is the
4494
- * Single Source of Truth for *how* a query's {@link OperationSpec}[] is staged:
4495
- * which operations are independent (may run concurrently) and which must wait for
4496
- * a prior operation's result. Every runtime that honors it produces identical
4497
- * effects with identical concurrency discipline — the staging is **serialized,
4498
- * not re-derived** (cf. the contract decided-facts discipline).
4499
- *
4500
- * - `groups` is an ordered list of **stages**; each stage is a list of indices
4501
- * into the query's `operations[]`. Operations **within** a stage are mutually
4502
- * independent and may be issued concurrently; stages run **in order** because a
4503
- * later stage reads a `{result.*}` value produced by an earlier one. Stage 0 is
4504
- * always `[0]` (the root). Every operation index appears in exactly one stage,
4505
- * and the stages are a topological layering of the result-dependency graph.
4506
- * - `concurrency` is the declared in-flight bound a runtime applies **within**
4507
- * each stage (the shared {@link RELATION_TRAVERSAL_CONCURRENCY} = 16): at most
4508
- * this many operations of a stage are issued at once.
4509
- *
4510
- * ## Derivation (see `buildReadOperations` / `deriveExecutionPlan`)
4511
- *
4512
- * The planner emits `operations[]` in a deterministic pre-order: the root at
4513
- * index 0, then each relation child immediately followed by its own descendants.
4514
- * A child's key templates reference `{result.<sourceField>}` of the **operation
4515
- * whose `resultPath` is the child's parent path** — that producer is the child's
4516
- * sole dependency. The stage of an operation is therefore `1 + stage(parent)`:
4517
- * the root is stage 0; sibling relations that share a parent path land in the
4518
- * **same** stage (no inter-`{result.*}` dependency between siblings, so they are
4519
- * independent and concurrency-eligible); a grandchild that reads its parent's
4520
- * `{result.*}` lands one stage later. This mirrors exactly the level-by-level
4521
- * fan-out the TS relation runtime performs at runtime (`resolveRelations` issues
4522
- * sibling relations together under the same bound) — #70a only *records* it.
4523
- *
4524
- * Absent on a single-operation spec and on specs produced before #70a; a consumer
4525
- * without a plan falls back to one-operation-per-stage **sequential** execution
4526
- * (the pre-#70 behavior), so an absent plan never changes results.
4527
- */
4528
- interface ExecutionPlanSpec {
4529
- /** Ordered stages; each stage is indices into `operations[]` (stage 0 = `[0]`). */
4530
- readonly groups: readonly (readonly number[])[];
4531
- /** The declared in-flight bound applied within each stage (16). */
4532
- readonly concurrency: number;
4533
- }
4534
- /** A query (read) definition's full execution spec. */
4535
- interface QuerySpec {
4536
- readonly params: Readonly<Record<string, ParamSpec>>;
4537
- readonly operations: readonly OperationSpec[];
4538
- /**
4539
- * Result cardinality of the **root** (`$`) operation, derived from the
4540
- * definition kind: `'one'` for `defineQuery` (a single entity object, with any
4541
- * relations attached directly), `'many'` for `defineList` (a `{ items, cursor }`
4542
- * connection). The relation runtime (#45) uses this to shape the root result:
4543
- * a `'one'` Query root takes the first matched item rather than returning a
4544
- * connection. Absent in specs produced before #45; consumers should treat an
4545
- * absent value as `'many'` for a `Query`/`BatchGetItem` root and `'one'` for a
4546
- * `GetItem` root (the pre-#45 behavior).
4547
- */
4548
- readonly cardinality?: 'one' | 'many';
4549
- /**
4550
- * The staged execution plan (issue #70a): which {@link operations} are
4551
- * independent (concurrency-eligible) vs. result-dependent. Present only when the
4552
- * query has more than one operation (a relation chain); a single-operation read
4553
- * needs no plan. See {@link ExecutionPlanSpec} for the derivation and the
4554
- * backward-compatible (plan-absent → sequential) fallback.
4555
- */
4556
- readonly executionPlan?: ExecutionPlanSpec;
4557
- /**
4558
- * Optional human-readable description of the query (issue #154), from
4559
- * `defineQuery(..., { description })`. Pure documentation — absent unless
4560
- * declared, so a query with no description serializes byte-identically to the
4561
- * pre-#154 spec. Surfaces as the generated Python repository-method docstring.
4562
- */
4563
- readonly description?: string;
4564
- }
4565
- type WriteOperationType = 'PutItem' | 'UpdateItem' | 'DeleteItem';
4566
- /**
4567
- * A condition expression on a write, as a JSON-serializable spec (issues #46,
4568
- * #81). The supported subset:
4569
- *
4570
- * - `{ kind: 'notExists' }` → `attribute_not_exists(PK)` (legacy whole-row guard).
4571
- * - `{ kind: 'attributeExists'; field }` → `attribute_exists(<field>)` — the named
4572
- * attribute (any field, incl. PK/SK) must be present. The foundation for
4573
- * referential-integrity derivation.
4574
- * - `{ kind: 'attributeNotExists'; field }` → `attribute_not_exists(<field>)` — the
4575
- * named attribute must be absent (field-level uniqueness / first-write guard).
4576
- * - `{ kind: 'equals'; fields }` → `#f = :v AND …` field equality (each value a
4577
- * `{param}` / literal template).
4578
- * - `{ kind: 'expr'; declarative }` → the full declarative operator tree (issue
4579
- * #114-A): comparison (`gt`/`ge`/`lt`/`le`/`ne`), `between`, `in`,
4580
- * `begins_with`/`contains`/`notContains`, `attributeType`, and the logical
4581
- * `and`/`or`/`not` groups, mirroring the read-side {@link FilterSpec}. The tree
4582
- * is JSON-safe: each leaf value is either a native literal (string / number /
4583
- * boolean) or a {@link ConditionParamLeaf} (`{ $param }`) marking a caller
4584
- * param bound at execution time. The runtime resolves the param leaves against
4585
- * the caller params, then compiles the tree to a DynamoDB `ConditionExpression`
4586
- * with the SAME mechanics the filter compiler uses (so TS and Python emit an
4587
- * identical expression / semantics).
4588
- */
4589
- type ConditionSpec = {
4590
- readonly kind: 'notExists';
4591
- } | {
4592
- readonly kind: 'attributeExists';
4593
- readonly field: string;
4594
- } | {
4595
- readonly kind: 'attributeNotExists';
4596
- readonly field: string;
4597
- } | {
4598
- readonly kind: 'equals';
4599
- readonly fields: Readonly<Record<string, string>>;
4600
- } | {
4601
- readonly kind: 'expr';
4602
- readonly declarative: unknown;
4603
- } | {
4604
- /**
4605
- * A raw DynamoDB condition produced by the `cond` escape hatch (issue
4606
- * #114-B), for write conditions that the declarative operator subset
4607
- * cannot express. It is **pre-compiled and deterministic**: the
4608
- * `expression` is a finished DynamoDB `ConditionExpression` whose name
4609
- * placeholders are stable `#cr_<field>` aliases (reused per distinct
4610
- * column) and whose value placeholders are sequential `:cr0`, `:cr1`, …
4611
- * (assigned in template order), so the serialized golden is stable. The
4612
- * names map binds each `#cr_<field>` alias to its entity field; the values
4613
- * map binds each `:crN` alias to either a native literal (an embedded
4614
- * `cond` value — in-process the concrete value) or a {@link ConditionParamLeaf}
4615
- * (`{ $param }`) marker bound from a caller param at execution time (the
4616
- * public-contract slot). The runtime substitutes the param leaves, then
4617
- * attaches `expression` / `names` / serialized `values` to the write —
4618
- * TS and Python build the identical DynamoDB expression.
4619
- */
4620
- readonly kind: 'raw';
4621
- readonly expression: string;
4622
- readonly names: Readonly<Record<string, string>>;
4623
- readonly values: Readonly<Record<string, unknown>>;
4624
- };
4625
- interface CommandSpec {
4626
- readonly type: WriteOperationType;
4627
- readonly tableName: string;
4628
- readonly entity: string;
4629
- readonly params: Readonly<Record<string, ParamSpec>>;
4630
- /**
4631
- * Key condition for `UpdateItem` / `DeleteItem` (attribute name → template).
4632
- * Absent for `PutItem` (the whole item is built from `item`).
4633
- */
4634
- readonly keyCondition?: Readonly<Record<string, string>>;
4635
- /** The full item template for `PutItem` (field name → template / literal). */
4636
- readonly item?: Readonly<Record<string, string>>;
4637
- /** Field-level changes for `UpdateItem` (field name → template / literal). */
4638
- readonly changes?: Readonly<Record<string, string>>;
4639
- /** Optional write condition (subset). */
4640
- readonly condition?: ConditionSpec;
4641
- /**
4642
- * Optional human-readable description of the command (issue #154), from
4643
- * `definePut`/`defineUpdate`/`defineDelete(..., { description })`. Pure
4644
- * documentation — absent unless declared, so a command with no description
4645
- * serializes byte-identically to the pre-#154 spec. Surfaces as the generated
4646
- * Python repository-method docstring.
4647
- */
4648
- readonly description?: string;
4649
- }
4650
- /**
4651
- * A declarative `when` guard on a transaction item (issue #46). Compares a
4652
- * templated left-hand value against a templated right-hand value; the item is
4653
- * skipped when the comparison does not hold. Both values are template strings
4654
- * (`{param}` / `{item.<field>}` / literal), resolved by the runtime before the
4655
- * comparison. The runtime compares the resolved **string** values.
4656
- */
4657
- interface WhenSpec {
4658
- readonly op: 'eq' | 'ne';
4659
- /** Templated left-hand value (typically an `{item.<field>}` or `{param}`). */
4660
- readonly left: string;
4661
- /** Templated right-hand value (literal or another reference). */
4662
- readonly right: string;
4663
- }
4664
- /**
4665
- * The kind of a transaction item. The three write kinds (`Put` / `Update` /
4666
- * `Delete`) mutate an item; `ConditionCheck` (issue #81) is a **read-only
4667
- * assertion** on another item — it asserts a {@link ConditionSpec} holds for the
4668
- * keyed row without modifying it, and its failure cancels the **whole**
4669
- * `TransactWriteItems` atomically. It is the foundation for referential-integrity
4670
- * derivation (proposal: `requires <Entity> exists` → a `ConditionCheck` with
4671
- * `attribute_exists`).
4672
- */
4673
- type TransactionItemType = 'Put' | 'Update' | 'Delete' | 'ConditionCheck';
4674
- /**
4675
- * A value leaf of a transaction `item` / `changes` / `add` map (issue #245).
4676
- *
4677
- * - a **string** carries a template: a `{param}` / `{item.field}` placeholder, a
4678
- * composite (`PREFIX#{param}`), or a plain string literal — resolved by the
4679
- * runtimes' template machinery (a whole-placeholder string keeps the bound
4680
- * param's *type*, a composite / plain string resolves to a string);
4681
- * - a **number** / **boolean** carries a *typed literal* verbatim, so a numeric
4682
- * literal in a `put` item / `update` changes (`version: 0`) survives to DynamoDB
4683
- * as an `N` / `BOOL` instead of being stringified to an `S` (the pre-#245 bug).
4684
- *
4685
- * A `Date` literal is serialized to its ISO-8601 **string** at build time (a
4686
- * `@datetime` / `@date` field is stored as `S`), so it stays a string leaf.
4687
- */
4688
- type TransactionValueLeaf = string | number | boolean;
4689
- /**
4690
- * A single templated item in a transaction. When `forEach` is present, the item
4691
- * is expanded **once per element** of the named array param, with each element's
4692
- * fields bound to the item's `{item.<field>}` placeholders.
4693
- *
4694
- * Field presence by `type`:
4695
- *
4696
- * | type | item | keyCondition | changes | add | condition |
4697
- * |----------------|------|--------------|---------|-----|--------------------------|
4698
- * | `Put` | ✓ | — | — | — | optional |
4699
- * | `Update` | — | ✓ | ✓ / — | ✓ / — | optional |
4700
- * | `Delete` | — | ✓ | — | — | optional |
4701
- * | `ConditionCheck` | — | ✓ | — | — | **required** (the assert) |
4702
- *
4703
- * An `Update` carries `changes` (a `SET` of named fields) and/or `add` (an atomic
4704
- * `ADD` of named numeric fields, issue #85) — at least one of the two. The two are
4705
- * distinct DynamoDB update actions: `SET #f = :v` **overwrites**, while
4706
- * `ADD #f :delta` **atomically increments** without a read (concurrency-safe), so
4707
- * a derived counter (`User.postCount += 1`) MUST be an `add`, never a `changes`
4708
- * `SET` (which would clobber a concurrent increment).
4709
- */
4710
- interface TransactionItemSpec {
4711
- readonly type: TransactionItemType;
4712
- readonly tableName: string;
4713
- readonly entity: string;
4714
- /**
4715
- * Put: the full item template (field → template / typed literal). A string
4716
- * value is a template ({@link TransactionValueLeaf}); a `number` / `boolean` is
4717
- * a typed literal carried verbatim (issue #245).
4718
- */
4719
- readonly item?: Readonly<Record<string, TransactionValueLeaf>>;
4720
- /** Update / Delete / ConditionCheck: the key template (attribute → template). */
4721
- readonly keyCondition?: Readonly<Record<string, string>>;
4722
- /**
4723
- * Update: field-level `SET` change templates (overwrite). A string value is a
4724
- * template; a `number` / `boolean` is a typed literal carried verbatim (#245).
4725
- */
4726
- readonly changes?: Readonly<Record<string, TransactionValueLeaf>>;
4727
- /**
4728
- * Update: field-level atomic `ADD` deltas (issue #85, derived counters). Each
4729
- * value is a numeric template (`{param}` / a literal number string) or a typed
4730
- * numeric literal (issue #245); the runtime applies `ADD #field :delta`, an
4731
- * atomic increment that needs no prior read and is safe under concurrency. A
4732
- * negative delta decrements (e.g. `-1` on a remove).
4733
- */
4734
- readonly add?: Readonly<Record<string, TransactionValueLeaf>>;
4735
- /**
4736
- * The write / assertion condition (subset). Optional on `Put` / `Update` /
4737
- * `Delete`; **required** on a `ConditionCheck` (the assertion it makes).
4738
- */
4739
- readonly condition?: ConditionSpec;
4740
- /** Optional declarative guard; the item is skipped when it does not hold. */
4741
- readonly when?: WhenSpec;
4742
- /** When present, expand this item once per element of the named array param. */
4743
- readonly forEach?: {
4744
- readonly source: string;
4745
- };
4746
- /**
4747
- * Marks a **raw marker-row** write (issue #86 uniqueness guard): the row is NOT a
4748
- * modeled entity, so its primary key is carried **literally** rather than derived
4749
- * from manifest metadata. When `true`:
4750
- *
4751
- * - a `Put` writes its `item` record **verbatim** as the stored item — the `item`
4752
- * already carries the synthetic `PK` / `SK` templates (`UNIQUE#…`), so the
4753
- * runtimes do NOT prepend a model prefix or compute GSI attributes;
4754
- * - a `Delete` uses its `keyCondition` (the `PK` / `SK` templates) as the row Key
4755
- * verbatim.
4756
- *
4757
- * The companion {@link entity} is the {@link MARKER_ROW_ENTITY} sentinel (there is
4758
- * no manifest entity to resolve). Absent / `false` on every modeled write (a base
4759
- * entity / edge / counter / ConditionCheck item), so those paths are unchanged.
4760
- */
4761
- readonly literalKey?: boolean;
4762
- /**
4763
- * A **relation-side maintenance write** (Epic #118, issue #129 — the JSON-SSoT /
4764
- * Python mirror of the TS in-process `renderMaintainWriteItem`). Present ONLY on an
4765
- * `Update` item that materializes a maintained access path: a projected
4766
- * {@link DerivedMaintainWrite} of a just-written source row into a SEPARATE owner
4767
- * (destination) row, in the SAME atomic transaction as the source write.
4768
- *
4769
- * The item's {@link keyCondition} carries the owner row's key templates (bound from
4770
- * the source payload — `{sourceInputField}`), so the same key derivation / collapse
4771
- * signature the other `Update` items use applies unchanged. This `maintain` payload
4772
- * carries the projection transform IR (`identity` / `preview`) the runtimes apply
4773
- * IDENTICALLY when rendering the `SET … = :v` (snapshot) / `list_append(…)`
4774
- * (collection) `UpdateExpression`, so the maintained row is byte-consistent across
4775
- * TS and Python. The `changes` / `add` fields are NOT used when this is present.
4776
- */
4777
- readonly maintain?: MaintainItemSpec;
4778
- }
4779
- /**
4780
- * The transform op applied to one projected maintenance attribute (Epic #118).
4781
- * Mirrors `ProjectionTransformOp` from `src/define/entity-writes.ts`: `identity`
4782
- * copies the source value through unchanged; `preview` keeps the first `n`
4783
- * characters of (the string form of) the source value. The runtimes apply this
4784
- * IDENTICALLY so the maintained row is byte-consistent.
4785
- */
4786
- type MaintainProjectionOp = 'identity' | 'preview';
4787
- /**
4788
- * One projected maintenance attribute: the source value is read from the mutation
4789
- * input field {@link inputField} (payload 同梱 — the just-written source row image),
4790
- * then run through {@link op} with {@link args} (e.g. `preview`'s length bound).
4791
- */
4792
- interface MaintainProjectionEntry {
4793
- /** The transform op applied to the source value (`identity` / `preview`). */
4794
- readonly op: MaintainProjectionOp;
4795
- /** The op's positional arguments (`[n]` for `preview`; empty for `identity`). */
4796
- readonly args: readonly unknown[];
4797
- /** The mutation-input field the source value is read from (`{inputField}`). */
4798
- readonly inputField: string;
4799
- }
4800
- /**
4801
- * The bounded-collection options a `collection` maintenance write carries. Phase 1
4802
- * is **append-only**: `maxItems` / `orderBy` are recorded for the future async trim
4803
- * (#130) but NOT applied synchronously — a single `UpdateExpression` cannot
4804
- * read-modify-write a bounded/ordered list.
4805
- */
4806
- interface MaintainCollectionSpec {
4807
- /** The target attribute that holds the maintained collection. */
4808
- readonly field: string;
4809
- /** Cap on the collection size (recorded for #130; not trimmed in Phase 1). */
4810
- readonly maxItems?: number;
4811
- /** The mutation-input field the items are ordered by (recorded for #130). */
4812
- readonly orderBy?: string;
4813
- /** The direction `orderBy` sorts by, from the relation's `read.order` (recorded for #130). */
4814
- readonly orderDir?: 'ASC' | 'DESC';
4815
- }
4816
- /**
4817
- * The maintenance payload an `Update` {@link TransactionItemSpec} carries when it
4818
- * materializes a maintained access path (Epic #118 / #129). A `snapshot` mirrors
4819
- * the projection onto the owner row via `SET`; a `collection` appends the projection
4820
- * as an item into the bounded list named by {@link collection} via `list_append`.
4821
- */
4822
- interface MaintainItemSpec {
4823
- /**
4824
- * Whether the write mirrors a single row (`snapshot`), appends a collection item
4825
- * (`collection`), or applies a scalar `ADD` counter (`counter`, Epic #118 / #141).
4826
- */
4827
- readonly kind: 'snapshot' | 'collection' | 'counter';
4828
- /** The relation property / `@aggregate` field that declared this maintenance (documentary). */
4829
- readonly relationProperty: string;
4830
- /** The trigger (`<sourceLogicalName>.<event>`) that fired this maintenance. */
4831
- readonly trigger: string;
4832
- /** target attribute → its projection transform (source field + op + args). Empty for `counter`. */
4833
- readonly projection: Readonly<Record<string, MaintainProjectionEntry>>;
4834
- /** The bounded-collection options; present only for `kind: 'collection'`. */
4835
- readonly collection?: MaintainCollectionSpec;
4836
- /**
4837
- * The scalar counter `ADD`; present only for `kind: 'counter'` (#141). The runtimes
4838
- * render `ADD #attr :delta` — an atomic, concurrency-safe increment that MERGES with
4839
- * a same-row counter ADD (#92), the maintenance-pipeline analogue of the
4840
- * self-lifecycle derived counter's `add` slot. `delta` is a numeric template string
4841
- * (the compile-time constant `+1` created / `-1` removed).
4842
- */
4843
- readonly counter?: MaintainCounterSpec;
4844
- }
4845
- /**
4846
- * The scalar counter a `counter` maintenance write applies (Epic #118 / #141): an
4847
- * atomic `ADD #attribute :delta`. `delta` is a literal numeric template string (the
4848
- * compile-time constant the trigger fixed — `"1"` created / `"-1"` removed); the
4849
- * runtimes serialize it as a number. Only `count()` is realized synchronously
4850
- * (`max(field)` is rejected for the synchronous path — #130 / Phase 2).
4851
- */
4852
- interface MaintainCounterSpec {
4853
- /** The target attribute the counter increments (e.g. `postCount`). */
4854
- readonly attribute: string;
4855
- /** The signed `ADD` delta as a numeric template string (`"1"` / `"-1"`). */
4856
- readonly delta: string;
4857
- }
4858
- /** A declarative transaction definition's full execution spec. */
4859
- interface TransactionSpec {
4860
- readonly params: Readonly<Record<string, ParamSpec>>;
4861
- readonly items: readonly TransactionItemSpec[];
4862
- /**
4863
- * A static upper bound on the expanded item count when computable (no `forEach`
4864
- * present → the exact count; with `forEach` it is left absent because the
4865
- * element count is only known at execution — the runtime then enforces the
4866
- * DynamoDB ≤25 limit after expansion).
4867
- */
4868
- readonly maxItems?: number;
4869
- }
4870
- /**
4871
- * Whether a contract is a public **read** (`'query'`) or **write** (`'command'`)
4872
- * interface. Mirrors {@link QueryModelContract} / {@link CommandModelContract}'s
4873
- * `kind` discriminant from the Contract IR (#58).
4874
- */
4875
- type ContractKind = 'query' | 'command';
4876
- /**
4877
- * The resolution kind of a contract query method, **derived** by the planner from
4878
- * the internal op (never hand-written; see proposal "N+1 Safety"):
4879
- *
4880
- * - `'point'` — target keys are known (unique-key `query` / `GetItem`); a key
4881
- * array coalesces to one `BatchGetItem`.
4882
- * - `'range'` — target key set is unknown (partition `list` / `Query`); one
4883
- * request per partition key, so it is only ever safe for a single key.
4884
- */
4885
- type ContractResolution = 'point' | 'range';
4886
- /**
4887
- * What input arity a contract method accepts, **derived** from its resolution:
4888
- *
4889
- * - `'either'` — a single key **or** an array (a `point` read / a known-key
4890
- * write; the array form is one `BatchGetItem` / batched write).
4891
- * - `'single'` — a single key only (a `range` read; an array would be an N+1
4892
- * fan-out and is rejected by construction).
4893
- * - `'array'` — an array only (reserved; not produced by the current resolvers).
4894
- */
4895
- type ContractInputArity = 'single' | 'array' | 'either';
4896
- /**
4897
- * The per-key result cardinality of a contract query method, **derived** from the
4898
- * internal op kind (proposal "Cardinality matrix"):
4899
- *
4900
- * - `'one'` — at most one item per key (a `point` read).
4901
- * - `'many'` — a connection (`{ items, cursor }`) per key (a `range` read).
4902
- *
4903
- * This is the per-key shape; the input arity (single vs. array) then decides
4904
- * whether the overall result is bare or keyed.
4905
- */
4906
- type ContractCardinality = 'one' | 'many';
4907
- /**
4908
- * The category of a contract command method's declared result, part of the
4909
- * contract (it surfaces in OpenAPI and every binding; proposal "Return values"):
4910
- *
4911
- * - `'void'` — fire-and-forget write (no body).
4912
- * - `'result'` — an outcome / status object (e.g. `{ ok, version }`).
4913
- * - `'entity'` — the updated entity (the post-write projection).
4914
- */
4915
- type ContractCommandResult = 'void' | 'result' | 'entity';
4916
- /** The Key of a contract: the field names that compose its access / join key. */
4917
- interface ContractKeySpec {
4918
- /** The key field names, in declaration order (e.g. `["articleId"]`). */
4919
- readonly fields: readonly string[];
4920
- }
4921
- /**
4922
- * One External Query (Query Composition) binding on a contract query method
4923
- * (proposal "External Query"). It is a **build-time, in-process** read dependency
4924
- * on another contract referenced by name in the same SSoT — there is no protocol
4925
- * or transport. The runtime collects every parent key produced at this level and
4926
- * resolves the referenced contract **once, batched**, for all of them.
4927
- *
4928
- * A composed child MUST be `point` (proposal "N+1 Safety"): the parent step may
4929
- * yield N records, so a `range` child would be an N+1 fan-out.
4930
- */
4931
- interface ComposeSpec {
4932
- /** The result property the composed value is attached to (e.g. `billing`). */
4933
- readonly as: string;
4934
- /** The referenced contract name (a symbol in the same SSoT). */
4935
- readonly contract: string;
4936
- /** The referenced method on that contract (e.g. `get`). */
4937
- readonly method: string;
4938
- /**
4939
- * An optional **logical** owner label (the bounded context that owns the
4940
- * referenced contract) — not a network address. Omitted when unlabeled.
4941
- */
4942
- readonly context?: string;
4943
- /**
4944
- * The child-key binding: child key field → a `from` source path on the parent
4945
- * result, e.g. `{ accountId: "$.billingAccountId" }`.
4946
- */
4947
- readonly bind: Readonly<Record<string, string>>;
4948
- /**
4949
- * The composed child's resolution. It MUST be `'point'` in a valid contract — a
4950
- * composed child coalesces to one `BatchGetItem` across all parent keys, so a
4951
- * `range` child (a partition `Query` that cannot be batched across the parent's
4952
- * keys) is an N+1 fan-out. The N+1 static checker (#60,
4953
- * {@link assertContractN1Safe}) rejects a `'range'` value on the build path; the
4954
- * field is widened to {@link ContractResolution} so the offending node can be
4955
- * carried into the assembled {@link ContractSpec} for the checker to inspect and
4956
- * reject with a clear message, rather than being silently unrepresentable.
4957
- */
4958
- readonly resolution: ContractResolution;
4959
- /** The composed child's per-key cardinality (`'one'` for a valid `point`). */
4960
- readonly cardinality: ContractCardinality;
4961
- }
4962
- /**
4963
- * The **composition execution plan** for one query contract method (issue #70a),
4964
- * the contract-layer mirror of {@link ExecutionPlanSpec}. It records how a method's
4965
- * External Query compositions (`compose[]`) are staged relative to the parent read
4966
- * and to one another, and the batching discipline each composed child obeys:
4967
- *
4968
- * - `stages` is an ordered list whose entries are indices into the method's
4969
- * `compose[]`. The parent read (the referenced `operation`) is the implicit
4970
- * stage before all of these. Every compose child binds its child key purely from
4971
- * the **parent** result (`from('$.…')` paths), so sibling compositions are
4972
- * mutually independent: they form a single stage `[0, 1, …]` and may be resolved
4973
- * concurrently. (A composed child cannot, today, bind from another composed
4974
- * child — the #63 DSL only exposes parent `from` paths — so there is never a
4975
- * cross-composition dependency; the field is a list of stages so the shape can
4976
- * carry one if a future primitive introduces it.)
4977
- * - `concurrency` is the declared in-flight bound applied **within** a stage (the
4978
- * shared {@link RELATION_TRAVERSAL_CONCURRENCY} = 16).
4979
- * - `batchChunkSize` is the per-request key cap for a composed child read (100,
4980
- * the DynamoDB `BatchGetItem` limit): a composition resolves **one batched
4981
- * call** across all parent records (chunked at this size), never an N+1 fan-out
4982
- * (the composed child is `point`, enforced by the #60 N+1 checker).
4983
- *
4984
- * Emitted only when the method declares at least one composition. A method without
4985
- * compositions carries no plan, and a runtime without one resolves any
4986
- * compositions sequentially (the pre-#70 behavior) — so an absent plan never
4987
- * changes results.
4988
- */
4989
- interface CompositionPlanSpec {
4990
- /** Ordered stages of indices into `compose[]` (independent siblings share a stage). */
4991
- readonly stages: readonly (readonly number[])[];
4992
- /** The declared in-flight bound applied within each composition stage (16). */
4993
- readonly concurrency: number;
4994
- /** Per-request key cap for a composed child's batched read (100, BatchGetItem limit). */
4995
- readonly batchChunkSize: number;
4996
- }
4997
- /**
4998
- * The serialized spec of one **query** contract method. Carries the decided facts
4999
- * (`resolution` / `inputArity` / `cardinality`) the planner derived in #58, a
5000
- * reference into `queries` by name (the internal op), and any External Query
5001
- * compositions. The decided facts are **serialized, not re-derived** — every
5002
- * runtime honors them.
5003
- */
5004
- interface QueryContractMethodSpec {
5005
- /** Derived: `'point'` (unique-key query) or `'range'` (partition list). */
5006
- readonly resolution: ContractResolution;
5007
- /** Derived: `'either'` for `point`, `'single'` for `range`. */
5008
- readonly inputArity: ContractInputArity;
5009
- /** Derived per-key result shape: `'one'` (point) or `'many'` (range). */
5010
- readonly cardinality: ContractCardinality;
5011
- /** The referenced read op name in `queries` (e.g. `ArticleById__get`). */
5012
- readonly operation: string;
5013
- /** External Query compositions on this method; omitted when there are none. */
5014
- readonly compose?: readonly ComposeSpec[];
5015
- /**
5016
- * The composition staging + batch plan (issue #70a); present only when `compose`
5017
- * is. See {@link CompositionPlanSpec}: independent compositions form one
5018
- * concurrency-eligible stage after the parent read, each resolved as one batched
5019
- * call (chunk ≤100). Absent → resolve compositions sequentially (pre-#70).
5020
- */
5021
- readonly compositionPlan?: CompositionPlanSpec;
5022
- /**
5023
- * Optional human-readable description of the read use case (issue #154), from the
5024
- * descriptor's `description`. Pure documentation — absent unless declared, so a
5025
- * method with no description serializes byte-identically to the pre-#154 spec.
5026
- * The same string appears in the TS contract IR and (via the SSoT) in any
5027
- * generated binding, so TS↔Python carry an identical description.
5028
- */
5029
- readonly description?: string;
5030
- }
5031
- /**
5032
- * How a contract command method resolves a single key vs. an array of keys to the
5033
- * underlying write surface (proposal "Consistency with the existing write
5034
- * surface"). A single key → one write op (or one transaction); an array → a
5035
- * batched write — a `TransactWriteItems` when atomicity / per-item conditions are
5036
- * required, or a `BatchWriteItem` when neither is.
5037
- */
5038
- type CommandResolutionTarget =
5039
- /** One write op, referenced by name in `commands`. */
5040
- {
5041
- readonly mode: 'op';
5042
- readonly operation: string;
5043
- }
5044
- /** One transaction, referenced by name in `transactions`. */
5045
- | {
5046
- readonly mode: 'transaction';
5047
- readonly transaction: string;
5048
- }
5049
- /**
5050
- * A non-atomic **parallel** fan-out over the per-key write op (#101). The op
5051
- * MAY carry a condition (guarded create / conditioned update). Unconditioned
5052
- * `put`/`delete` ops coalesce into a `BatchWriteItem` (with `UnprocessedItems`
5053
- * retry); conditioned/update ops issue individual conditional writes
5054
- * concurrently. Per-op success/failure is reported (partial success); a
5055
- * per-op failure never aborts the others. References the per-key write op in
5056
- * `commands`.
5057
- */
5058
- | {
5059
- readonly mode: 'parallel';
5060
- readonly operation: string;
5061
- };
5062
- /**
5063
- * The serialized spec of one **command** contract method. Symmetric to
5064
- * {@link QueryContractMethodSpec}: it carries the declared `result` type and the
5065
- * `single` / `batch` resolution targets into the existing write specs.
5066
- */
5067
- interface CommandContractMethodSpec {
5068
- /** Derived input arity: writes accept `'either'` (single key or key array). */
5069
- readonly inputArity: ContractInputArity;
5070
- /** The declared result type (`void` | a `Result` | the updated entity). */
5071
- readonly result: ContractCommandResult;
5072
- /** How a single key resolves (one op / one transaction). */
5073
- readonly single: CommandResolutionTarget;
5074
- /**
5075
- * How an array of keys resolves (a transaction / `BatchWriteItem`); omitted
5076
- * when the contract does not declare a batched write form.
5077
- */
5078
- readonly batch?: CommandResolutionTarget;
5079
- /**
5080
- * The **return projection** of a `mutation`-derived command method (issue #83;
5081
- * proposal §3, "return selection as a read projection"). A JSON-safe boolean
5082
- * field map: after the write commits, the runtime issues a **`GetItem` with
5083
- * `ConsistentRead`** of the written entity's primary key and applies this
5084
- * projection via the existing read-projection machinery, returning the projected
5085
- * item. This is the **uniform** return mechanism for both a single-op command
5086
- * and a future transaction (a `TransactWriteItems` cannot return item images),
5087
- * so TS and Python return an **identical** projected item.
5088
- *
5089
- * The consistent read-back is issued against the read op named
5090
- * `<Contract>__<method>__readback` (a synthesized {@link QuerySpec}: a `GetItem`
5091
- * on the written entity's primary key projecting these fields). Present only on
5092
- * a `.plan(mutation)` method; a hand-written #64 command omits it (its
5093
- * {@link result} stays `'void'` / `'entity'` and it returns no projected item).
5094
- */
5095
- readonly returnSelection?: Readonly<Record<string, boolean>>;
5096
- /**
5097
- * Optional human-readable description of the write use case (issue #154), from
5098
- * the descriptor's `description`. Pure documentation — absent unless declared, so
5099
- * a method with no description serializes byte-identically to the pre-#154 spec.
5100
- */
5101
- readonly description?: string;
5102
- }
5103
- /**
5104
- * A serialized contract — a keyed public read (`'query'`) or write (`'command'`)
5105
- * interface holding one or more named methods. Each method references an existing
5106
- * operation spec by name and adds the decided contract facts. The existing
5107
- * operation-spec shapes are unchanged, so the current runtime keeps working.
5108
- */
5109
- interface ContractSpec {
5110
- /** Whether this is a read (`'query'`) or write (`'command'`) contract. */
5111
- readonly kind: ContractKind;
5112
- /** The contract Key (the access pattern / join / batch key). */
5113
- readonly key: ContractKeySpec;
5114
- /**
5115
- * The named methods (use cases over the same Key). A query contract's methods
5116
- * are {@link QueryContractMethodSpec}; a command contract's are
5117
- * {@link CommandContractMethodSpec}. The map is keyed by contract `kind`.
3686
+ * string.
5118
3687
  */
5119
- readonly methods: Readonly<Record<string, QueryContractMethodSpec>> | Readonly<Record<string, CommandContractMethodSpec>>;
3688
+ readonly destinationRowKey: string;
3689
+ /** The A-defined maintenance IR this relation lowers to. */
3690
+ readonly effect: MaintainEffect;
5120
3691
  }
5121
3692
  /**
5122
- * One bounded context's membership (issue #59, "context ownership"). Lists the
5123
- * Models and Contracts that belong to the same context, so the future boundary
5124
- * lint (#61) can tell **own** from **foreign**: a contract may resolve its own
5125
- * context's Models directly, but may only reach another context through that
5126
- * context's published Contract (direct use of a foreign Model is a build error).
5127
- *
5128
- * This issue only **serializes / emits** this declaration; it does not enforce
5129
- * it. Both lists are entity / contract *names* (matching `manifest.entities` keys
5130
- * and `contracts` keys respectively).
5131
- */
5132
- interface ContextSpec {
5133
- /** Model (entity) names owned by this context (sorted; manifest entity keys). */
5134
- readonly models: readonly string[];
5135
- /** Contract names owned by this context (sorted; `contracts` map keys). */
5136
- readonly contracts: readonly string[];
5137
- }
5138
- interface OperationsDocument {
5139
- readonly version: typeof SPEC_VERSION;
5140
- readonly queries: Readonly<Record<string, QuerySpec>>;
5141
- readonly commands: Readonly<Record<string, CommandSpec>>;
5142
- /** Declarative transactions (issue #46). Absent in pre-#46 specs. */
5143
- readonly transactions?: Readonly<Record<string, TransactionSpec>>;
5144
- /**
5145
- * The CQRS Contract layer (issue #59): contract name → {@link ContractSpec}.
5146
- * Layered **on top of** the existing `queries` / `commands` / `transactions`
5147
- * (each method references one of them by name). **Absent** when the input
5148
- * defines no contracts — so a contract-free input produces a byte-identical
5149
- * pre-#59 operations document (backward compatibility).
5150
- */
5151
- readonly contracts?: Readonly<Record<string, ContractSpec>>;
3693
+ * The maintenance graph: the trigger effects index plus the validation surface.
3694
+ *
3695
+ * `byTrigger` is the core index: a `MaintainTrigger` (`"Post.created"`) maps to
3696
+ * every {@link MaintainItem} that fires on it, in a stable order (owner entity,
3697
+ * then relation property). Empty (no entry) for a trigger nothing maintains on.
3698
+ */
3699
+ interface MaintenanceGraph {
3700
+ /** Every maintenance item, flattened, in deterministic order. */
3701
+ readonly items: readonly MaintainItem[];
3702
+ /** The core index: trigger → the effects it fires. */
3703
+ readonly byTrigger: ReadonlyMap<MaintainTrigger, readonly MaintainItem[]>;
3704
+ /** Look up the effects fired by a trigger (empty array if none). */
3705
+ effectsFor(trigger: MaintainTrigger): readonly MaintainItem[];
5152
3706
  /**
5153
- * Context-ownership declarations (issue #59): context name
5154
- * {@link ContextSpec}. Used by the boundary lint (#61) to tell own from
5155
- * foreign. **Absent** when no contexts are declared (backward compatibility).
3707
+ * Items that, under the SAME trigger, write the SAME target row — grouped by
3708
+ * `"<trigger><targetRowKey>"`. Only groups with **more than one** item are
3709
+ * present, so an empty map means no trigger has a multi-maintainer collision.
3710
+ * This is the detection material for 論点2 = b; #124 surfaces it, #125/#126
3711
+ * decide the reject. (Build-time we do NOT reject here — a model may legitimately
3712
+ * declare overlapping shapes that a later phase reconciles; #124's contract is
3713
+ * to make the collision *detectable*.)
5156
3714
  */
5157
- readonly contexts?: Readonly<Record<string, ContextSpec>>;
5158
- }
5159
- /** The full `{ manifest, operations }` bundle produced by the static planner. */
5160
- interface BridgeBundle {
5161
- readonly manifest: Manifest;
5162
- readonly operations: OperationsDocument;
3715
+ readonly multiMaintainerTargets: ReadonlyMap<string, readonly MaintainItem[]>;
5163
3716
  }
5164
3717
 
5165
3718
  /**
@@ -5789,225 +4342,6 @@ interface CompiledMutationPlan {
5789
4342
  */
5790
4343
  declare function compileMutationPlan(plan: CommandPlan): CompiledMutationPlan;
5791
4344
 
5792
- /**
5793
- * Single-service contract **Runtime** — execution of CQRS query-contract methods
5794
- * (issue #62, Epic #57; spec `docs/cqrs-contract.md`, "Query Method
5795
- * — Common Interface" + "Cardinality matrix"). READS only; command/write
5796
- * execution is #64 and single-contract composition / External Query is #63 — both
5797
- * out of scope here.
5798
- *
5799
- * The Runtime is the **execution engine**: given a resolved query contract (a
5800
- * {@link QueryModelContract} from #58, carrying per-method `resolution` /
5801
- * `inputArity` and a resolved {@link ContractMethodOp}) it executes one method
5802
- * for a single key **or** an array of keys and returns the correct result shape
5803
- * from the proposal's cardinality matrix:
5804
- *
5805
- * | Input | `resolution` | Result shape | Notation |
5806
- * | -------------- | ------------ | ------------------------------------- | ---------------- |
5807
- * | `key` (single) | `point` | `Item \| null` | `key1 : result1` |
5808
- * | `key` (single) | `range` | `Connection` (`{ items, cursor }`) | `key1 : resultM` |
5809
- * | `keys[]` (N) | `point` | keyed `Map<keyId, Item \| null>` | `keyN : resultN` |
5810
- * | `keys[]` (N) | `range` | **unreachable for a single contract** | `keyN : resultN×M` |
5811
- *
5812
- * ## On `keyN : resultN×M` (range + array) — unreachable here, by design
5813
- *
5814
- * The fourth matrix cell — a keyed map of connections — is **not reachable for a
5815
- * single contract** at the direct-call surface. The proposal's N+1 Safety rule
5816
- * fixes a `range` method's `inputArity` to `'single'` ("A `range` method is
5817
- * necessarily `'single'`"; "feeding an array of keys into a range method … is an
5818
- * N+1 fan-out and is rejected at build time, with no opt-in escape hatch"). #58's
5819
- * {@link inputArityOf} derives exactly that, and #60's N+1 checker rejects any
5820
- * contract that violates it before it reaches the SSoT. So a range method only
5821
- * ever accepts one key, and this runtime **rejects** an array fed into a `range`
5822
- * method ({@link executeQueryMethod}) rather than fanning out. `keyN : resultN×M`
5823
- * arises only under cross-contract composition (a `point` parent referencing a
5824
- * child whose per-key result is a connection) — that is #63's External Query
5825
- * territory, explicitly out of scope for #62. We therefore implement the **three
5826
- * reachable shapes** and guard the fourth with a clear error, rather than
5827
- * silently fanning out a forbidden N+1.
5828
- *
5829
- * ## Reuse, not reinvention
5830
- *
5831
- * Execution delegates to the proven machinery:
5832
- *
5833
- * - `point` single → {@link executeQuery} (GetItem / unique Query).
5834
- * - `point` array → {@link executeKeyedBatchGet} below, which reuses the
5835
- * `batch-retry` primitives (chunk ≤100, dedup, UnprocessedKeys retry) and
5836
- * builds the **keyed** map (BatchGet neither preserves order nor returns absent
5837
- * keys, so missing keys are filled with explicit `null`).
5838
- * - `range` single → {@link executeListInternal} for a `{ items, cursor }`
5839
- * connection, with the cursor wrapped in a per-key envelope
5840
- * ({@link encodePerKeyCursor}) so it carries the key it belongs to.
5841
- *
5842
- * The result-shape typing (single → bare, array → keyed) is expressed as call
5843
- * overloads on {@link executeQueryMethod}.
5844
- */
5845
-
5846
- /**
5847
- * The **structural** shape of a resolved query contract the runtime executes.
5848
- * It depends only on the facts the executor reads — the `query` discriminant and
5849
- * the per-method `resolution` and internal `op` — and is deliberately
5850
- * **independent of the phantom Key / params / result types** carried by
5851
- * {@link QueryModelContract}. Those phantom types are invariant /
5852
- * contravariant on the full interface, so a concrete `QueryModelContract<{…}>`
5853
- * is not assignable to `QueryModelContract<unknown, …>`; this minimal structural
5854
- * type sidesteps that variance while accepting every real contract (a
5855
- * {@link QueryModelContract} is structurally assignable to it). The runtime never
5856
- * needs the method param / result types — it returns the dynamic
5857
- * cardinality-matrix shapes ({@link ContractItem} / {@link Connection} /
5858
- * {@link KeyedResult}).
5859
- */
5860
- interface ExecutableQueryContract {
5861
- readonly kind: 'query';
5862
- readonly methods: Readonly<Record<string, {
5863
- readonly resolution: Resolution;
5864
- /**
5865
- * The method's decided input arity (issue #71): `'either'` for a
5866
- * coalescible base-table `point` (a key array → one `BatchGetItem`),
5867
- * `'single'` for a `range` (partition `Query`) **or** a unique-GSI `point`
5868
- * (a per-key GSI `Query`, not coalescible). The executor honors it to
5869
- * reject an array fed into any `'single'` method rather than fanning out.
5870
- */
5871
- readonly inputArity: InputArity;
5872
- readonly op: ContractMethodOp;
5873
- }>>;
5874
- }
5875
- /** A plain contract key (the access pattern / join key), e.g. `{ articleId }`. */
5876
- type ContractKeyInput = Record<string, unknown>;
5877
- /**
5878
- * The literal {@link InputArity} the contract `C` carries for method `K`. A
5879
- * {@link QueryMethodSpec} carries the method's literal arity in its `inputArity`
5880
- * type when the body was tagged with `point` / `range` (`src/define/contract.ts`);
5881
- * an untagged method keeps the wide `InputArity`. Falls back to `InputArity` for a
5882
- * structurally-typed contract (e.g. the bare {@link ExecutableQueryContract}),
5883
- * which keeps the call surface array-capable (the runtime backstop still rejects an
5884
- * array fed into a `'single'` method).
5885
- */
5886
- type ArityOfMethod<C extends ExecutableQueryContract, K extends keyof C['methods']> = C['methods'][K] extends {
5887
- readonly inputArity: infer A extends InputArity;
5888
- } ? A : InputArity;
5889
- /**
5890
- * The **array** key-argument type accepted by `executeQueryMethod` for method `K`
5891
- * of contract `C`. A method whose literal arity is `'single'` (a `range` read **or**
5892
- * a unique-GSI `point` — both one request per key) accepts **no** array: the type
5893
- * collapses to `never`, so passing an array is a `tsc` compile error at the
5894
- * user-facing surface (issue #71, N+1 rule (a), the **primary** enforcement layer).
5895
- * Any other arity (`'either'` / `'array'`, or the wide `InputArity` for an untagged
5896
- * / structural contract) accepts an array of keys (it coalesces to one batched
5897
- * call). This is the array-overload half of {@link ContractCallSignature}'s
5898
- * narrowing, applied at the real execution surface.
5899
- */
5900
- type ArrayKeyArg<C extends ExecutableQueryContract, K extends keyof C['methods']> = ArityOfMethod<C, K> extends 'single' ? never : readonly ContractKeyInput[];
5901
- /** A single hydrated result item (only the method's projected fields). */
5902
- type ContractItem = Record<string, unknown>;
5903
- /**
5904
- * A pagination connection: a page of items plus an opaque `cursor` for the next
5905
- * page (`null` when the page is the last). For a `range` contract method the
5906
- * cursor is a **per-key envelope** ({@link encodePerKeyCursor}) that identifies
5907
- * the key it paginates.
5908
- */
5909
- interface Connection<TItem = ContractItem> {
5910
- readonly items: TItem[];
5911
- readonly cursor: string | null;
5912
- }
5913
- /**
5914
- * The keyed result of a batched contract read: input-key identity (the canonical
5915
- * {@link serializeContractKey} string) → per-key result. Backed by a `Map` so
5916
- * the association is explicit and order-independent; missing keys are present
5917
- * with an explicit `null` value (`point`) rather than omitted.
5918
- *
5919
- * @typeParam TValue The per-key value (`Item | null` for `point`).
5920
- */
5921
- type KeyedResult<TValue> = Map<string, TValue>;
5922
- /**
5923
- * Retrieval options accepted by a contract query method (the serializable subset
5924
- * from the proposal). All optional; a `point` method ignores the pagination
5925
- * fields, a `range` method honors them.
5926
- */
5927
- interface ContractQueryParams {
5928
- /** Strongly-consistent read (point reads on the base table only). */
5929
- readonly consistentRead?: boolean;
5930
- /** Page size for a `range` (`list`) method. */
5931
- readonly limit?: number;
5932
- /** Resume token — a per-key cursor envelope from a prior `range` page. */
5933
- readonly after?: string;
5934
- /** Sort direction for a `range` (`list`) method. */
5935
- readonly order?: 'ASC' | 'DESC';
5936
- }
5937
- /**
5938
- * Maximum number of in-flight per-key queries for a batched `range` fan-out.
5939
- * Reuses the relation-traversal cap — the same rationale (bound in-flight
5940
- * requests, ride out throttling via UnprocessedKeys retry beneath the cap). Note
5941
- * that a single-contract `range` method is `inputArity: 'single'`, so the
5942
- * fan-out path is only exercised by the (out-of-scope, #63) composition runtime;
5943
- * the bound is defined here so the seam is ready and tested in isolation.
5944
- */
5945
- declare const CONTRACT_RANGE_FANOUT_CONCURRENCY = 16;
5946
- /**
5947
- * Keyed BatchGet: resolve `keys` against `modelClass` in one batched call and
5948
- * return a `Map<keyId, Item | null>` with every input key present (misses →
5949
- * `null`). Reuses the `batch-retry` primitives (chunk ≤100, dedup, UnprocessedKeys
5950
- * retry) — never reinvented.
5951
- *
5952
- * Implementation notes:
5953
- * - **Dedup**: the same key supplied twice is fetched once; both map entries
5954
- * point at the same resolved item (or `null`).
5955
- * - **Order / absence**: BatchGet returns matched items unordered and omits
5956
- * misses, so each returned raw item is indexed by its `PK::SK` and looked up
5957
- * per input key; an unmatched key gets `null`.
5958
- * - **Projection**: items are fetched whole (DynamoDB BatchGet has no reliable
5959
- * server-side projection for mixed keys here) and hydrated against the
5960
- * method's `select`, so the per-key item carries exactly the projected fields.
5961
- */
5962
- declare function executeKeyedBatchGet(modelClass: Function, keys: readonly ContractKeyInput[], select: Record<string, unknown>): Promise<KeyedResult<ContractItem | null>>;
5963
- /**
5964
- * Execute a query-contract method for a **single** key.
5965
- *
5966
- * The result shape is determined by the method's derived `resolution`:
5967
- * - `point` → `Item | null` (`key1 : result1`);
5968
- * - `range` → `Connection` (`key1 : resultM`).
5969
- *
5970
- * A single key is accepted for **every** method regardless of arity.
5971
- *
5972
- * @param contract The resolved query contract (a {@link QueryModelContract}).
5973
- * @param methodName The method to execute (a method name of `contract`).
5974
- * @param key A single contract key.
5975
- * @param params Retrieval options (pagination / consistency).
5976
- */
5977
- declare function executeQueryMethod<C extends ExecutableQueryContract, K extends keyof C['methods'] & string>(contract: C, methodName: K, key: ContractKeyInput, params?: ContractQueryParams): Promise<ContractItem | null | Connection>;
5978
- /**
5979
- * Execute a query-contract method for an **array** of keys (a single batched
5980
- * call). Valid **only** for a method whose literal arity is not `'single'` — a
5981
- * coalescible base-table `point` (`inputArity: 'either'`, a key array → one
5982
- * `BatchGetItem`). A `'single'` method (a `range` read **or** a unique-GSI `point`)
5983
- * makes the array key argument `never`, so passing an array is a **`tsc` compile
5984
- * error** by construction (issue #71, N+1 rule (a) — the primary enforcement
5985
- * layer; the build-time checker and runtime backstop remain). The result is a keyed
5986
- * `Map<keyId, Item | null>` (`keyN : resultN`).
5987
- *
5988
- * @param contract The resolved query contract.
5989
- * @param methodName The method to execute (a method name of `contract`).
5990
- * @param keys An array of contract keys (rejected for a `'single'` method).
5991
- * @param params Retrieval options.
5992
- */
5993
- 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>>;
5994
- /**
5995
- * The runtime fan-out seam for a batched `range` (`keyN : resultN×M`). NOT
5996
- * reachable for a single contract under #62 (a `range` method's `inputArity` is
5997
- * `'single'`, so {@link executeQueryMethod} rejects an array). It is the
5998
- * cross-contract composition runtime (#63) that drives a per-key range fan-out;
5999
- * this function implements that fan-out — N bounded-parallel Queries grouped by
6000
- * input key, each connection carrying its own per-key cursor — so the seam is
6001
- * defined, reused (via {@link mapWithConcurrency}), and unit-tested in isolation
6002
- * ahead of #63, rather than left as a stub. #62 does not call it on any contract
6003
- * surface.
6004
- *
6005
- * @param op The `range` method op to run per key.
6006
- * @param keys The parent keys to fan out over.
6007
- * @param params Retrieval options applied to **every** key's connection.
6008
- */
6009
- declare function executeRangeFanout(op: ContractMethodOp, keys: readonly ContractKeyInput[], params?: ContractQueryParams): Promise<KeyedResult<Connection>>;
6010
-
6011
4345
  /**
6012
4346
  * Single-service contract **Runtime** — execution of CQRS command-contract
6013
4347
  * methods (issue #64, Epic #57; spec `docs/cqrs-contract.md`,
@@ -6060,188 +4394,12 @@ declare function executeRangeFanout(op: ContractMethodOp, keys: readonly Contrac
6060
4394
  * The TS and Python runtimes produce identical effects for the same SSoT.
6061
4395
  */
6062
4396
 
6063
- /**
6064
- * The **structural** shape of a resolved command contract the runtime executes.
6065
- * It depends only on the facts the executor reads — the `command` discriminant
6066
- * and the per-method `op` / `batch` / `result` — and is deliberately
6067
- * **independent of the phantom Key / params / result types** carried by
6068
- * {@link CommandModelContract} (those are invariant on the full interface, so a
6069
- * concrete `CommandModelContract<{…}>` is not assignable to
6070
- * `CommandModelContract<unknown, …>`). Every real command contract is
6071
- * structurally assignable to this minimal type.
6072
- */
6073
- interface ExecutableCommandContract {
6074
- readonly kind: 'command';
6075
- readonly methods: Readonly<Record<string, {
6076
- readonly op: ContractMethodOp;
6077
- readonly result: CommandResultKind;
6078
- /**
6079
- * The #101 first-class per-call execution mode (`'transaction'` |
6080
- * `'parallel'`). Drives the key-array bulk form directly (`'transaction'`
6081
- * → atomic `TransactWriteItems`; `'parallel'` → non-atomic per-key fan-out
6082
- * with partial success, coalescing unconditioned put/delete into a
6083
- * `BatchWriteItem`).
6084
- */
6085
- readonly mode?: CommandMode;
6086
- /**
6087
- * The composed write ops of a **multi-fragment** `mutation`-derived method
6088
- * (issue #90). When present (N≥2), a **single-key** call executes ALL of
6089
- * them as **one atomic `TransactWriteItems`** (not sequential writes), so a
6090
- * condition failure on any fragment rolls back the whole transaction. Absent
6091
- * for a single-fragment / #83 / hand-written #64 method (which executes its
6092
- * one {@link op}). The read-back stays keyed on the primary op ({@link op}).
6093
- */
6094
- readonly ops?: readonly ContractMethodOp[];
6095
- /**
6096
- * The referential-integrity assertions derived from the mutation's
6097
- * `requires` effects (issue #84). When present (non-empty), a
6098
- * **single-key** call composes the base write(s) **and** one read-only
6099
- * `ConditionCheck` (`attribute_exists`) per assertion into **one atomic
6100
- * `TransactWriteItems`** — so a single-op write is **promoted** to a
6101
- * transaction. If any referenced entity is absent, its assertion fails and
6102
- * the WHOLE transaction rolls back (the entity write never persists). Absent
6103
- * on a mutation with no `requires` / a #64 hand-written method.
6104
- */
6105
- readonly conditionChecks?: readonly DerivedConditionCheck[];
6106
- /**
6107
- * The adjacency edge writes derived from the mutation's `edges` effects
6108
- * (issue #85). When present, a **single-key** call composes each edge's
6109
- * adjacency `Put` / `Delete` item(s) into the **same atomic
6110
- * `TransactWriteItems`** as the entity write (and any ConditionChecks /
6111
- * derived updates), so the edge is created / removed / moved atomically with
6112
- * the entity. Absent on a mutation with no `edges` / a #64 hand-written method.
6113
- */
6114
- readonly edgeWrites?: readonly DerivedEdgeWrite[];
6115
- /**
6116
- * The derived counter updates from the mutation's `derive` effects (issue
6117
- * #85): each an atomic `ADD` (`UpdateItem`) on a target counter (e.g.
6118
- * `User.postCount += 1`). When present, a **single-key** call composes each
6119
- * `ADD` into the same atomic `TransactWriteItems` as the entity write, so the
6120
- * counter moves atomically (and never clobbers a concurrent increment — an
6121
- * `ADD` needs no prior read). Absent on a mutation with no `derive`.
6122
- */
6123
- readonly derivedUpdates?: readonly DerivedUpdate[];
6124
- /**
6125
- * The uniqueness guards derived from the mutation's `unique` effects (issue
6126
- * #86): each a marker-row `Put` (`attribute_not_exists`) / `Delete` /
6127
- * `Delete(old)+Put(new)` swap. When present, a **single-key** call composes
6128
- * the guard item(s) into the same atomic `TransactWriteItems` as the entity
6129
- * write, so a duplicate scoped value (the guard `Put`'s `attribute_not_exists`
6130
- * failing) rolls the WHOLE transaction back — the entity is never created.
6131
- * Absent on a mutation with no `unique` / a #64 hand-written method.
6132
- */
6133
- readonly uniqueGuards?: readonly DerivedUniqueGuard[];
6134
- /**
6135
- * The outbox events derived from the mutation's `emits` effects (issue #87):
6136
- * each a transactional-outbox `Put`. When present, a **single-key** call
6137
- * composes each event's `Put` into the same atomic `TransactWriteItems` as the
6138
- * entity write, so the event RECORD is written ATOMICALLY with the state it
6139
- * announces (they can never diverge). The row is drainable from real
6140
- * DynamoDB Streams (captured at the table layer regardless of code path);
6141
- * delivery / handlers are OUT of scope. Since #94 the derived-command
6142
- * transaction also feeds every committed item (including this outbox `Put`)
6143
- * into the in-process `ChangeCaptureRegistry` seam, so the dev/test
6144
- * `src/cdc/` emulator now observes this path too — production Streams and the
6145
- * emulator agree. Absent on a mutation with no `emits`.
6146
- */
6147
- readonly outboxEvents?: readonly DerivedOutboxEvent[];
6148
- /**
6149
- * The client-token idempotency guard derived from the mutation's `idempotency`
6150
- * effect (issue #87): a marker-row `Put` (`attribute_not_exists`). When present,
6151
- * a **single-key** call composes the guard `Put` into the same atomic
6152
- * `TransactWriteItems` as the entity write, so a same-token re-execution fails
6153
- * the guard and rolls the WHOLE transaction back — no effect is double-applied.
6154
- * Absent on a mutation with no `idempotency` / a #64 hand-written method.
6155
- */
6156
- readonly idempotencyGuard?: DerivedIdempotencyGuard;
6157
- /**
6158
- * The maintenance writes derived from the relation-side `write.maintainedOn`
6159
- * declarations (issue #125 → #127; the D/#124 → compile lowering). When
6160
- * present (non-empty), a **single-key** call composes each maintenance write
6161
- * — a projected snapshot (`SET`) or a bounded-collection append
6162
- * (`SET … = list_append(…)`) on a SEPARATE owner row — into the same atomic
6163
- * `TransactWriteItems` as the source entity write (and every other effect),
6164
- * so the maintained row moves ATOMICALLY with the source: a failure on any
6165
- * item rolls the WHOLE transaction back (the snapshot / collection never
6166
- * partially persists). The owner row is upserted (a bare `UpdateItem`
6167
- * creates it if absent — the additive maintenance semantics). This is the
6168
- * synchronous (`updateMode: 'mutation'`) path only; the async stream path is
6169
- * #130 (loud-rejected at compile time). Absent on a mutation that fires no
6170
- * maintainer / a #64 hand-written method.
6171
- */
6172
- readonly maintainWrites?: readonly DerivedMaintainWrite[];
6173
- /**
6174
- * The maintenance-outbox events for `updateMode: 'stream'` maintainers (issue
6175
- * #130). Each is a transactional-outbox `Put` (the same `literalKey` marker
6176
- * mechanism as #87's outbox) composed into the method's atomic
6177
- * `TransactWriteItems` — recording the maintenance intent ATOMICALLY with the
6178
- * source write. The `src/cdc/` drain selects these (`PK begins_with
6179
- * 'OUTBOX#MAINT#'`) and applies the owner-row write asynchronously (so a
6180
- * snapshot SET / collection append+trim / counter ADD / running-max conditional
6181
- * SET happens off the write path, at-least-once, per-shard ordered). Absent on a
6182
- * mutation with no stream maintainer.
6183
- */
6184
- readonly maintainOutbox?: readonly DerivedMaintainOutbox[];
6185
- /**
6186
- * The return projection of a `mutation`-derived method (issue #83). When
6187
- * present, a **single-key** call performs the write then issues a
6188
- * **consistent read-back** (`GetItem` with `ConsistentRead`) of the
6189
- * written entity's primary key and returns the projected item via the
6190
- * existing read-projection machinery. Absent on a #64 hand-written method.
6191
- */
6192
- readonly returnSelection?: Readonly<Record<string, boolean>>;
6193
- }>>;
6194
- }
6195
- /** The params a command method consumes (the body's `params` argument). */
6196
- type ContractCommandParams = Record<string, unknown>;
6197
4397
  /**
6198
4398
  * The projected read-back item a `mutation`-derived command method returns, or
6199
4399
  * `null` when the written row was absent at read-back (e.g. a `remove`). A plain
6200
4400
  * record of the projected fields.
6201
4401
  */
6202
4402
  type CommandReturn = Record<string, unknown> | null;
6203
- /**
6204
- * #101 Phase 3 — the per-op outcome of a `mode: 'parallel'` key-array bulk write.
6205
- * A non-atomic fan-out reports each key's result independently: `{ ok: true }` on
6206
- * success, `{ ok: false, error }` on a per-op failure (a failed condition / a
6207
- * `BatchWriteItem` item that stayed unprocessed after retry). The array index
6208
- * matches the input key index, and a per-op failure NEVER aborts the others.
6209
- */
6210
- interface ParallelOpResult$1 {
6211
- readonly ok: boolean;
6212
- readonly error?: string;
6213
- }
6214
- /** The partial-success return of a `mode: 'parallel'` key-array bulk write (#101). */
6215
- interface CommandParallelReturn {
6216
- readonly results: readonly ParallelOpResult$1[];
6217
- }
6218
- /**
6219
- * Execute a command-contract method for a **single** key — one write op
6220
- * (`put` / `update` / `delete`).
6221
- *
6222
- * @param contract The resolved command contract (a {@link CommandModelContract}).
6223
- * @param methodName The method to execute.
6224
- * @param key A single contract key (the mutation target).
6225
- * @param params The mutation params (the body's `params` argument).
6226
- */
6227
- declare function executeCommandMethod(contract: ExecutableCommandContract, methodName: string, key: ContractKeyInput, params?: ContractCommandParams): Promise<void | CommandReturn>;
6228
- /**
6229
- * Execute a command-contract method for an **array** of keys — a batched write
6230
- * per the method's declared {@link CommandBatchMode}:
6231
- *
6232
- * - `'transact'` → one atomic `TransactWriteItems` (≤25, condition-capable); a
6233
- * >25-key array is **rejected** (an atomic transaction cannot be split).
6234
- * - `'batchWrite'` → a `BatchWriteItem` (no conditions; chunked ≤25 with
6235
- * `UnprocessedItems` retry).
6236
- *
6237
- * A method that declared no batched form rejects an array with a clear error.
6238
- *
6239
- * @param contract The resolved command contract.
6240
- * @param methodName The method to execute.
6241
- * @param keys An array of contract keys.
6242
- * @param params The mutation params shared across every key.
6243
- */
6244
- declare function executeCommandMethod(contract: ExecutableCommandContract, methodName: string, keys: readonly ContractKeyInput[], params?: ContractCommandParams): Promise<void | CommandParallelReturn>;
6245
4403
 
6246
4404
  /**
6247
4405
  * The **unified envelope** in-process execution engine (issue #101).
@@ -6299,17 +4457,30 @@ interface ReadRouteOptions {
6299
4457
  interface ReadRouteDescriptor {
6300
4458
  readonly query?: ModelRef;
6301
4459
  readonly list?: ModelRef;
6302
- readonly key: Record<string, unknown>;
4460
+ /**
4461
+ * The route's primary-key binding. A single key object reads once (result:
4462
+ * `Item | null` for a `query` route, a connection for a `list` route). On a
4463
+ * `query` route a **key ARRAY** (#283 — the batch-read fold that supersedes the
4464
+ * removed `DDBModel.batchGet`) reads every key and the route result is an ARRAY
4465
+ * of `Item | null` aligned to the input key order (a miss → `null`).
4466
+ *
4467
+ * The array read is HYBRID-routed by whether the select / key can use a chunked
4468
+ * server-side `BatchGetItem` (see {@link executeReadRoute}): a pure point-read
4469
+ * batch (no relation traversal, full-primary-key keys) runs through the chunked
4470
+ * `BatchGetItem` machinery (⌈N/100⌉ ops, `UnprocessedKeys` retry) for batchGet's
4471
+ * efficiency; a batch whose select requests relations (or whose key is a GSI /
4472
+ * partial key) falls back to a per-key single-item read (`executeAdHocQuery`) run
4473
+ * in parallel, since `BatchGetItem` cannot assemble relations. Both branches
4474
+ * return the identical ordered `(Item | null)[]` — the routing is a physical-read
4475
+ * choice only (no data / order / projection difference). A key array on a `list`
4476
+ * route is rejected (a partition read has no multi-key meaning).
4477
+ */
4478
+ readonly key: Record<string, unknown> | readonly Record<string, unknown>[];
6303
4479
  readonly select: Record<string, unknown>;
6304
4480
  readonly options?: ReadRouteOptions;
6305
4481
  }
6306
4482
  /** The read envelope: alias → {@link ReadRouteDescriptor}. */
6307
4483
  type ReadEnvelope = Record<string, ReadRouteDescriptor>;
6308
- /** The result of a read route — a `query` item (or `null`) or a `list` connection. */
6309
- type ReadRouteResult = Record<string, unknown> | null | {
6310
- items: Record<string, unknown>[];
6311
- cursor: string | null;
6312
- };
6313
4484
  /** The write execution mode: atomic transaction (default) or non-atomic parallel. */
6314
4485
  type MutateMode = 'transaction' | 'parallel';
6315
4486
  /** A read-back projection for a write op (the EXISTING query select shape). */
@@ -6325,6 +4496,12 @@ interface InProcessWriteDescriptor {
6325
4496
  readonly create?: ModelRef;
6326
4497
  readonly update?: ModelRef;
6327
4498
  readonly remove?: ModelRef;
4499
+ /**
4500
+ * `upsert` — a `PutItem` with **no** `attribute_not_exists(PK)` guard (an
4501
+ * unconditional overwrite; the semantics of the removed `definePut`). Unlike
4502
+ * `create`, an `upsert` may carry a `condition` (honored verbatim).
4503
+ */
4504
+ readonly upsert?: ModelRef;
6328
4505
  /**
6329
4506
  * The target's primary-key binding. A single key object applies the op once; an
6330
4507
  * **array** of key objects (#101 `key: K | K[]`) applies the SAME op to every key
@@ -6339,25 +4516,6 @@ interface InProcessWriteDescriptor {
6339
4516
  }
6340
4517
  /** The write envelope: alias → {@link InProcessWriteDescriptor}. */
6341
4518
  type WriteEnvelope = Record<string, InProcessWriteDescriptor>;
6342
- /** Options for {@link executeWriteEnvelope}. */
6343
- interface MutateOptions {
6344
- readonly mode?: MutateMode;
6345
- /**
6346
- * Per-call throttle / transient-error retry override (issue #111), applied to
6347
- * every write this envelope composes (the atomic `TransactWriteItems` in
6348
- * `transaction` mode, or each op's send in `parallel` mode). A {@link RetryPolicy}
6349
- * overrides the global policy; `false` disables retry. Omit ⇒ the configured /
6350
- * built-in default policy.
6351
- */
6352
- readonly retry?: RetryOverride;
6353
- /**
6354
- * Host-injected per-call write context (issue #50 / #139) — exposed to every
6355
- * write hook (W1–W5) as `ctx.context`. In `transaction` mode every composed op
6356
- * shares it and the persist hooks fire once for the atomic batch; in `parallel`
6357
- * mode each independent op sees it. NEVER serialized. Absent ⇒ `{}`.
6358
- */
6359
- readonly context?: RequestContext;
6360
- }
6361
4519
  /** A per-alias result in `parallel` mode: success carries the read-back, else error. */
6362
4520
  type ParallelOpResult = {
6363
4521
  readonly ok: true;
@@ -6366,7 +4524,7 @@ type ParallelOpResult = {
6366
4524
  readonly ok: false;
6367
4525
  readonly error: Error;
6368
4526
  };
6369
- declare const INTENT_KEYS: readonly ["create", "update", "remove"];
4527
+ declare const INTENT_KEYS: readonly ["create", "update", "remove", "upsert"];
6370
4528
  type Intent = (typeof INTENT_KEYS)[number];
6371
4529
 
6372
4530
  /**
@@ -6378,11 +4536,17 @@ type Intent = (typeof INTENT_KEYS)[number];
6378
4536
 
6379
4537
  /** The entity type a {@link ModelStatic} value (or model class) carries. */
6380
4538
  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;
6381
- /** The result type of one read route, derived from `query` / `list` + `select`. */
4539
+ /**
4540
+ * The result type of one read route, derived from `query` / `list` + `select`.
4541
+ * A `query` route whose `key` is an ARRAY (#283 batch read, the `DDBModel.batchGet`
4542
+ * fold) resolves to a per-key ARRAY of `Item | null`, aligned to the input keys.
4543
+ */
6382
4544
  type ReadRouteResultOf<R> = R extends {
6383
4545
  readonly query: infer M;
6384
4546
  readonly select: infer S;
6385
- } ? QueryResultFor<EntityOf<M>, S> | null : R extends {
4547
+ } ? R extends {
4548
+ readonly key: readonly unknown[];
4549
+ } ? readonly (QueryResultFor<EntityOf<M>, S> | null)[] : QueryResultFor<EntityOf<M>, S> | null : R extends {
6386
4550
  readonly list: infer M;
6387
4551
  readonly select: infer S;
6388
4552
  } ? {
@@ -6538,7 +4702,7 @@ interface CompiledReadRoute {
6538
4702
  * ## Why "prepare once, execute many"
6539
4703
  *
6540
4704
  * `DDBModel.mutate` / `Model.query` recompile the plan on every call (`#203`'s
6541
- * "軽量だが per-call 再コンパイル" cell). `publicCommandModel` / `publicQueryModel`
4705
+ * "軽量だが per-call 再コンパイル" cell). `publishCommand` / `publishQuery`
6542
4706
  * precompile but weld it to a named CQRS contract. `prepare` is the missing
6543
4707
  * middle: precompiled **without** the contract ceremony.
6544
4708
  *
@@ -6553,11 +4717,11 @@ interface CompiledReadRoute {
6553
4717
  * computed once. `execute` binds the concrete key VALUES + per-call pagination /
6554
4718
  * consistency and runs the unified read-plan executor
6555
4719
  * (`executeCompiledReadRoute`, issue #249) — the identical core `Model.query` /
6556
- * `Model.list` and `publicQueryModel` use. (The
4720
+ * `Model.list` and `publishQuery` use. (The
6557
4721
  * `ExecutionPlan` bakes concrete key values into its `keyCondition`, so a single
6558
4722
  * compiled operation object is NOT reusable across param sets; the precompiled
6559
4723
  * product is the resolved model + normalized projection — exactly what the public
6560
- * `publicQueryModel` op reuses — and the residual per-op `plan()` is identical to
4724
+ * `publishQuery` op reuses — and the residual per-op `plan()` is identical to
6561
4725
  * that public path.)
6562
4726
  *
6563
4727
  * ## No runtime capture (the design's key constraint)
@@ -6602,17 +4766,25 @@ interface PreparedParamRef {
6602
4766
  /** The param name, e.g. `userId`. */
6603
4767
  readonly name: string;
6604
4768
  }
6605
- /** Runtime guard: is `value` a {@link PreparedParamRef} (a `$.<name>` read)? */
6606
- declare function isPreparedParamRef(value: unknown): value is PreparedParamRef;
6607
4769
  /** The placeholder proxy handed to a `prepare` body as `$`. Every `$.<name>` read
6608
4770
  * mints a {@link PreparedParamRef}; any further access / coercion throws so the
6609
- * body stays a pure, params-independent template (no runtime capture). */
4771
+ * body stays a pure, params-independent template (no runtime capture).
4772
+ *
4773
+ * **Input Port 統一 (issue #264)**: this proxy is one of the three authoring
4774
+ * spellings of the SAME Input Port reference model — see the unification note
4775
+ * on {@link import('../define/transaction.js').ParamProxy} (`defineTransaction`'s
4776
+ * `p.*` proxy is the model; a `$.<name>` read here is the same "declared
4777
+ * input field, bound at execution" reference that serializes as `{<name>}`
4778
+ * templates / `{ref:["input","<name>"]}` expression refs elsewhere). Behavior
4779
+ * here is unchanged in 0.7.x (interop kept). */
6610
4780
  type PreparedInputProxy = Record<string, PreparedParamRef>;
6611
4781
  /** A `prepare` write route: `{ create|update|remove: () => Model, key, input?, condition?, result? }`. */
6612
4782
  interface PreparedWriteRoute {
6613
4783
  readonly create?: ModelRef;
6614
4784
  readonly update?: ModelRef;
6615
4785
  readonly remove?: ModelRef;
4786
+ /** `upsert` — a `PutItem` with no `attribute_not_exists(PK)` guard (unconditional overwrite; the `definePut` semantics). */
4787
+ readonly upsert?: ModelRef;
6616
4788
  readonly key: Record<string, unknown>;
6617
4789
  readonly input?: Record<string, unknown>;
6618
4790
  readonly condition?: Record<string, unknown>;
@@ -6688,22 +4860,6 @@ declare class PreparedReadStatement {
6688
4860
  private runRoute;
6689
4861
  }
6690
4862
  type PreparedStatement = PreparedReadStatement | PreparedWriteStatement;
6691
- /**
6692
- * The max number of distinct compiled prepared statements the fallback cache
6693
- * retains. Bounded so an unbounded variety of inline `prepare` shapes cannot grow
6694
- * the cache without limit (issue #205 AC). LRU eviction: least-recently-used first.
6695
- */
6696
- declare const PREPARE_CACHE_MAX = 256;
6697
- /**
6698
- * Compile a declarative read/write route body into a reusable prepared statement
6699
- * (issue #205). Backs {@link DDBModel.prepare}.
6700
- *
6701
- * The body is evaluated ONCE with a recording `$` proxy; the resulting routes are
6702
- * classified (all-read or all-write), a structure key is computed, and the compiled
6703
- * handle is memoized in the bounded fallback cache so an inline `prepare(fn)` of the
6704
- * same shape recompiles at most once.
6705
- */
6706
- declare function prepare(body: PreparedBody): PreparedStatement;
6707
4863
 
6708
4864
  /**
6709
4865
  * `subscribe` — the declarative, batch CDC consumer builder (issue #153).
@@ -6778,66 +4934,6 @@ type CdcSubscribeHandlers = keyof CdcModelRegistry extends never ? Record<string
6778
4934
  declare abstract class DDBModel {
6779
4935
  /** @internal Type brand — prevents primitives from structurally matching DDBModel. */
6780
4936
  private readonly __brand;
6781
- /**
6782
- * Register the `DynamoDBClient` GraphDDB sends through.
6783
- *
6784
- * IMPORTANT (issue #111): GraphDDB now owns single-op throttle / transient-error
6785
- * retry (see {@link setRetryPolicy}), so a client left at the AWS SDK default
6786
- * `maxAttempts: 3` retries MULTIPLICATIVELY underneath the library. Construct the
6787
- * client with `maxAttempts: 1` so the library is the single source of truth for
6788
- * retry — e.g. `new DynamoDBClient({ maxAttempts: 1 })`. GraphDDB does NOT mutate
6789
- * a client you pass in.
6790
- */
6791
- static setClient(client: DynamoDBClient): void;
6792
- /**
6793
- * Read back the registered `DynamoDBClient` — the resolved client last passed to
6794
- * {@link setClient}. Symmetric with {@link setClient} and backed by the same
6795
- * {@link ClientManager} store, so it always reflects the client GraphDDB sends
6796
- * through.
6797
- *
6798
- * Throws a clear error if no client has been registered yet (call
6799
- * {@link setClient} first) — matching {@link ClientManager.getClient}'s "loud,
6800
- * never silently `undefined`" convention shared with the rest of the client API.
6801
- */
6802
- static getClient(): DynamoDBClient;
6803
- static setTableMapping(mapping: Record<string, string>): void;
6804
- /**
6805
- * Configure the global single-op retry policy (issue #111) — exponential backoff
6806
- * + jitter with a bounded attempt cap, applied to every GetItem / Query / Put /
6807
- * Update / Delete / relation-fan-out send and to `TransactWriteItems`. Mirrors
6808
- * {@link setClient} / {@link setTableMapping}. A sensible default is ALWAYS on; call
6809
- * this only to tune it (or pass `null` to restore the default). A per-call
6810
- * `retry?: RetryPolicy | false` on an operation's options overrides this globally
6811
- * for that call (`false` disables retry).
6812
- */
6813
- static setRetryPolicy(policy: RetryPolicy | null): void;
6814
- /**
6815
- * Register a {@link Middleware} (issue #50; read path #138, write path #139).
6816
- * One registered object may carry read and/or write hooks.
6817
- *
6818
- * - **Read** R1–R5 — request entry (`read.before`), each physical op before send
6819
- * / after raw fetch (`read.op.before` / `read.op.afterFetch`, INCLUDING every
6820
- * relation fan-out fetch), the final hydrated result (`read.afterFetch`), and on
6821
- * error (`read.op.onError` / `read.onError`).
6822
- * - **Write** W1–W5 — the logical write before effect derivation (`write.before`,
6823
- * may mutate `ctx.input` / `ctx.kind` incl. a delete→update rewrite), after commit
6824
- * (`write.after`), the physical persist on the fully composed batch
6825
- * (`write.persist.before` / `write.persist.after`, fires ONCE per atomic
6826
- * transaction), and on error (`write.persist.onError` / `write.onError`).
6827
- *
6828
- * Hooks may observe, mutate `ctx`, `throw` to cancel, or (on `onError`) recover by
6829
- * returning a value; the library imposes no semantics. Ordering: `before*` runs
6830
- * first-registered-first (FIFO); `after*` / `onError` run last-registered-first
6831
- * (LIFO). Mirrors {@link setRetryPolicy} — a host-runtime global stored on
6832
- * {@link ClientManager}; never serialized (the bridge #48 is unaffected). Per-call
6833
- * host context flows in via the read's / write's `{ context }` option.
6834
- */
6835
- static use(mw: Middleware): void;
6836
- /** Remove every registered middleware (read AND write; teardown / tests). See {@link use}. */
6837
- static clearMiddleware(): void;
6838
- static transaction(fn: (tx: TransactionContext) => void | Promise<void>, options?: {
6839
- context?: RequestContext;
6840
- }): Promise<void>;
6841
4937
  /**
6842
4938
  * In-process multi-route READ (issue #101 — the unified envelope). Each alias
6843
4939
  * route (`{ query | list: Model, key, select, options? }`) runs **independently
@@ -6868,8 +4964,8 @@ declare abstract class DDBModel {
6868
4964
  /**
6869
4965
  * **Prepared statement** (issue #205 / design #203) — the read/write-unified
6870
4966
  * middle tier between the ad-hoc `DDBModel.mutate` / `DDBModel.query` (per-call
6871
- * recompiled) and the public CQRS contract (`publicCommandModel` /
6872
- * `publicQueryModel`, precompiled but contract-welded).
4967
+ * recompiled) and the public CQRS contract (`publishCommand` /
4968
+ * `publishQuery`, precompiled but contract-welded).
6873
4969
  *
6874
4970
  * `prepare($ => ({...}))` **compiles the declarative route body once** and returns
6875
4971
  * a handle whose `.execute(params)` binds the per-call values and runs through the
@@ -6939,20 +5035,6 @@ declare abstract class DDBModel {
6939
5035
  * registered `@cdcProjected` model throws at build time.
6940
5036
  */
6941
5037
  static subscribe(handlers: CdcSubscribeHandlers): ChangeHandler;
6942
- /**
6943
- * Multi-key, multi-model batch read. Each {@link BatchGetRequest} contributes its
6944
- * keys to a chunked `BatchGetItem` per physical table; results are re-grouped by
6945
- * model on the returned {@link BatchGetResult}.
6946
- *
6947
- * Fires the read hooks (issue #142): request-level R1/R4/R5 with kind
6948
- * `'batchGet'`, and op-level R2/R3/R5 for each underlying physical `BatchGetItem`
6949
- * op. The optional `{ context }` is exposed to every hook as `ctx.context` and
6950
- * threaded UNCHANGED to every op (absent ⇒ `{}`), so a global hook (e.g. a
6951
- * tenant-scope R2 FilterExpression) applies across the whole batch. With no
6952
- * middleware registered the read path is unchanged (zero-overhead fast path).
6953
- */
6954
- static batchGet(requests: BatchGetRequest[], options?: BatchGetOptions): Promise<BatchGetResult>;
6955
- static batchWrite(requests: BatchWriteRequest[]): Promise<void>;
6956
5038
  static asModel<C extends new (...args: never[]) => DDBModel>(this: C): ModelStatic<InstanceType<C>, C>;
6957
5039
  }
6958
5040
 
@@ -6981,8 +5063,6 @@ interface Column<V = unknown, M = unknown> {
6981
5063
  /** @internal phantom — owning-model brand. */
6982
5064
  readonly __model?: M;
6983
5065
  }
6984
- /** Runtime guard: is this value a {@link Column} reference? */
6985
- declare function isColumn(value: unknown): value is Column;
6986
5066
  /**
6987
5067
  * The `Model.col` map: one {@link Column} per scalar field of the entity, each
6988
5068
  * typed with the field's declared value type and branded to the model.
@@ -7015,8 +5095,6 @@ interface KeySegment {
7015
5095
  /** The interpolated column references, in order. */
7016
5096
  readonly columns: readonly Column[];
7017
5097
  }
7018
- /** Runtime guard: is this value a {@link KeySegment}? */
7019
- declare function isKeySegment(value: unknown): value is KeySegment;
7020
5098
  /**
7021
5099
  * The interpolation slot accepted by {@link k}: a typed column reference
7022
5100
  * (`c.field`). Bare strings are NOT accepted — a key field must be named via a
@@ -7082,519 +5160,4 @@ declare function key<T extends Record<string, unknown>>(builder: (c: {
7082
5160
  readonly [K in keyof T]-?: Column<T[K], T>;
7083
5161
  }) => KeyStructure): KeyDefinitionMarker<T>;
7084
5162
 
7085
- type DynamoType = 'S' | 'N' | 'BOOL' | 'B' | 'SS' | 'NS' | 'L' | 'M';
7086
- interface FieldOptions {
7087
- format?: 'datetime' | 'date';
7088
- readonly?: boolean;
7089
- serialize?: (value: unknown) => unknown;
7090
- deserialize?: (value: unknown) => unknown;
7091
- /**
7092
- * Optional human-readable description of the field (issue #154). Pure
7093
- * documentation metadata — it does NOT affect storage, hydration, key handling,
7094
- * or any runtime behaviour. When present it is propagated to the field entry in
7095
- * `manifest.json` ({@link ManifestField.description}) and surfaces as a field
7096
- * comment in the generated Python `types.py`. Absent → byte-for-byte unchanged
7097
- * output (backward compatible).
7098
- */
7099
- description?: string;
7100
- }
7101
- interface FieldMetadata {
7102
- propertyName: string;
7103
- dynamoType: DynamoType;
7104
- options?: FieldOptions;
7105
- /**
7106
- * For a `@literal(...)` field — a `string`-stored field whose value is one of a
7107
- * known, finite set — the allowed literal values, in declaration order (issue
7108
- * #75). Recorded purely as model metadata so a Contract param bound into the
7109
- * field can recover the precise `literal` param-kind from its bind position
7110
- * (`src/spec/contracts.ts`), serializing as `{ type: 'literal', literals: [...] }`
7111
- * instead of the imprecise `'string'`. JSON-safe by construction (a non-empty
7112
- * array of strings). `undefined` for every other field decorator (`@string`,
7113
- * `@number`, …), so plain fields are byte-for-byte unchanged (backward compat).
7114
- */
7115
- literals?: readonly string[];
7116
- /**
7117
- * For an `@ttl` field (issue #172, Epic #167 — CFn generator C4) — marks this
7118
- * field as the model's **DynamoDB Time-To-Live** attribute. `@ttl` is a
7119
- * standalone semantic decorator pinned to `number` (it records `dynamoType: 'N'`
7120
- * itself, so `@ttl expiresAt!: string` is a compile error), because DynamoDB TTL
7121
- * requires a Number attribute holding **epoch seconds**. It is a **physical
7122
- * schema** fact (unlike the maintenance signals kept off the manifest): the flag
7123
- * flows through {@link EntityMetadata} into `manifest.json`
7124
- * ({@link ManifestEntity.ttlAttribute}), which the CloudFormation emitter consumes
7125
- * to render `TimeToLiveSpecification`. DynamoDB allows exactly one TTL attribute
7126
- * per table, enforced loudly at model registration (`@model`). `undefined` for
7127
- * every other field decorator, so non-TTL fields are byte-for-byte unchanged.
7128
- */
7129
- ttl?: boolean;
7130
- }
7131
-
7132
- /**
7133
- * The maintenance kind of a model (issue #152). `'entity'` (the default) is a plain
7134
- * stored entity. `'materializedView'` / `'sparseView'` mark a model as a maintained
7135
- * **view** row whose `@maintainedFrom` declarations feed the maintenance graph (the
7136
- * adapter that replaces the old `defineView` / `ViewRegistry` path). A `sparseView`
7137
- * requires every `@maintainedFrom` to carry a `when` membership predicate.
7138
- */
7139
- type ModelKind = 'entity' | 'materializedView' | 'sparseView';
7140
- /**
7141
- * One lowered `@maintainedFrom(source, (self, source) => options)` declaration on a
7142
- * view model (issue #152). It is the decorator-declarative replacement for one
7143
- * `defineView` source slice: the symbolic `(self, source) =>` callback has already
7144
- * been evaluated into the SAME payload-rooted IR (`keyBind` → `$.entity.*` key
7145
- * binding, `project` → {@link ProjectionMap}), so the adapter
7146
- * (`src/relation/maintenance-view-adapter.ts`) reconstructs a byte-identical
7147
- * `ViewSourceSlice` from it.
7148
- *
7149
- * `collection` and `when` are mutually exclusive (a row either holds a maintained
7150
- * list or appears/disappears as a whole — the same rule the old builder enforced).
7151
- */
7152
- interface MaintainedFromMetadata {
7153
- /** Resolves the source entity whose lifecycle events maintain the view row. */
7154
- sourceFactory: () => new (...args: unknown[]) => unknown;
7155
- /** The lifecycle events that maintain the view row (`created`/`updated`/`removed`). */
7156
- on: readonly MaintainEvent[];
7157
- /** The view-row key binding (view-row key field → payload-rooted source value). */
7158
- keyBind: Readonly<Record<string, EffectPath>>;
7159
- /** The projection of the source payload into the view row (target attr → transform). */
7160
- project: ProjectionMap;
7161
- /** Bounded/ordered collection options when this declaration maintains a list. */
7162
- collection?: {
7163
- /** The view-row attribute that holds the maintained list (a `self.<field>`). */
7164
- field: string;
7165
- maxItems?: number;
7166
- order?: 'ASC' | 'DESC';
7167
- /** The source value the collection orders by / trims on (`$.entity.<field>`). */
7168
- orderBy?: EffectPath;
7169
- };
7170
- /** Sparse-view membership predicate (mutually exclusive with `collection`). */
7171
- when?: MembershipPredicate;
7172
- consistency?: MaintainConsistency;
7173
- updateMode?: MaintainUpdateMode;
7174
- /**
7175
- * Optional human-readable description of this maintained-from source slice (issue
7176
- * #166, follow-up of #154), supplied via
7177
- * `@maintainedFrom(() => Source, (self, source) => ({ description, ... }))`. Pure
7178
- * documentation metadata — it does NOT affect the maintenance IR (`keyBind` /
7179
- * `project` / `on` / `collection` / `when`) or any runtime behaviour.
7180
- *
7181
- * NOTE (representation): the maintenance IR is deliberately kept OFF the
7182
- * serializable `manifest.json` / `operations.json` — the {@link Manifest} carries
7183
- * only the physical shape (table / keys / GSIs / relation navigation), NOT the
7184
- * maintenance graph (see `detectStreamMaintenance` in `src/codegen/cloudformation.ts`).
7185
- * There is therefore no manifest / operations / generated-Python site for a
7186
- * maintained-from source; this description is captured at the metadata layer only
7187
- * (mirroring where #154 captured its descriptions before propagation), available to
7188
- * any future consumer that DOES surface `@maintainedFrom` (e.g. the CDC-projection
7189
- * typed-consumer path, #153). Absent → byte-for-byte unchanged metadata.
7190
- */
7191
- description?: string;
7192
- }
7193
- interface KeyDefinition {
7194
- /** The canonical structured key (PK/SK segment lists). */
7195
- segmented: SegmentedKey;
7196
- inputFieldNames: string[];
7197
- }
7198
- interface GsiDefinition {
7199
- indexName: string;
7200
- /** The canonical structured key (PK/SK segment lists). */
7201
- segmented: SegmentedKey;
7202
- inputFieldNames: string[];
7203
- unique: boolean;
7204
- /**
7205
- * Optional human-readable description of the GSI (issue #166, follow-up of #154),
7206
- * supplied via `gsi(name, key, { description })`. Pure documentation metadata — it
7207
- * does NOT affect the index key, projection, or any runtime behaviour. When present
7208
- * it is propagated to the index entry in `manifest.json`
7209
- * ({@link ManifestGsi.description}) and surfaces as the docstring of any generated
7210
- * Python query method that reads through this index (a query carrying the GSI's
7211
- * `indexName`, when the query itself declares no description). Absent →
7212
- * byte-for-byte unchanged output (backward compatible).
7213
- */
7214
- description?: string;
7215
- }
7216
- interface RelationLimitOptions {
7217
- default: number;
7218
- max: number;
7219
- }
7220
- /**
7221
- * The named maintenance pattern a relation participates in (Epic #118 §5.1).
7222
- *
7223
- * `pattern` is the *preset* selector: omitting it leaves the relation a plain
7224
- * read-only navigation (the historical `hasMany`/`belongsTo`/`hasOne`
7225
- * behaviour), exactly backward compatible. When present it names the AWS
7226
- * design pattern the relation lowers to — the lowering itself (maintenance
7227
- * graph / compile injection) is out of scope for issue #121 and lands in
7228
- * #124/#125; here the value is recorded purely as declaration metadata.
7229
- *
7230
- * Typed as a `string` union of the known presets but kept open-ended via the
7231
- * `(string & {})` tail so a future preset can be authored before this union is
7232
- * widened, without a breaking change to callers.
7233
- *
7234
- * The union lists only the presets that are actually *reachable* as a recorded
7235
- * `RelationOptions.pattern` / `AggregateOptions.pattern` value: `samePartition`,
7236
- * `counter`, `embeddedSnapshot`, and the two versioned discriminators
7237
- * `versionedLatest` / `versionedHistory` (issue #152). `materializedView` /
7238
- * `sparseView` migrated to the model-level `@model({ kind })` selector (they are
7239
- * a {@link ModelKind}, not a relation preset); the placeholder `edge` /
7240
- * `dualEdge` / `versioned` / `externalProjection` names are not authored anywhere
7241
- * and were removed. A future preset can still be written against the `(string & {})`
7242
- * tail before it is added to this union.
7243
- */
7244
- type RelationPattern = 'samePartition' | 'counter' | 'embeddedSnapshot' | 'versionedLatest' | 'versionedHistory' | (string & {});
7245
- /**
7246
- * Consistency boundary a maintained write is declared against (Epic #118 §4.A.7):
7247
- * `transactional` = same `TransactWriteItems`; `eventual` = asynchronous catch-up.
7248
- */
7249
- type RelationConsistency = 'transactional' | 'eventual';
7250
- /**
7251
- * How a maintained write is applied (Epic #118 §4.A.7):
7252
- * `mutation` = composed into the same mutation; `stream` = driven asynchronously
7253
- * off a stream / outbox.
7254
- */
7255
- type RelationUpdateMode = 'mutation' | 'stream';
7256
- /**
7257
- * Read-strategy hints for a maintained relation (Epic #118 §5.1 `read`).
7258
- *
7259
- * All fields optional — present only when a `pattern` declares a read shape
7260
- * (e.g. an `embeddedSnapshot` keeping the latest N items). A bare relation
7261
- * carries no `read` block.
7262
- */
7263
- interface RelationReadOptions {
7264
- /** Cap on materialised items kept inline (e.g. "latest 3 posts"). */
7265
- maxItems?: number;
7266
- /** Ordering of the maintained collection. */
7267
- order?: 'ASC' | 'DESC';
7268
- }
7269
- /**
7270
- * Write-maintenance declaration for a relation (Epic #118 §5.1 `write`).
7271
- *
7272
- * `maintainedOn` is the cross-entity trigger list (`"<Entity>.<event>"`, e.g.
7273
- * `"Post.created"`) that the maintenance graph (#124/#125) will key off. Recorded
7274
- * here as declaration metadata only.
7275
- */
7276
- interface RelationWriteOptions {
7277
- /**
7278
- * Cross-entity triggers, e.g. `['Post.created', 'Post.removed']`. The event
7279
- * segment is the confirmed shared lifecycle vocabulary (epic #118 A/B
7280
- * reconciliation): `created` / `updated` / `removed`.
7281
- */
7282
- maintainedOn?: string[];
7283
- consistency?: RelationConsistency;
7284
- updateMode?: RelationUpdateMode;
7285
- }
7286
- /**
7287
- * Snapshot / projection capture map for a maintained relation
7288
- * (Epic #118 §5.1 / §5.2 `projection`).
7289
- *
7290
- * Maps each captured field name on the maintaining side to **how** the source
7291
- * value is projected. The confirmed shared vocabulary (epic #118 A/B
7292
- * reconciliation) is the **function-form IR** — the same {@link
7293
- * ProjectionTransform} node A (#120) defines on `src/define/entity-writes.ts`,
7294
- * re-used here (NOT re-defined) so the two surfaces never re-diverge. Each value
7295
- * is either:
7296
- *
7297
- * - a {@link ProjectionTransform} (built with the standalone authoring helpers,
7298
- * e.g. `preview('body', 120)` — RFC §5.2 `textPreview: preview('body', 120)`),
7299
- * or
7300
- * - a bare `string` — the **identity shorthand**: project that source path
7301
- * through unchanged (e.g. `{ postId: 'postId' }`).
7302
- *
7303
- * `Readonly` so the recorded declaration metadata is not mutated downstream.
7304
- * Issue #121 scopes this to the declaration shape; the maintenance graph /
7305
- * compile injection that consumes the IR is #124/#125.
7306
- *
7307
- * @example `{ postId: 'postId', textPreview: preview('body', 120) }`
7308
- */
7309
- type RelationProjection = Readonly<Record<string, ProjectionTransform | string>>;
7310
- interface RelationOptions {
7311
- limit?: RelationLimitOptions;
7312
- order?: 'ASC' | 'DESC';
7313
- /**
7314
- * Optional human-readable description of the relation (issue #166, follow-up of
7315
- * #154), supplied via `@hasMany/@belongsTo/@hasOne(() => T, keyBind, { description })`.
7316
- * Pure documentation metadata — it does NOT affect navigation, key binding, or any
7317
- * runtime behaviour. When present it is propagated to the relation entry in
7318
- * `manifest.json` ({@link ManifestRelation.description}) and surfaces as a trailing
7319
- * `# …` comment on the relation's field in the generated Python result TypedDict /
7320
- * dataclass. Absent → byte-for-byte unchanged output (backward compatible).
7321
- */
7322
- description?: string;
7323
- /**
7324
- * Named maintenance preset (Epic #118). Omitted = plain read-only navigation,
7325
- * fully backward compatible with the historical relation decorators.
7326
- */
7327
- pattern?: RelationPattern;
7328
- read?: RelationReadOptions;
7329
- write?: RelationWriteOptions;
7330
- projection?: RelationProjection;
7331
- /**
7332
- * Lowered versioned-pattern declaration (issue #152, section B). Present only when
7333
- * the relation declares `pattern: 'versionedLatest' | 'versionedHistory'` via the
7334
- * `@hasOne/@hasMany(() => Target, (self, source) => ({...}))` callback form. The
7335
- * declaring model is the SOURCE (a revision); the relation target is the
7336
- * DESTINATION view row. The adapter
7337
- * (`src/relation/maintenance-view-adapter.ts`) lowers this to the SAME `defineView`
7338
- * snapshot IR the old `defineVersioned` produced (latest = overwrite, history =
7339
- * append). `on` are the lifecycle events; `project` is the lowered projection map.
7340
- */
7341
- versioned?: {
7342
- readonly mode: 'latest' | 'history';
7343
- readonly on: readonly MaintainEvent[];
7344
- readonly project: ProjectionMap;
7345
- readonly consistency?: MaintainConsistency;
7346
- readonly updateMode?: MaintainUpdateMode;
7347
- };
7348
- }
7349
- /**
7350
- * Descriptor for a `refs` relation (issue #197, Pattern 2 Embedded Refs read
7351
- * side). A `refs` relation resolves an **inline id-list** held on the parent row
7352
- * — e.g. `PPost.tagRefs: { tagId: string }[]` — into the referenced child
7353
- * bodies, in ONE deduped `BatchGetItem` (never per-ref `GetItem`).
7354
- *
7355
- * Unlike `hasMany`/`belongsTo`/`hasOne`, whose key is bound from a parent
7356
- * **scalar** field ({@link RelationMetadata.keyBinding} — `target key ← parent
7357
- * scalar`), a `refs` key is bound from EACH ELEMENT of a parent LIST attribute:
7358
- * the traversal reads `parent[from]` (a list), pulls `.key` off every element,
7359
- * and fans those out into a single BatchGet against the target. This descriptor
7360
- * captures exactly that list-valued source shape, which the scalar `keyBinding`
7361
- * `Record<string,string>` cannot express.
7362
- *
7363
- * `keyBinding` on a `refs` relation still records the **target child key field ←
7364
- * element key name** mapping (e.g. `{ tagId: 'tagId' }`), so key building /
7365
- * projection reuse the existing scalar machinery per resolved element; `refs`
7366
- * only adds *where the scalar values come from* (the parent list), not a new key
7367
- * grammar. That keeps `refs` byte-compatible with every scalar-keyBinding
7368
- * consumer (manifest, planner, dedupe) — an unknown-to-them relation kind is
7369
- * simply carried through with a valid scalar `keyBinding`.
7370
- */
7371
- interface RefsBinding {
7372
- /** The parent LIST attribute holding the inline reference elements (e.g. `'tagRefs'`). */
7373
- from: string;
7374
- /** The key field to read off EACH list element (e.g. `'tagId'`). */
7375
- key: string;
7376
- }
7377
- interface RelationMetadata {
7378
- type: 'hasMany' | 'hasOne' | 'belongsTo' | 'refs';
7379
- propertyName: string;
7380
- targetFactory: () => new (...args: unknown[]) => unknown;
7381
- keyBinding: Record<string, string>;
7382
- /**
7383
- * List-valued source descriptor for a `refs` relation (issue #197). Present
7384
- * IFF `type === 'refs'`; the traversal reads the parent list `refs.from`, pulls
7385
- * `refs.key` off each element, and resolves the referenced child bodies in one
7386
- * deduped BatchGet. Absent for every scalar-keyed relation (byte-compatible).
7387
- */
7388
- refs?: RefsBinding;
7389
- options?: RelationOptions;
7390
- }
7391
- /**
7392
- * The scalar-aggregate value an `@aggregate` field derives from its source entity
7393
- * (Epic #118 §5.2 counter / latest preset; issue #122). It is the function-form
7394
- * IR built by the {@link count} / {@link max} value helpers — the aggregation an
7395
- * `@aggregate(() => Source, keyBinding, { pattern, value })` field maintains:
7396
- *
7397
- * - `count` — the cardinality of source rows matched by the key binding (no
7398
- * source field; e.g. `postCount!: number` ← `count()`).
7399
- * - `max` — the maximum value of a named source `field` (e.g. `lastPostAt!:
7400
- * string` ← `max('createdAt')`).
7401
- *
7402
- * Recorded purely as declaration metadata (issue #122 scope = declaration
7403
- * surface): the maintenance graph (#124) / compile injection (#125) consume it;
7404
- * the effect itself is out of scope here. Kept open-ended via the discriminated
7405
- * `op` so a future aggregation (`min`, `sum`, …) extends the union without a
7406
- * breaking change.
7407
- *
7408
- * @example `count()` → `{ op: 'count' }`
7409
- * @example `max('createdAt')` → `{ op: 'max', field: 'createdAt' }`
7410
- */
7411
- type AggregateValue = {
7412
- readonly op: 'count';
7413
- } | {
7414
- readonly op: 'max';
7415
- readonly field: string;
7416
- };
7417
- /**
7418
- * Options for an `@aggregate` field (Epic #118 §5.2 counter / latest preset;
7419
- * issue #122). Mirrors the maintenance vocabulary a relation declares so the two
7420
- * authoring surfaces stay in lockstep:
7421
- *
7422
- * - `pattern` reuses {@link RelationPattern} (e.g. `'counter'`) — the named
7423
- * maintenance preset, NOT re-defined here.
7424
- * - `value` is the {@link AggregateValue} the field maintains (`count()` /
7425
- * `max('createdAt')`).
7426
- * - `write` reuses {@link RelationWriteOptions} verbatim — `maintainedOn`
7427
- * triggers (`Post.created` / `Post.removed`, the confirmed shared
7428
- * `created|updated|removed` event vocabulary) plus `consistency`
7429
- * (`transactional|eventual`) / `updateMode` (`mutation|stream`). These are NOT
7430
- * re-defined; they are the same shared types B (#121) added.
7431
- */
7432
- interface AggregateOptions {
7433
- /** Named maintenance preset (typically `'counter'`); reuses {@link RelationPattern}. */
7434
- pattern?: RelationPattern;
7435
- /** The scalar aggregation this field maintains (`count()` / `max('createdAt')`). */
7436
- value: AggregateValue;
7437
- /** Cross-entity maintenance triggers / consistency / update-mode (reused from B). */
7438
- write?: RelationWriteOptions;
7439
- }
7440
- /**
7441
- * Declaration metadata recorded by an `@aggregate` field decorator (issue #122).
7442
- *
7443
- * An `@aggregate` is a **scalar** field (`postCount!: number`,
7444
- * `lastPostAt!: string`) whose value is derived — by aggregating rows of a source
7445
- * entity resolved by {@link targetFactory} + {@link keyBinding} — rather than
7446
- * stored directly. It is therefore recorded in its **own** metadata array
7447
- * (`EntityMetadata.aggregates`), distinct from both {@link FieldMetadata} (a
7448
- * plain stored attribute) and {@link RelationMetadata} (a navigation yielding
7449
- * model instances): an aggregate carries a source binding like a relation, but
7450
- * surfaces a scalar value like a field. The collector path mirrors relations
7451
- * (decorator pushes, `@model` drains).
7452
- *
7453
- * Issue #122 scope is the declaration surface + metadata pass-through only; the
7454
- * aggregation effect's compile / runtime (#124/#125/#127) is out of scope.
7455
- */
7456
- interface AggregateMetadata {
7457
- /** The scalar field the aggregate value is written to. */
7458
- propertyName: string;
7459
- /** Resolves the source entity whose rows are aggregated. */
7460
- targetFactory: () => new (...args: unknown[]) => unknown;
7461
- /** The key binding selecting the source rows (target key field → source field). */
7462
- keyBinding: Record<string, string>;
7463
- /** The aggregate preset / value / maintenance-write declaration. */
7464
- options: AggregateOptions;
7465
- }
7466
- interface EmbeddedMetadata {
7467
- propertyName: string;
7468
- modelFactory: () => new (...args: unknown[]) => unknown;
7469
- }
7470
- interface EntityMetadata {
7471
- tableName: string;
7472
- prefix: string;
7473
- fields: FieldMetadata[];
7474
- primaryKey: KeyDefinition | null;
7475
- gsiDefinitions: GsiDefinition[];
7476
- relations: RelationMetadata[];
7477
- /** Scalar `@aggregate` fields (Epic #118 §5.2 counter / latest; issue #122). */
7478
- aggregates: AggregateMetadata[];
7479
- embeddedFields: EmbeddedMetadata[];
7480
- /**
7481
- * The model's maintenance kind (issue #152). `'entity'` (default; also assumed
7482
- * when absent, for hand-built metadata in tests) is a plain stored entity;
7483
- * `'materializedView'` / `'sparseView'` mark it a maintained view row fed by
7484
- * {@link maintainedFrom}.
7485
- */
7486
- kind?: ModelKind;
7487
- /**
7488
- * Optional human-readable description of the entity (issue #154), supplied via
7489
- * `@model({ description })`. Pure documentation metadata — it does NOT affect
7490
- * storage, keys, or any runtime behaviour. When present it is propagated to the
7491
- * entity entry in `manifest.json` ({@link ManifestEntity.description}) and
7492
- * surfaces as the class docstring in the generated Python `types.py`. Absent →
7493
- * byte-for-byte unchanged output (backward compatible).
7494
- */
7495
- description?: string;
7496
- /**
7497
- * Class-level `@maintainedFrom(...)` declarations (issue #152). Non-empty only on
7498
- * a `materializedView` / `sparseView` model; each is one source slice the view
7499
- * row is maintained from. The adapter lowers these to the maintenance graph's
7500
- * `ViewDefinition` IR (replacing the old `defineView` / `ViewRegistry` path).
7501
- * Absent / empty for a plain entity.
7502
- */
7503
- maintainedFrom?: MaintainedFromMetadata[];
7504
- /**
7505
- * Whether the model is flagged **CDC-parseable** by `@cdcProjected()` (issue
7506
- * #153). It carries NO runtime side effect — it is purely a compile-/registration-
7507
- * time capability marker: `Model.fromChange(event)` (and, transitively,
7508
- * `DDBModel.subscribe` routing to this model) is gated on it, and it is the SSoT
7509
- * the codegen reads to emit the `CdcModelRegistry` module augmentation that types
7510
- * `subscribe`'s handlers by model name. `true` only on a model carrying the
7511
- * decorator; absent (⇒ falsy) for every plain entity, so a non-`@cdcProjected`
7512
- * model is byte-for-byte unchanged (backward compatible). Deliberately kept OFF
7513
- * the manifest / bridge (it is a host-runtime + TS-typing concern, like the
7514
- * maintenance signals — not a physical-schema fact like {@link ttlAttribute}).
7515
- */
7516
- cdcProjected?: boolean;
7517
- /**
7518
- * The physical attribute name of the model's `@ttl` field, when one is declared
7519
- * (issue #172, Epic #167 — CFn generator C4). DynamoDB TTL requires a single
7520
- * Number attribute holding epoch seconds; `@model` derives this from the (unique)
7521
- * field carrying {@link FieldMetadata.ttl} and throws if more than one field on the
7522
- * SAME model is `@ttl` (the cross-entity, same-physical-table single-TTL rule is
7523
- * enforced downstream, at manifest build, where all entities on a table are known).
7524
- * A non-key field's physical attribute name is its property name, so this is the
7525
- * `@ttl` field's property name. Propagated to the manifest
7526
- * ({@link ManifestEntity.ttlAttribute}) — the SSoT the CloudFormation emitter reads
7527
- * to render `TimeToLiveSpecification`. Absent when the model declares no `@ttl`.
7528
- */
7529
- ttlAttribute?: string;
7530
- }
7531
-
7532
- /**
7533
- * Decorator-metadata → maintenance-graph `ViewDefinition` adapter (issue #152).
7534
- *
7535
- * ## What this replaces
7536
- *
7537
- * The old `defineView` / `defineVersioned` builders (deleted) registered
7538
- * `ViewDefinition`s into a side `ViewRegistry`, which the maintenance graph read.
7539
- * That builder carried a registration side-effect + a `generation` counter (a
7540
- * silent-drop guard). This adapter removes that whole path: it derives the SAME
7541
- * `ViewDefinition` IR purely from the declarative `MetadataRegistry` — the
7542
- * Contract-layer SSoT, whose own `generation` counter is the single
7543
- * invalidation source — so a view declared via `@model({ kind })` + `@maintainedFrom`
7544
- * (and a versioned relation declared via `@hasOne/@hasMany` `pattern`) is discovered
7545
- * with no separate registry.
7546
- *
7547
- * ## IR invariance
7548
- *
7549
- * The `ViewDefinition` / `ViewSourceSlice` shapes are UNCHANGED. The maintenance
7550
- * graph, the K (#130) stream/drain, and the L (#131) rebuild consume the adapter's
7551
- * output exactly as they consumed `ViewRegistry.getAll()`. Only the SOURCE of the
7552
- * definitions moved from a builder side-effect to declarative metadata.
7553
- *
7554
- * ## Order independence (issue #152 hardening)
7555
- *
7556
- * Stacked `@maintainedFrom` declarations carry no order meaning. The adapter rejects
7557
- * — symmetrically, so the error is identical whichever order the decorators are
7558
- * stacked — any two declarations on the same view that:
7559
- * 1. write the SAME projection target attribute (ambiguous duplicate projection);
7560
- * 2. maintain the SAME `collection.field`;
7561
- * 3. bind the SAME view-key field to a DIFFERENT source path (the view-row identity
7562
- * would be inconsistent across sources).
7563
- * A legitimate multi-source view (different sources filling DIFFERENT fields, all
7564
- * binding the SAME key the SAME way) is fine.
7565
- */
7566
-
7567
- type AnyModelClass = abstract new (...args: any[]) => any;
7568
- /** The view source slice IR the maintenance graph consumes (one per source × view). */
7569
- interface ViewSourceSlice {
7570
- readonly sourceClass: AnyModelClass;
7571
- readonly maintainedOn: readonly MaintainTrigger[];
7572
- readonly keys: Readonly<Record<string, EffectPath>>;
7573
- readonly project: ProjectionMap;
7574
- readonly collection?: {
7575
- readonly field: string;
7576
- readonly maxItems?: number;
7577
- readonly orderBy?: EffectPath;
7578
- readonly orderDir?: 'ASC' | 'DESC';
7579
- };
7580
- readonly predicate?: MembershipPredicate;
7581
- }
7582
- /** The view definition IR — an independent view row maintained from one or more sources. */
7583
- interface ViewDefinition {
7584
- readonly name: string;
7585
- readonly viewClass: AnyModelClass;
7586
- readonly pattern: 'materializedView' | 'sparseView';
7587
- readonly slices: readonly ViewSourceSlice[];
7588
- readonly updateMode?: 'mutation' | 'stream';
7589
- readonly consistency?: 'transactional' | 'eventual';
7590
- }
7591
- /**
7592
- * Build every {@link ViewDefinition} for a registry — from `@model({ kind })` +
7593
- * `@maintainedFrom` view models AND from versioned-pattern relations. This is the
7594
- * adapter the maintenance graph calls in place of the deleted `ViewRegistry.getAll()`.
7595
- *
7596
- * @param registry The models to scan. Defaults to `MetadataRegistry.getAll()`.
7597
- */
7598
- declare function collectViewDefinitions(registry?: ReadonlyMap<Function, EntityMetadata>): ViewDefinition[];
7599
-
7600
- export { type AnyOperationDefinition as $, type ContractSpec as A, type BatchResult as B, type CdcEmulatorOptions as C, type DynamoDBOperation as D, type EventLog as E, type FaultSpec as F, type QuerySpec as G, type CommandSpec as H, type TransactionSpec as I, type ContextSpec as J, SPEC_VERSION as K, type ParamSpec as L, type ModelStatic as M, type ParamDescriptor as N, type EntityRef as O, type PutInput as P, type QueryModelContract as Q, type ReplayOptions as R, type ShardId as S, type TransactWriteExecItem as T, type Unsubscribe as U, type ViewDefinition as V, type WriteExecOptions as W, type ConditionInput as X, type Param as Y, type DefinitionMap as Z, type OperationsDocument as _, type CdcMode as a, type ModelKind as a$, type Manifest as a0, type BridgeBundle as a1, type ConditionSpec as a2, type PreparedBody as a3, type CommandContractMethodSpec as a4, type CommandResolutionTarget as a5, type CompiledFragment as a6, type CompiledMutationPlan as a7, type ComposeSpec as a8, type CompositionPlanSpec as a9, type QueryContractMethodSpec as aA, type RangeConditionSpec as aB, type ReadOperationType as aC, type TransactionItemSpec as aD, type TransactionItemType as aE, type WhenSpec as aF, type WriteOperationType as aG, assertNoCrossFragmentMaintainCollision as aH, compileFragment as aI, compileMutationPlan as aJ, compileSingleFragmentPlan as aK, resetMaintenanceGraphCache as aL, resolveLifecycle as aM, resolveMaintainers as aN, type SelectableOf as aO, type PrimaryKeyOf as aP, type RequestContext as aQ, type Middleware as aR, type ReadRequestKind as aS, type CtxModel as aT, type ReadParams as aU, type ReadRequestCtx as aV, type Item as aW, type RetryPolicy as aX, type RetryOverride as aY, type KeyDefinition as aZ, type GsiDefinition as a_, type ContractCardinality as aa, type ContractCommandResult as ab, type ContractInputArity as ac, type ContractKeySpec as ad, type ContractKind as ae, type ContractResolution as af, type DerivedConditionCheck as ag, type DerivedEdgeWrite as ah, type DerivedIdempotencyGuard as ai, type DerivedMaintainOutbox as aj, type DerivedMaintainWrite as ak, type DerivedOutboxEvent as al, type DerivedUniqueGuard as am, type DerivedUpdate as an, type EntityRefResolver as ao, type ExecutionPlanSpec as ap, type FilterSpec as aq, MAX_TRANSACT_COMPOSE_ITEMS as ar, type ManifestEntity as as, type ManifestField as at, type ManifestFieldType as au, type ManifestGsi as av, type ManifestKey as aw, type ManifestRelation as ax, type ManifestTable as ay, type OperationSpec as az, type ChangeBatch as b, type ContractQueryParams as b$, type FieldOptions as b0, type DynamoType as b1, type ProjectionTransform as b2, type MaintainEvent as b3, type MembershipPredicate as b4, type MaintainConsistency as b5, type MaintainUpdateMode as b6, type MembershipPredicateOp as b7, type RelationOptions as b8, type AggregateOptions as b9, type BatchWriteRequest as bA, CONTRACT_RANGE_FANOUT_CONCURRENCY as bB, type CdcModelRegistry as bC, type CdcSubscribeHandlers as bD, type Change as bE, type CollectionEffect as bF, type CollectionOptions as bG, type Column as bH, type ColumnMap as bI, type CommandInputShape as bJ, type CommandMethod as bK, type CommandPlan as bL, type CommandResultKind as bM, type CommandSelectShape as bN, type CondSlot as bO, type ConditionCheckInput as bP, type Connection as bQ, type ContractCallSignature as bR, type ContractCommandParams as bS, type ContractComposeNode as bT, type ContractFromRef as bU, type ContractItem as bV, type ContractKeyFieldRef as bW, type ContractKeyInput as bX, type ContractKeyRef as bY, type ContractMethodOp as bZ, type ContractParamRef as b_, type AggregateValue as ba, type SelectBuilderSpec as bb, type RawCondition as bc, type ExecutionPlan as bd, type FieldMetadata as be, type ResolvedKey as bf, type Slot as bg, type PreparedWriteExecOptions as bh, type CommandReturn as bi, type ParallelOpResult as bj, type PreparedStatement as bk, type RelationMetadata as bl, type MaintainEffect as bm, type OperationDefinition as bn, type WriteDefinitionOptions as bo, type PartialQueryKeyOf as bp, type StrictSelectSpec as bq, type ReadDefinitionOptions as br, type EntityInput as bs, type UniqueQueryKeyOf as bt, type AggregateMetadata as bu, type BatchDeleteRequest as bv, type BatchGetOptions as bw, type BatchGetRequest as bx, BatchGetResult as by, type BatchPutRequest as bz, type ChangeEvent as c, PreparedReadStatement as c$, type CounterAggregate as c0, type CounterEffect as c1, type CtxBase as c2, DEFAULT_MAX_ATTEMPTS as c3, DEFAULT_RETRY_POLICY as c4, type DeleteOptions as c5, type DeriveEffect as c6, type DescriptorBinding as c7, ENTITY_WRITES_MARKER as c8, type EdgeEffect as c9, type MaintainItem as cA, type MaintainTrigger as cB, type MaintenanceGraph as cC, type MembershipEffect as cD, type ModelRef as cE, type MutateMode as cF, type MutateOptions as cG, type MutateParallelResult as cH, type MutateTransactionResult as cI, type MutationBody as cJ, type MutationDescriptorMap as cK, type MutationFragment as cL, type MutationInputProxy as cM, type MutationInputRef as cN, type MutationIntent as cO, type NumberParam as cP, type OperationKind as cQ, PREPARE_CACHE_MAX as cR, type ParamKind as cS, type ParamStructure as cT, type PersistCtx as cU, type PersistOrigin as cV, type PlannedCommandMethod as cW, type PreparedInputProxy as cX, type PreparedParamRef as cY, type PreparedReadExecOptions as cZ, type PreparedReadRoute as c_, type EffectPath as ca, type EmbeddedMetadata as cb, type EmitEffect as cc, type EntityWritesDefinition as cd, type EntityWritesShape as ce, type ExecutableCommandContract as cf, type ExecutableQueryContract as cg, type FilterInput as ch, type FragmentCondition as ci, type FragmentConditionOperatorObject as cj, type FragmentInput as ck, type GsiDefinitionMarker as cl, type GsiOptions as cm, type IdempotencyEffect as cn, type InProcessWriteDescriptor as co, type InlineSnapshotSpec as cp, type InputArity as cq, type KeyDefinitionMarker as cr, type KeySegment as cs, type KeySlot as ct, type KeyStructure as cu, type KeyedResult as cv, LIFECYCLE_CONTRACT_MARKER as cw, type LifecycleContract as cx, type LifecycleEffects as cy, type LiteralParam as cz, type ChangeEventName as d, executeCommandMethod as d$, type PreparedWriteRoute as d0, PreparedWriteStatement as d1, type ProjectionMap as d2, type ProjectionTransformOp as d3, type PutOptions as d4, type QueryEnvelopeResult as d5, type QueryKeyOf as d6, type QueryMethod as d7, type QueryResult as d8, type ReadEnvelope as d9, type StringParam as dA, TransactionContext as dB, type UniqueEffect as dC, type Updatable as dD, type UpdateOptions as dE, type ViewSourceSlice as dF, type WriteCtx as dG, type WriteDescriptor as dH, type WriteEnvelope as dI, type WriteInput as dJ, type WriteKind as dK, type WriteLifecyclePhase as dL, type WriteMiddleware as dM, type WriteRecorder as dN, type WriteResultProjection as dO, attachModelClass as dP, buildDeleteInput as dQ, buildMaintenanceGraph as dR, buildPutInput as dS, buildUpdateInput as dT, collectViewDefinitions as dU, cond as dV, contractOfMethodSpec as dW, definePlan as dX, entityWrites as dY, executeBatchGet as dZ, executeBatchWrite as d_, type ReadOpCtx as da, type ReadOpKind as db, type ReadRouteDescriptor as dc, type ReadRouteOptions as dd, type ReadRouteResult as de, type RecordedCompose as df, type RelationBuilder as dg, type RelationConsistency as dh, type RelationLimitOptions as di, type RelationPattern as dj, type RelationProjection as dk, type RelationReadOptions as dl, type RelationSelect as dm, type RelationSpec as dn, type RelationUpdateMode as dp, type RelationWriteOptions as dq, type RequiresEffect as dr, type Resolution as ds, type RetryInfo as dt, type RetryOperationKind as du, type SegmentSpec as dv, type SegmentedKey as dw, type SelectBuilder as dx, type SelectOf as dy, type SnapshotEffect as dz, type ChangeHandler as e, executeDelete as e0, executeKeyedBatchGet as e1, executePut as e2, executeQueryMethod as e3, executeRangeFanout as e4, executeTransaction as e5, executeUpdate as e6, from as e7, getEntityWrites as e8, gsi as e9, mintContractParamRef as eA, mutation as eB, param as eC, prepare as eD, preview as eE, publicCommandModel as eF, publicQueryModel as eG, query as eH, wholeKeysSentinel as eI, identity as ea, isColumn as eb, isCommandModelContract as ec, isCommandPlan as ed, isContractComposeNode as ee, isContractFromRef as ef, isContractKeyFieldRef as eg, isContractKeyRef as eh, isContractParamRef as ei, isEntityWritesDefinition as ej, isKeySegment as ek, isLifecycleContract as el, isMaintainTrigger as em, isMutationFragment as en, isMutationInputRef as eo, isParam as ep, isPlannedCommandMethod as eq, isPreparedParamRef as er, isQueryModelContract as es, isRetryableError as et, isRetryableTransactionCancellation as eu, k as ev, key as ew, lifecyclePhaseForIntent as ex, maintainTrigger as ey, mintContractKeyFieldRef as ez, type ClockMode as f, type ConcurrentRecomputeRef as g, type StartingPosition as h, type StreamViewType as i, type SubscribeHandler as j, type SubscribeHandlers as k, buildSubscribeHandler as l, type EntityMetadata as m, type Executor as n, type ReadExecOptions as o, type ExecutorResult as p, type BatchGetExecInput as q, type WriteResult as r, type UpdateInput as s, type DeleteInput as t, type BatchWriteExecItem as u, type BatchExecOptions as v, DDBModel as w, type QueryMethodSpec as x, type CommandModelContract as y, type CommandMethodSpec as z };
5163
+ export { type AnyOperationDefinition as $, type EventLog as A, type BatchGetExecInput as B, type CommandReturn as C, DDBModel as D, type EffectPath as E, type FaultSpec as F, type ReplayOptions as G, type ShardId as H, type MaintainTrigger as I, type BatchResult as J, type CdcMode as K, type ChangeBatch as L, type ModelStatic as M, type ChangeEventName as N, type ClockMode as O, type PreparedWriteExecOptions as P, type QueryModelContract as Q, type ReadExecOptions as R, type Slot as S, type TransactWriteExecItem as T, type UpdateInput as U, type StartingPosition as V, type WriteExecOptions as W, type StreamViewType as X, type SubscribeHandler as Y, type SubscribeHandlers as Z, buildSubscribeHandler as _, type ParallelOpResult as a, type SelectBuilder as a$, type TransactionDefinition as a0, type CompiledFragment as a1, type CompiledMutationPlan as a2, type DerivedConditionCheck as a3, type DerivedEdgeWrite as a4, type DerivedIdempotencyGuard as a5, type DerivedMaintainOutbox as a6, type DerivedMaintainWrite as a7, type DerivedOutboxEvent as a8, type DerivedUniqueGuard as a9, type GsiDefinitionMarker as aA, type GsiOptions as aB, type InlineSnapshotSpec as aC, type Item as aD, type KeyDefinitionMarker as aE, type KeySegment as aF, type KeySlot as aG, type KeyStructure as aH, type MutateAuthoringOptions as aI, type ParamDescriptor as aJ, type ParamStructure as aK, type PartialQueryKeyOf as aL, type PersistCtx as aM, type PersistOrigin as aN, type QueryKeyOf as aO, type QueryResult as aP, type RawCondition as aQ, type ReadOpCtx as aR, type ReadOpKind as aS, type ReadParams as aT, type ReadRequestCtx as aU, type ReadRequestKind as aV, type RelationBuilder as aW, type RelationSelect as aX, type RelationSpec as aY, type RequestContext as aZ, type SegmentSpec as a_, type DerivedUpdate as aa, type EntityRefResolver as ab, MAX_TRANSACT_COMPOSE_ITEMS as ac, assertNoCrossFragmentMaintainCollision as ad, compileFragment as ae, compileMutationPlan as af, compileSingleFragmentPlan as ag, resetMaintenanceGraphCache as ah, resolveLifecycle as ai, resolveMaintainers as aj, type SelectableOf as ak, type PrimaryKeyOf as al, type MembershipPredicateOp as am, publishQuery as an, publishCommand as ao, type RetryPolicy as ap, type Middleware as aq, type CdcModelRegistry as ar, type CdcSubscribeHandlers as as, type Change as at, type Column as au, type ColumnMap as av, type CondSlot as aw, type CtxBase as ax, type CtxModel as ay, type FilterInput as az, type PreparedBody as b, type SelectOf as b0, type UniqueQueryKeyOf as b1, type Updatable as b2, type WriteCtx as b3, type WriteInput as b4, type WriteKind as b5, type WriteMiddleware as b6, cond as b7, entityWrites as b8, getEntityWrites as b9, gsi as ba, identity as bb, k as bc, key as bd, mutate as be, preview as bf, when as bg, type PreparedStatement as c, type SegmentedKey as d, type ProjectionTransform as e, type MaintainEvent as f, type ProjectionMap as g, type MaintainConsistency as h, type MaintainUpdateMode as i, type MembershipPredicate as j, type QueryMethodSpec as k, type CommandModelContract as l, type CommandMethodSpec as m, type Executor as n, type DynamoDBOperation as o, type ExecutorResult as p, type PutInput as q, type WriteResult as r, type DeleteInput as s, type BatchWriteExecItem as t, type BatchExecOptions as u, type ChangeEvent as v, type CdcEmulatorOptions as w, type ChangeHandler as x, type Unsubscribe as y, type ConcurrentRecomputeRef as z };