graphddb 0.7.10 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +6 -6
  2. package/dist/cdc/index.d.ts +389 -5
  3. package/dist/cdc/index.js +4 -4
  4. package/dist/{chunk-AD6ZQTTE.js → chunk-GS4C5VGO.js} +2 -6
  5. package/dist/{chunk-DFUKGU2Q.js → chunk-HNY2EJPV.js} +216 -229
  6. package/dist/{chunk-EOJDN3SA.js → chunk-I4LEJ4TF.js} +3744 -6144
  7. package/dist/{chunk-3ZU2VW3L.js → chunk-L2NEDS7U.js} +582 -781
  8. package/dist/chunk-L4QRCHRQ.js +278 -0
  9. package/dist/chunk-LGHSZIEE.js +187 -0
  10. package/dist/chunk-N4NWYNGZ.js +1987 -0
  11. package/dist/{chunk-PDUVTYC5.js → chunk-XTWXMOHD.js} +0 -1
  12. package/dist/cli.js +63 -254
  13. package/dist/index.d.ts +23 -1550
  14. package/dist/index.js +94 -1850
  15. package/dist/internal/index.d.ts +84 -0
  16. package/dist/internal/index.js +701 -0
  17. package/dist/{maintenance-view-adapter-BAZ9uBGe.d.ts → key-DR7_lpyk.d.ts} +504 -1910
  18. package/dist/linter/index.d.ts +39 -7
  19. package/dist/linter/index.js +22 -4
  20. package/dist/{registry-LWE54Sdc.d.ts → linter-C-vypgut.d.ts} +22 -22
  21. package/dist/prepared-artifact-BpPgkXEo.d.ts +281 -0
  22. package/dist/spec/index.d.ts +506 -5
  23. package/dist/spec/index.js +22 -18
  24. package/dist/testing/index.d.ts +2 -3
  25. package/dist/testing/index.js +4 -4
  26. package/dist/transform/index.d.ts +1 -1
  27. package/dist/transform/index.js +4 -4
  28. package/dist/{types-BQLzTEqh.d.ts → types-2PMXEn5x.d.ts} +8 -10
  29. package/dist/types-BXLzIcQD.d.ts +450 -0
  30. package/docs/cdc-projection.md +5 -5
  31. package/docs/class-hydration.md +1 -1
  32. package/docs/cqrs-contract.md +28 -20
  33. package/docs/design-patterns.md +5 -5
  34. package/docs/docs-generation.md +6 -6
  35. package/docs/middleware.md +15 -15
  36. package/docs/mutation-command-derivation.md +52 -42
  37. package/docs/prepared-statements.md +14 -14
  38. package/docs/python-bridge.md +96 -65
  39. package/docs/spec.md +153 -124
  40. package/docs/testing.md +9 -8
  41. package/package.json +14 -4
  42. package/dist/chunk-3UD3XIF2.js +0 -860
  43. package/dist/chunk-MMVHOUM4.js +0 -24
  44. package/dist/from-change-Ty95KA8C.d.ts +0 -327
  45. package/dist/index-Dc7d8mWI.d.ts +0 -1089
  46. package/dist/relation-depth-BRS513Tq.d.ts +0 -36
@@ -1,5 +1,4 @@
1
- import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
2
- import { c as Param, E as ExpressionSpec, a0 as ParamKind, Y as TransactionItemSpec } from './types-BQLzTEqh.js';
1
+ import { a1 as Param, E as ExpressionSpec, a2 as ParamKind, T as TransactionSpec, X as TransactionItemSpec } from './types-2PMXEn5x.js';
3
2
 
4
3
  /**
5
4
  * CDC emulator types (issue #72). These types are **cdc-module-owned**; core
@@ -114,132 +113,6 @@ interface CdcEmulatorOptions {
114
113
  }
115
114
  type ShardId = string;
116
115
 
117
- /**
118
- * App-level throttle / transient-error retry for single-op sends (issue #111).
119
- *
120
- * GraphDDB's single-item ops (GetItem / Query / Put / Update / Delete) and the
121
- * read fan-out (relation resolution) used to throw on throttling
122
- * (`ProvisionedThroughputExceededException` / `ThrottlingException`) and rely
123
- * solely on the AWS SDK v3 default retry, so the LIBRARY itself guaranteed no
124
- * throttle-resilience. The batch ops already retry partial completion
125
- * (`UnprocessedKeys` / `UnprocessedItems`, see `src/operations/batch-retry.ts`);
126
- * this module is the SEPARATE, library-owned ERROR retry that wraps every
127
- * `docClient.send()` for the single ops via {@link RetryingExecutor}.
128
- *
129
- * The two retry layers are DISTINCT and must not be merged:
130
- * - (A) partial-batch retry (`batch-retry.ts`) — DynamoDB accepted the request
131
- * but could not process every key/item; the request itself did NOT error.
132
- * - (B) error retry (this module) — `send()` REJECTED with a throttle / transient
133
- * error and is retried whole.
134
- * They share {@link computeBackoffDelay} so backoff is consistent across both,
135
- * but they are different mechanisms.
136
- *
137
- * The default policy is ENABLED out of the box (see {@link DEFAULT_RETRY_POLICY}):
138
- * even with zero configuration a sensible exponential-backoff-with-jitter,
139
- * bounded-attempts retry applies. Users tune it globally via
140
- * `DDBModel.setRetryPolicy(...)` or per-call via the `retry?:` option on the
141
- * operation option bags; `retry: false` disables retry for that one call.
142
- *
143
- * NOTE on the AWS SDK's OWN retry: now that the library owns retry, a
144
- * `DynamoDBClient` left at the SDK default `maxAttempts: 3` retries
145
- * MULTIPLICATIVELY underneath this layer. Users should construct their client
146
- * with `maxAttempts: 1` so the library is the single source of truth for retry
147
- * (documented at `DDBModel.setClient` and in the README). The library does NOT
148
- * silently mutate a user-provided client.
149
- */
150
- /** The single-op kind a retry is wrapping — surfaced to {@link RetryPolicy.onRetry}. */
151
- type RetryOperationKind = 'get' | 'query' | 'batchGet' | 'put' | 'update' | 'delete' | 'batchWrite' | 'transactWrite';
152
- /** The context handed to {@link RetryPolicy.onRetry} before each backoff sleep. */
153
- interface RetryInfo {
154
- /** 1-based attempt number that just FAILED and is about to be retried. */
155
- readonly attempt: number;
156
- /** The throttle / transient error that triggered this retry. */
157
- readonly error: unknown;
158
- /** The backoff delay (ms) about to be slept before the next attempt. */
159
- readonly delayMs: number;
160
- /** Which single-op kind is being retried. */
161
- readonly operation: RetryOperationKind;
162
- }
163
- /**
164
- * A retry policy for the single-op error-retry layer (issue #111). Every field is
165
- * optional and resolved INDEPENDENTLY, in layers (see {@link resolveRetryPolicy}):
166
- * a per-call override field wins, else the globally-configured policy's field
167
- * (from {@link import('../model/DDBModel.js').DDBModel.setRetryPolicy}), else
168
- * {@link DEFAULT_RETRY_POLICY}. So a partial per-call override (e.g.
169
- * `{ maxAttempts: 3 }`) overrides ONLY that field and INHERITS the rest from the
170
- * global policy — a global `onRetry` / `jitter` is NOT dropped. A policy value of
171
- * `false` on an option bag disables retry entirely for that call.
172
- */
173
- interface RetryPolicy {
174
- /**
175
- * Maximum number of attempts (the FIRST try counts as attempt 1). `1` disables
176
- * retry (a single try, no backoff). Default {@link DEFAULT_MAX_ATTEMPTS}.
177
- */
178
- readonly maxAttempts?: number;
179
- /**
180
- * Compute the base backoff delay (ms) for a 1-based retry attempt, BEFORE jitter.
181
- * Defaults to {@link computeBackoffDelay} (`min(1000, 50*2^(attempt-1))`), the
182
- * same ramp the batch partial-retry layer uses, so backoff is consistent across
183
- * the two layers.
184
- */
185
- readonly computeDelay?: (attempt: number) => number;
186
- /**
187
- * Jitter strategy applied to the base delay:
188
- * - `'full'` (default) — uniform random in `[0, base]` (AWS "full jitter"),
189
- * the recommended strategy to de-correlate retries across clients.
190
- * - `'none'` — use the base delay verbatim (deterministic; handy in tests).
191
- */
192
- readonly jitter?: 'full' | 'none';
193
- /**
194
- * Classify whether an error is retryable. Defaults to {@link isRetryableError}
195
- * (throttle + 5xx / transient). A custom classifier fully replaces the default —
196
- * it should still avoid retrying validation / conditional-check failures.
197
- */
198
- readonly isRetryable?: (error: unknown) => boolean;
199
- /**
200
- * Observability hook (issue #111 §8): invoked BEFORE each backoff sleep with the
201
- * attempt number, the triggering error, the computed (post-jitter) delay, and the
202
- * operation kind. Never invoked on the terminal success or the terminal failure.
203
- */
204
- readonly onRetry?: (info: RetryInfo) => void;
205
- }
206
- /** Default cap on attempts: the first try plus up to 9 retries (mirrors the batch cap). */
207
- declare const DEFAULT_MAX_ATTEMPTS = 10;
208
- /**
209
- * The default, always-on retry policy (issue #111 §3). Exponential backoff (the
210
- * shared {@link computeBackoffDelay} ramp) + full jitter, capped at
211
- * {@link DEFAULT_MAX_ATTEMPTS} attempts, retrying throttle / 5xx / transient
212
- * errors only (never validation / conditional-check failures).
213
- */
214
- declare const DEFAULT_RETRY_POLICY: Required<Pick<RetryPolicy, 'maxAttempts' | 'computeDelay' | 'jitter' | 'isRetryable'>>;
215
- /**
216
- * Default error classifier (issue #111 §5). Retryable when the error is a known
217
- * throttle / transient fault, an AWS SDK error flagged `$retryable`, or a 5xx /
218
- * 429 status. NEVER retryable for validation / conditional-check / not-found /
219
- * access-denied — those are deterministic and would just burn the attempt budget.
220
- *
221
- * NOTE: a {@link https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactWriteItems.html
222
- * TransactionCanceledException} is handled SEPARATELY by
223
- * {@link isRetryableTransactionCancellation}; this function treats it as
224
- * non-retryable so the transact path can inspect its `CancellationReasons` first.
225
- */
226
- declare function isRetryableError(error: unknown): boolean;
227
- /**
228
- * Classify a `TransactionCanceledException` (issue #111 §6). Inspects the
229
- * `CancellationReasons`:
230
- * - ANY `ConditionalCheckFailed` reason ⇒ NOT retryable (a deterministic
231
- * condition failure; retrying would just fail again) — return `false`.
232
- * - Otherwise, when EVERY non-`None` reason is a throttle / conflict code
233
- * (`ThrottlingError` / `ProvisionedThroughputExceeded` / `TransactionConflict`)
234
- * ⇒ retryable — return `true`.
235
- * - A cancellation with no inspectable reasons, or any other reason code, is
236
- * treated conservatively as NOT retryable.
237
- *
238
- * Only meaningful for an error whose name is `TransactionCanceledException`; the
239
- * caller checks that first.
240
- */
241
- declare function isRetryableTransactionCancellation(error: unknown): boolean;
242
-
243
116
  interface RangeCondition {
244
117
  operator: 'begins_with';
245
118
  key: string;
@@ -280,17 +153,6 @@ type DynamoDBOperation = GetItemOperation | QueryOperation | BatchGetItemOperati
280
153
  interface ExecutionPlan {
281
154
  operations: DynamoDBOperation[];
282
155
  }
283
- type ResolvedKey = {
284
- type: 'pk';
285
- partial: boolean;
286
- inputFieldNames: string[];
287
- } | {
288
- type: 'gsi';
289
- indexName: string;
290
- unique: boolean;
291
- partial: boolean;
292
- inputFieldNames: string[];
293
- };
294
156
 
295
157
  /**
296
158
  * Internal brand identifying a {@link RawCondition} produced by {@link cond}.
@@ -467,27 +329,6 @@ interface WriteOptions<T> {
467
329
  readonly condition?: WriteCondition<T>;
468
330
  }
469
331
 
470
- /**
471
- * Internal brand symbol identifying a compiled select builder at runtime.
472
- * Consumers should not depend on this; it is used by the execution layer to
473
- * distinguish a builder instance from a plain select object.
474
- */
475
- declare const SELECT_BUILDER: unique symbol;
476
- /**
477
- * The normalized spec a select builder resolves to. This is the shape the
478
- * planner / traversal layer consumes. `filter` is the declarative server-side
479
- * DynamoDB FilterExpression input.
480
- *
481
- * @internal
482
- */
483
- interface SelectBuilderSpec {
484
- readonly [SELECT_BUILDER]: true;
485
- select: Record<string, unknown>;
486
- filter?: Record<string, unknown>;
487
- limit?: number;
488
- after?: string;
489
- order?: 'ASC' | 'DESC';
490
- }
491
332
  /**
492
333
  * Top-level select builder produced by `Model.project(...)`.
493
334
  *
@@ -827,65 +668,6 @@ type AtLeastOneKey<T> = {
827
668
  */
828
669
  type PartialQueryKeyOf<C> = QueryKeyOf<C> extends infer U ? U extends Record<string, unknown> ? AtLeastOneKey<U> : never : never;
829
670
 
830
- interface PutOptions<T = unknown> {
831
- condition?: WriteCondition<T>;
832
- /**
833
- * Per-call throttle / transient-error retry override (issue #111). A
834
- * {@link RetryPolicy} replaces the global policy for this call; `false` disables
835
- * retry for this call. Omit to use the configured (or built-in default) policy.
836
- */
837
- retry?: RetryOverride;
838
- /**
839
- * Host-injected per-call write context (issue #50 / #139) — exposed to every
840
- * write hook (W1–W5) as `ctx.context`. A host-language value, NEVER serialized
841
- * into the SSoT / `operations.json` (the bridge #48 is unaffected). Absent ⇒ `{}`.
842
- */
843
- context?: RequestContext;
844
- }
845
- interface UpdateOptions<T = unknown> {
846
- condition?: WriteCondition<T>;
847
- /**
848
- * Per-call throttle / transient-error retry override (issue #111). A
849
- * {@link RetryPolicy} replaces the global policy for this call; `false` disables
850
- * retry for this call.
851
- */
852
- retry?: RetryOverride;
853
- /**
854
- * How to re-derive a GSI key whose composing fields are changed but **not all**
855
- * available from `{ ...key, ...changes }` (issue #115).
856
- *
857
- * - **unset (default)** — refuse with an explicit error rather than let the
858
- * index silently rot. The error names the index, the changed field, and the
859
- * missing field(s). The happy path (all composing fields available) re-derives
860
- * in the SAME `UpdateExpression` and is unaffected by this option.
861
- * - `'read-modify-write'` — read the current item, merge `{ ...item, ...changes }`,
862
- * re-derive every affected GSI key from the merged image, and write back under
863
- * an optimistic condition (the item must still exist / be unchanged). This costs
864
- * one extra read and is NOT atomic with the original update, but always re-derives
865
- * correctly.
866
- */
867
- rederive?: 'read-modify-write';
868
- /**
869
- * Host-injected per-call write context (issue #50 / #139) — exposed to every
870
- * write hook (W1–W5) as `ctx.context`. NEVER serialized. Absent ⇒ `{}`.
871
- */
872
- context?: RequestContext;
873
- }
874
- interface DeleteOptions<T = unknown> {
875
- condition?: WriteCondition<T>;
876
- /**
877
- * Per-call throttle / transient-error retry override (issue #111). A
878
- * {@link RetryPolicy} replaces the global policy for this call; `false` disables
879
- * retry for this call.
880
- */
881
- retry?: RetryOverride;
882
- /**
883
- * Host-injected per-call write context (issue #50 / #139) — exposed to every
884
- * write hook (W1–W5) as `ctx.context`. NEVER serialized. Absent ⇒ `{}`.
885
- */
886
- context?: RequestContext;
887
- }
888
-
889
671
  interface PutInput {
890
672
  TableName: string;
891
673
  Item: Record<string, unknown>;
@@ -893,8 +675,6 @@ interface PutInput {
893
675
  ExpressionAttributeNames?: Record<string, string>;
894
676
  ExpressionAttributeValues?: Record<string, unknown>;
895
677
  }
896
- declare function buildPutInput(modelClass: Function, item: Record<string, unknown>, options?: PutOptions): PutInput;
897
- declare function executePut(modelClass: Function, item: Record<string, unknown>, options?: PutOptions): Promise<void>;
898
678
 
899
679
  interface UpdateInput {
900
680
  TableName: string;
@@ -904,8 +684,6 @@ interface UpdateInput {
904
684
  ExpressionAttributeValues?: Record<string, unknown>;
905
685
  ConditionExpression?: string;
906
686
  }
907
- declare function buildUpdateInput(modelClass: Function, entity: Record<string, unknown>, changes: Record<string, unknown>, options?: UpdateOptions): UpdateInput;
908
- declare function executeUpdate(modelClass: Function, entity: Record<string, unknown>, changes: Record<string, unknown>, options?: UpdateOptions): Promise<void>;
909
687
 
910
688
  interface DeleteInput {
911
689
  TableName: string;
@@ -914,8 +692,96 @@ interface DeleteInput {
914
692
  ExpressionAttributeNames?: Record<string, string>;
915
693
  ExpressionAttributeValues?: Record<string, unknown>;
916
694
  }
917
- declare function buildDeleteInput(modelClass: Function, keyObj: Record<string, unknown>, options?: DeleteOptions): DeleteInput;
918
- 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
+ }
919
785
 
920
786
  /** Result of a read operation (GetItem / Query / BatchGetItem). */
921
787
  interface ExecutorResult {
@@ -1057,7 +923,7 @@ interface Executor {
1057
923
  * registry / runtime machinery the read hooks (#138) introduced.
1058
924
  *
1059
925
  * A write {@link Middleware} hook is host-only, runtime-registered (via
1060
- * `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
1061
927
  * the {@link import('../client/ClientManager.js').ClientManager} host-runtime
1062
928
  * singleton and never touches the planner / spec generator / `operations.json`, so
1063
929
  * the TS↔Python bridge (#48) is unaffected. It is **unrestricted by intent**: a
@@ -1210,7 +1076,7 @@ interface WriteMiddleware {
1210
1076
  * module implements the **read** hook points R1–R5 and their context objects.
1211
1077
  *
1212
1078
  * A {@link Middleware} is a host-only, runtime-registered object (registered via
1213
- * `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
1214
1080
  * **never serialized** — they live only on the {@link import('../client/ClientManager.js').ClientManager}
1215
1081
  * host-runtime singleton and never touch the planner / spec generator /
1216
1082
  * `operations.json`, so the TS↔Python bridge (#48) is unaffected. Hooks are
@@ -1394,7 +1260,7 @@ type ProjectionOf<T, Sel> = Sel extends SelectableOf<T> ? Sel : SelectOf<Sel>;
1394
1260
  * usages keep compiling, while the precise inference is available when the
1395
1261
  * constructor type is supplied (as it is from `DDBModel.asModel()`).
1396
1262
  */
1397
- type AnyModelClass$1 = abstract new (...args: any[]) => DDBModel;
1263
+ type AnyModelClass = abstract new (...args: any[]) => DDBModel;
1398
1264
  /**
1399
1265
  * The accepted PK / GSI key type for `list()` / `explain()`.
1400
1266
  *
@@ -1404,24 +1270,24 @@ type AnyModelClass$1 = abstract new (...args: any[]) => DDBModel;
1404
1270
  * {@link QueryKey}. Derived from the model class when available, otherwise
1405
1271
  * permissive.
1406
1272
  */
1407
- type ListKey<C> = [C] extends [AnyModelClass$1] ? PartialQueryKeyOf<C> : Record<string, unknown>;
1273
+ type ListKey<C> = [C] extends [AnyModelClass] ? PartialQueryKeyOf<C> : Record<string, unknown>;
1408
1274
  /**
1409
1275
  * The accepted key type for `query()` — only keys guaranteed to return a
1410
1276
  * single item (PK + unique GSIs). Derived from the model class when available.
1411
1277
  */
1412
- type QueryKey<C> = [C] extends [AnyModelClass$1] ? UniqueQueryKeyOf<C> : Record<string, unknown>;
1278
+ type QueryKey<C> = [C] extends [AnyModelClass] ? UniqueQueryKeyOf<C> : Record<string, unknown>;
1413
1279
  /**
1414
1280
  * The accepted key type for `delete()` (PK only). Derived from the model
1415
1281
  * class when available.
1416
1282
  */
1417
- type DeleteKey<C> = [C] extends [AnyModelClass$1] ? PrimaryKeyOf<C> : Record<string, unknown>;
1283
+ type DeleteKey<C> = [C] extends [AnyModelClass] ? PrimaryKeyOf<C> : Record<string, unknown>;
1418
1284
  /**
1419
1285
  * The accepted *explicit* key type for `update()` — the base-table primary key
1420
1286
  * only. DynamoDB `UpdateItem` can target an item solely by its base-table PK
1421
1287
  * (GSI-based updates are impossible), so this is `PrimaryKeyOf<C>`, not
1422
1288
  * `UniqueQueryKeyOf<C>`. Derived from the model class when available.
1423
1289
  */
1424
- type UpdateExplicitKey<C> = [C] extends [AnyModelClass$1] ? PrimaryKeyOf<C> : Record<string, unknown>;
1290
+ type UpdateExplicitKey<C> = [C] extends [AnyModelClass] ? PrimaryKeyOf<C> : Record<string, unknown>;
1425
1291
  /**
1426
1292
  * Options accepted by `list()` (third argument). The projection is supplied as
1427
1293
  * the separate second `select` argument, symmetric with `query()`.
@@ -1595,145 +1461,6 @@ interface ModelStatic<T extends DDBModel, C = unknown> {
1595
1461
  deleteItem(key: DeleteKey<C>, options?: WriteOptions<T>): Promise<void>;
1596
1462
  }
1597
1463
 
1598
- type TransactWriteItemInput = {
1599
- Put: PutInput;
1600
- } | {
1601
- Update: UpdateInput;
1602
- } | {
1603
- Delete: DeleteInput;
1604
- } | {
1605
- ConditionCheck: ConditionCheckInput;
1606
- };
1607
- declare function attachModelClass<T extends DDBModel>(modelStatic: ModelStatic<T>, modelClass: new (...args: unknown[]) => T): ModelStatic<T>;
1608
- /**
1609
- * Per-item capture descriptor recorded alongside each transact item, so the
1610
- * write-capture seam (issue #72) can emit a labelled raw record after the
1611
- * transaction commits. `TransactWriteItems` returns no images (spec §14), so
1612
- * these carry the keys + the new item for Put/Update and keys only for Delete.
1613
- */
1614
- interface TransactCaptureMeta {
1615
- modelName?: string;
1616
- op: 'put' | 'update' | 'delete';
1617
- table: string;
1618
- keys: {
1619
- pk: string;
1620
- sk: string;
1621
- };
1622
- newItem?: Record<string, unknown>;
1623
- }
1624
- /**
1625
- * One recorded logical write op of an imperative {@link TransactionContext}
1626
- * (issue #139). Captured at `tx.put/update/delete` call time so the write hooks
1627
- * W1 (`write.before`) can run on each op — and rebuild its physical item from the
1628
- * possibly-mutated input — before the batch composes / commits. A `conditionCheck`
1629
- * records NO logical op (it is a read-only assertion, not a logical write).
1630
- */
1631
- interface LogicalWriteOp {
1632
- kind: WriteKind;
1633
- modelClass: Function;
1634
- item?: Record<string, unknown>;
1635
- key?: Record<string, unknown>;
1636
- changes?: Record<string, unknown>;
1637
- options?: PutOptions & UpdateOptions & DeleteOptions;
1638
- }
1639
- declare class TransactionContext {
1640
- private readonly items;
1641
- private readonly captureMeta;
1642
- /**
1643
- * The logical write ops, parallel to {@link items} but EXCLUDING `conditionCheck`
1644
- * entries (those are read-only assertions, recorded only in `items`). Used by the
1645
- * write-hook path (#139) to run W1 on each logical op and recompose the batch.
1646
- * A `null` entry marks an `items` slot that has no logical op (a ConditionCheck),
1647
- * so the eager and logical arrays can be re-aligned at commit.
1648
- */
1649
- private readonly logicalOps;
1650
- get itemCount(): number;
1651
- put(model: ModelStatic<DDBModel>, item: Record<string, unknown>, options?: PutOptions): void;
1652
- update(model: ModelStatic<DDBModel>, entity: Record<string, unknown>, changes: Record<string, unknown>, options?: UpdateOptions): void;
1653
- delete(model: ModelStatic<DDBModel>, key: Record<string, unknown>, options?: DeleteOptions): void;
1654
- /**
1655
- * Add a read-only `ConditionCheck` assertion on a keyed item (issue #81). The
1656
- * item is **not** mutated; the `options.condition` (e.g.
1657
- * `{ attributeExists: 'PK' }` to require the row exists) is asserted, and a
1658
- * failed assertion cancels the **whole** `TransactWriteItems` atomically. This
1659
- * is the foundation for referential-integrity derivation (proposal: `requires
1660
- * <Entity> exists`). A ConditionCheck emits no change-capture record (it writes
1661
- * nothing).
1662
- *
1663
- * @throws if `options.condition` is missing — a ConditionCheck without an
1664
- * assertion is meaningless.
1665
- */
1666
- conditionCheck(model: ModelStatic<DDBModel>, key: Record<string, unknown>, options: {
1667
- condition: Record<string, unknown>;
1668
- }): void;
1669
- /** @internal */
1670
- getTransactItems(): TransactWriteItemInput[];
1671
- /** @internal — the recorded logical write ops (issue #139 W1), `null` per ConditionCheck slot. */
1672
- getLogicalOps(): readonly (LogicalWriteOp | null)[];
1673
- /** @internal — capture descriptors for the write-capture seam (issue #72). */
1674
- getCaptureMeta(): TransactCaptureMeta[];
1675
- private assertWithinLimit;
1676
- }
1677
- declare function executeTransaction(fn: (tx: TransactionContext) => void | Promise<void>, options?: {
1678
- context?: RequestContext;
1679
- }): Promise<void>;
1680
-
1681
- interface BatchGetRequest {
1682
- model: ModelStatic<DDBModel>;
1683
- keys: Record<string, unknown>[];
1684
- }
1685
- /**
1686
- * Per-call options for {@link DDBModel.batchGet} (issue #142).
1687
- */
1688
- interface BatchGetOptions {
1689
- /**
1690
- * Host-injected per-call read context (issue #50 / #138 / #142) — exposed to
1691
- * every read hook (R1/R4/R5 at the `batchGet` request level, R2/R3/R5 on each
1692
- * underlying `BatchGetItem` op) as `ctx.context`, and threaded UNCHANGED to
1693
- * every physical op so a global hook (e.g. a tenant-scope R2 FilterExpression
1694
- * or an observability probe) applies across the whole multi-key batch read. A
1695
- * host-language value, NEVER serialized into the SSoT / `operations.json` (the
1696
- * bridge #48 is unaffected). Absent ⇒ `{}`.
1697
- */
1698
- context?: RequestContext;
1699
- }
1700
- interface BatchPutRequest {
1701
- type: 'put';
1702
- model: ModelStatic<DDBModel>;
1703
- item: Record<string, unknown>;
1704
- options?: PutOptions;
1705
- }
1706
- interface BatchDeleteRequest {
1707
- type: 'delete';
1708
- model: ModelStatic<DDBModel>;
1709
- key: Record<string, unknown>;
1710
- options?: DeleteOptions;
1711
- }
1712
- type BatchWriteRequest = BatchPutRequest | BatchDeleteRequest;
1713
- declare class BatchGetResult {
1714
- private readonly groups;
1715
- constructor(groups: Map<Function, Record<string, unknown>[]>);
1716
- get<T extends DDBModel>(model: ModelStatic<T>): Partial<T>[];
1717
- }
1718
- /**
1719
- * The multi-key, multi-model batch-read core (issue #142). Wraps the request
1720
- * entry in the request-level read hooks R1 (`read.before`) / R4
1721
- * (`read.afterFetch`) / R5 (`read.onError`) with request kind `'batchGet'`, and
1722
- * each underlying physical `BatchGetItem` op (per table/chunk) in R2/R3/R5 via
1723
- * {@link executeBatchGetForTable}. The read middleware runtime is built ONCE here
1724
- * from `options.context` and threaded UNCHANGED to every op, so a global hook
1725
- * (e.g. a tenant-scope R2 FilterExpression) applies across the whole batch.
1726
- *
1727
- * R1 sees the mutable batch `requests` on `ctx.params.requests`; the library
1728
- * re-reads them after R1 runs, so a hook may add / drop / mutate requests before
1729
- * any op is issued and may `throw` to cancel the whole batch. R4 may transform /
1730
- * replace the assembled {@link BatchGetResult}; R5 may recover by returning one.
1731
- * With no middleware registered the shared `NO_MIDDLEWARE` runtime keeps the hot
1732
- * path unchanged (the zero-overhead fast path).
1733
- */
1734
- declare function executeBatchGet(requests: BatchGetRequest[], options?: BatchGetOptions): Promise<BatchGetResult>;
1735
- declare function executeBatchWrite(requests: BatchWriteRequest[]): Promise<void>;
1736
-
1737
1464
  /**
1738
1465
  * Intermediate representation (IR) produced by the parameterized definition DSL
1739
1466
  * (issue #41). This is the **in-memory, typed** representation consumed by the
@@ -1831,26 +1558,6 @@ interface ConditionTree<X = never> {
1831
1558
  readonly not?: ConditionInput<X>;
1832
1559
  readonly [field: string]: ConditionLeaf<X> | ConditionOperatorObject<X> | readonly ConditionInput<X>[] | ConditionInput<X> | undefined;
1833
1560
  }
1834
- /** Options accepted by the write `define*` entry points (issue #46). */
1835
- interface WriteDefinitionOptions {
1836
- /** Optional declarative write condition (subset). */
1837
- readonly condition?: ConditionInput;
1838
- /**
1839
- * Optional human-readable description of the command use case (issue #154). Pure
1840
- * documentation — propagated to `operations.json` and the generated Python
1841
- * repository-method docstring. Omit for unchanged output.
1842
- */
1843
- readonly description?: string;
1844
- }
1845
- /** Options accepted by the read `define*` entry points (issue #154). */
1846
- interface ReadDefinitionOptions {
1847
- /**
1848
- * Optional human-readable description of the query use case (issue #154). Pure
1849
- * documentation — propagated to `operations.json` and the generated Python
1850
- * repository-method docstring. Omit for unchanged output.
1851
- */
1852
- readonly description?: string;
1853
- }
1854
1561
  /**
1855
1562
  * Identifies the entity an operation targets. `name` is the model class name
1856
1563
  * (entity name); `modelClass` is retained so the planner can resolve metadata
@@ -1896,8 +1603,8 @@ type ParamStructure = {
1896
1603
  /**
1897
1604
  * The typed IR for one definition. Generic over the entity, the parameterized
1898
1605
  * structure(s), the (optional) select projection, and the collected params map
1899
- * so that all type information survives into the value returned by
1900
- * `defineQueries` / `defineCommands`.
1606
+ * so that all type information survives into the operation IR the contract layer
1607
+ * (`publishQuery` / `publishCommand`) lowers to via `opToDefinition`.
1901
1608
  *
1902
1609
  * Field presence by `operation`:
1903
1610
  *
@@ -1937,7 +1644,7 @@ interface OperationDefinition<T extends DDBModel, Op extends OperationKind, K, S
1937
1644
  readonly condition?: ConditionInput;
1938
1645
  /**
1939
1646
  * Optional human-readable description of the definition (issue #154), supplied
1940
- * via the entry-point options (`defineQuery(..., { description })` etc.). Pure
1647
+ * via a contract method's `description` field. Pure
1941
1648
  * documentation — propagated to the serialized {@link
1942
1649
  * import('../spec/types.js').QuerySpec.description} /
1943
1650
  * {@link import('../spec/types.js').CommandSpec.description} and to the generated
@@ -1951,62 +1658,7 @@ interface OperationDefinition<T extends DDBModel, Op extends OperationKind, K, S
1951
1658
  }
1952
1659
  /** Any operation definition node, regardless of its type parameters. */
1953
1660
  type AnyOperationDefinition = OperationDefinition<any, OperationKind, unknown, unknown, unknown, Record<string, ParamDescriptor>>;
1954
- /**
1955
- * A map of definition name → {@link OperationDefinition}, as accepted by
1956
- * `defineQueries` / `defineCommands` and returned (unchanged in type) from them.
1957
- */
1958
- type DefinitionMap = Record<string, AnyOperationDefinition>;
1959
1661
 
1960
- /**
1961
- * Model write-semantics — the reusable *save contract* `entityWrites` (issue #83,
1962
- * Epic #80; spec `docs/mutation-command-derivation.md` §2).
1963
- *
1964
- * `Model.writes` declares the **write-side invariants / effects required for this
1965
- * entity to be consistent on DynamoDB** — a *reusable save contract*, NOT a
1966
- * mutation and NOT business logic. It is a per-lifecycle map
1967
- * (`{ create?, update?, remove? }`) whose values are {@link LifecycleContract}s
1968
- * built with `w.lifecycle({...})`; each carries the §2 effect arrays
1969
- * (`requires` / `unique` / `edges` / `derive` / `emits` / `idempotency`).
1970
- *
1971
- * ## What #83 does — and deliberately does NOT — with the effects
1972
- *
1973
- * The mutation compiler (#83) resolves a fragment's lifecycle from this save
1974
- * contract (the fragment's intent — `m.create` / `m.update` / `m.remove` — picks
1975
- * `create` / `update` / `remove`) and emits the **base write op** for that
1976
- * lifecycle (create → `PutItem` with `attribute_not_exists(PK)`, update →
1977
- * `UpdateItem`, remove → `DeleteItem`). The §2 effect arrays are **stored
1978
- * opaquely** here and consumed by NONE of #83: deriving `ConditionCheck`
1979
- * (requires, #84), uniqueness guards (#86), adjacency edge `Put` / `Delete` from
1980
- * `edges` (#85), derived counter `UpdateItem`s (#85), outbox events (#87), and
1981
- * the idempotency guard (#87) are each their own follow-up issue. #83 only shapes
1982
- * the declaration and exposes the {@link LifecycleContract.effects} a later issue
1983
- * reads — the **per-fragment hook** the compiler leaves empty (see
1984
- * `compileFragment` in `src/spec/mutation-command.ts`).
1985
- *
1986
- * ## Coexistence with edge writes (`edgeWrites`, #82)
1987
- *
1988
- * This is a **distinct** construct from {@link edgeWrites} (#82, the adjacency
1989
- * edge-only `writes` member). `edgeWrites` declares *only* the edge write side of
1990
- * an adjacency entity; `entityWrites` is the full per-lifecycle save contract a
1991
- * mutation fragment adopts. Both are recognized by their own brand, so a model
1992
- * may carry either form on its static `writes` member without collision; the
1993
- * mutation compiler reads {@link getEntityWrites} (this marker), the edge
1994
- * derivation reads its own.
1995
- *
1996
- * ## Trivial base op when a model declares no `writes`
1997
- *
1998
- * A target model that declares **no** `entityWrites` save contract has no
1999
- * lifecycle to resolve; the fragment then compiles the **trivial base op** for
2000
- * its intent (create → `Put` `attribute_not_exists(PK)`, update → `Update`,
2001
- * remove → `Delete`) directly against the plain model. So
2002
- * `{ create: () => PostModel, key, input }` works on a bare model — see
2003
- * {@link resolveLifecycle} in `src/spec/mutation-command.ts`.
2004
- */
2005
- /**
2006
- * The lifecycle phase a save contract entry declares. The mutation fragment's
2007
- * intent (`m.create` / `m.update` / `m.remove`) selects the matching entry.
2008
- */
2009
- type WriteLifecyclePhase = 'create' | 'update' | 'remove';
2010
1662
  /**
2011
1663
  * A path-rooted leaf value in a save-contract effect (proposal §2): every value
2012
1664
  * binds to an explicit path root so its source is unambiguous —
@@ -2126,18 +1778,6 @@ declare const MAINTAIN_TRIGGER_BRAND: unique symbol;
2126
1778
  type MaintainTrigger = `${string}.${MaintainEvent}` & {
2127
1779
  readonly [MAINTAIN_TRIGGER_BRAND]: true;
2128
1780
  };
2129
- /**
2130
- * Construct a validated {@link MaintainTrigger} from an `Entity.event` string.
2131
- * Rejects a value that is not a non-empty entity name followed by `.created` /
2132
- * `.updated` / `.removed`, so an invalid trigger is refused at construction time
2133
- * (the AC's "invalid trigger string is rejected"). The brand is a compile-time
2134
- * marker only; the returned value is the input string verbatim.
2135
- *
2136
- * @throws if `value` is not a well-formed `Entity.event` trigger string.
2137
- */
2138
- declare function maintainTrigger(value: string): MaintainTrigger;
2139
- /** Runtime guard: is `value` a well-formed {@link MaintainTrigger} string? */
2140
- declare function isMaintainTrigger(value: unknown): value is MaintainTrigger;
2141
1781
  /**
2142
1782
  * A projection transform op (issue #120, 論点3 = 関数形): the function-form
2143
1783
  * `w.transform(path, op, ...args)` is the **primary** authoring surface (there is
@@ -2502,8 +2142,6 @@ interface LifecycleContract {
2502
2142
  /** The §2 effect arrays, stored opaquely by #83. */
2503
2143
  readonly effects: LifecycleEffects;
2504
2144
  }
2505
- /** Runtime guard: is `value` a {@link LifecycleContract} (a `w.lifecycle(...)`)? */
2506
- declare function isLifecycleContract(value: unknown): value is LifecycleContract;
2507
2145
  /**
2508
2146
  * Marker carried by a model's static `writes` member when it is an
2509
2147
  * {@link EntityWritesDefinition} (an `entityWrites(...)` save contract), so it is
@@ -2526,8 +2164,6 @@ interface EntityWritesDefinition {
2526
2164
  /** The remove-lifecycle save contract, if declared. */
2527
2165
  readonly remove?: LifecycleContract;
2528
2166
  }
2529
- /** Runtime guard: is `value` an {@link EntityWritesDefinition}? */
2530
- declare function isEntityWritesDefinition(value: unknown): value is EntityWritesDefinition;
2531
2167
  /**
2532
2168
  * The recorder handed to the {@link entityWrites} builder. `w.lifecycle({...})`
2533
2169
  * accepts the §2 effect arrays and brands them into a {@link LifecycleContract}.
@@ -2654,8 +2290,6 @@ declare function entityWrites<M = unknown>(builder: (w: WriteRecorder) => Entity
2654
2290
  * collision (a model carries one or the other; the marker disambiguates).
2655
2291
  */
2656
2292
  declare function getEntityWrites(modelClass: new (...args: unknown[]) => unknown): EntityWritesDefinition | undefined;
2657
- /** The lifecycle phase a mutation fragment intent selects. */
2658
- declare function lifecyclePhaseForIntent(intent: 'create' | 'update' | 'remove'): WriteLifecyclePhase;
2659
2293
 
2660
2294
  /**
2661
2295
  * Internal write-plan composition DSL — `mutation` / `definePlan` (issue #83,
@@ -2664,7 +2298,7 @@ declare function lifecyclePhaseForIntent(intent: 'create' | 'update' | 'remove')
2664
2298
  * A **mutation is INTERNAL** — it is *not* a public API. It is a *write-plan
2665
2299
  * composition language* used as the **implementation** of a Command: it declares
2666
2300
  * a set of write **fragments** that must execute atomically. The only externally
2667
- * exposed surface is the fixed Command IF (`publicCommandModel(...).plan(...)`,
2301
+ * exposed surface is the fixed Command IF (`publishCommand(...).plan(...)`,
2668
2302
  * `src/define/contract.ts`); the mutation document is never exposed.
2669
2303
  *
2670
2304
  * The authoring form (issue #108) is a **descriptor map**: the body returns an
@@ -2715,11 +2349,18 @@ declare function lifecyclePhaseForIntent(intent: 'create' | 'update' | 'remove')
2715
2349
 
2716
2350
  /**
2717
2351
  * The intent of a write fragment (proposal §3): `create` / `update` / `remove`
2718
- * (`remove`, **not** `del`). The intent selects the lifecycle from the target's
2719
- * save contract, and the base write op (create → `PutItem`, update →
2720
- * `UpdateItem`, remove → `DeleteItem`).
2721
- */
2722
- 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';
2723
2364
  declare const INPUT_REF_BRAND: unique symbol;
2724
2365
  /**
2725
2366
  * A faithful reference to a mutation **input** field, minted by reading `$.<field>`
@@ -2736,8 +2377,6 @@ interface MutationInputRef {
2736
2377
  /** The template token this reference renders to, e.g. `{title}`. */
2737
2378
  readonly token: string;
2738
2379
  }
2739
- /** Runtime guard: is `value` a {@link MutationInputRef} (a `$.<field>` read)? */
2740
- declare function isMutationInputRef(value: unknown): value is MutationInputRef;
2741
2380
  declare const ENTITY_REF_BRAND: unique symbol;
2742
2381
  /**
2743
2382
  * A faithful **cross-fragment reference** (`$.entity[i].field`): the value a later
@@ -2759,32 +2398,6 @@ interface MutationEntityRef {
2759
2398
  /** The source path, e.g. `$.entity[0].postId` (documentary; surfaces in errors). */
2760
2399
  readonly path: string;
2761
2400
  }
2762
- /**
2763
- * The placeholder proxy handed to a mutation body as `$`. In the descriptor-map
2764
- * authoring form (issue #108) a top-level read `$.<x>` is **ambiguous** until used:
2765
- *
2766
- * - `$.role` placed at a `key` / `input` leaf is an **input-field reference**
2767
- * (a {@link MutationInputRef}, token `{role}`) — bound from the command input;
2768
- * - `$.membership.role` is a **cross-fragment reference** — the `role` field
2769
- * written by the fragment named `membership` (an alias). It mints an
2770
- * {@link AliasFieldRef}, which the {@link mutation} assembler rewrites into the
2771
- * legacy `$.entity[<index>].<field>` string the compiler resolves (the alias →
2772
- * declaration-order index is known once every alias key is seen).
2773
- *
2774
- * So each top-level read returns a value that is **both** a faithful
2775
- * {@link MutationInputRef} (when used directly as a leaf) and supports one further
2776
- * `.<field>` access (when used as an alias root). Any other access / coercion
2777
- * throws — the binding stays declarative (only a direct `$.field` / `$.alias.field`
2778
- * reference or a literal is representable, never a transform).
2779
- *
2780
- * **Input Port 統一 (issue #264)**: this proxy is one of the three authoring
2781
- * spellings of the SAME Input Port reference model — see the unification note
2782
- * on {@link import('./transaction.js').ParamProxy} (`defineTransaction`'s
2783
- * `p.*` proxy is the model; `{<field>}` templates and `{ref:["input","<field>"]}`
2784
- * expression refs are the two serialized mirrors of one declared-input
2785
- * namespace). Behavior here is unchanged in 0.7.x (interop kept).
2786
- */
2787
- type MutationInputProxy = Record<string, MutationInputRef>;
2788
2401
  declare const FRAGMENT_BRAND: unique symbol;
2789
2402
  /**
2790
2403
  * The per-field input binding of a fragment: model field name → a faithful
@@ -2793,14 +2406,6 @@ declare const FRAGMENT_BRAND: unique symbol;
2793
2406
  * field they key, never a transform.
2794
2407
  */
2795
2408
  type FragmentInput = Readonly<Record<string, MutationInputRef | string | number | boolean | Date | null>>;
2796
- /**
2797
- * A descriptor `key` / `input` value as authored in the descriptor-map form
2798
- * (issue #108): a `$.field` input reference, a `$.alias.field` cross-fragment
2799
- * reference (an opaque alias-field ref the assembler rewrites), or a literal.
2800
- * Structurally `unknown` at the leaf because the alias-field ref is internal; the
2801
- * assembler validates every leaf via {@link assertFaithfulInput}.
2802
- */
2803
- type DescriptorBinding = Readonly<Record<string, unknown>>;
2804
2409
  /** A concrete scalar admissible at a condition operand leaf. */
2805
2410
  type ConditionScalar = MutationInputRef | string | number | boolean | Date | null;
2806
2411
  /** A declarative operator object on one condition field (the #114-A subset). */
@@ -2820,8 +2425,8 @@ interface FragmentConditionOperatorObject {
2820
2425
  }
2821
2426
  /**
2822
2427
  * A declarative write **condition** authored on a {@link WriteDescriptor} (issue
2823
- * #242, Phase 2) — the same #114-A subset the #101 public write descriptor and
2824
- * `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
2825
2430
  * (the mutation authoring form's placeholder) rather than `param.*` / concrete
2826
2431
  * values. It is a recursive tree of field clauses (bare equality or an operator
2827
2432
  * object) and `and` / `or` / `not` logical groups. The compiler translates each
@@ -2874,8 +2479,6 @@ interface MutationFragment {
2874
2479
  */
2875
2480
  readonly condition?: FragmentCondition;
2876
2481
  }
2877
- /** Runtime guard: is `value` a {@link MutationFragment}? */
2878
- declare function isMutationFragment(value: unknown): value is MutationFragment;
2879
2482
  /**
2880
2483
  * A model reference in a write descriptor (issue #108): either the model class /
2881
2484
  * `.asModel()` value **directly** (`{ create: GroupMembership }`) or a **thunk**
@@ -2885,60 +2488,6 @@ declare function isMutationFragment(value: unknown): value is MutationFragment;
2885
2488
  * `m.create(() => Model, …)` recorder required.
2886
2489
  */
2887
2490
  type ModelRef = ModelStatic<DDBModel> | (new (...args: unknown[]) => unknown) | (() => ModelStatic<DDBModel> | (new (...args: unknown[]) => unknown));
2888
- /**
2889
- * One **write descriptor** in the descriptor-map authoring form (issue #108) —
2890
- * the declarative twin of an in-process `DDBModel.mutate` entry (#101). The intent
2891
- * key (`create` / `update` / `remove`) is the **discriminator** and carries the
2892
- * target {@link ModelRef} (direct or thunk). The remaining fields are declarative
2893
- * bindings (`$.field` references or literals):
2894
- *
2895
- * - `key` — the target row's primary-key binding (identifies the row);
2896
- * - `input` — the non-key field binding (the body / changed fields). Optional for
2897
- * a `remove` (a delete writes no fields);
2898
- * - `condition` — an optional declarative write gate (issue #242, Phase 2). It is
2899
- * the #114-A condition subset (bare equality, operator objects `{ gt }` / `{ in }`
2900
- * / `{ between }` / …, and `and` / `or` / `not` groups) whose operand leaves are
2901
- * `$.field` input references or literals. The declaration-DSL compiler **consumes**
2902
- * it: each `$.field` leaf becomes a {@link import('./contract.js').ContractParamRef}
2903
- * and the tree is attached to the compiled base op's `condition`, so it is
2904
- * serialized (into the operation spec) and evaluated at runtime exactly as a #101
2905
- * public write descriptor's / `defineCommands` condition is (single-op, in-process
2906
- * transaction, and the Python bridge). A conditioned `update` / `remove` therefore
2907
- * fails a CAS-style write when the gate does not hold, rather than silently
2908
- * committing;
2909
- * - `result` — an optional read-back projection `{ select, options? }` (likewise
2910
- * accepted for authoring parity; the public read-back projection of a
2911
- * `command(...).plan(...)` method is still declared on `command({ select })`).
2912
- *
2913
- * Exactly one intent key must be present.
2914
- */
2915
- interface WriteDescriptor {
2916
- readonly create?: ModelRef;
2917
- readonly update?: ModelRef;
2918
- readonly remove?: ModelRef;
2919
- /** The target row's primary-key binding (`{ field: $.field | literal }`). */
2920
- readonly key: DescriptorBinding;
2921
- /** The non-key field binding; optional (a `remove` writes no fields). */
2922
- readonly input?: DescriptorBinding;
2923
- /**
2924
- * Optional declarative write gate — the #114-A condition subset (issue #242,
2925
- * Phase 2). Compiled, serialized, and evaluated at runtime; see the interface doc.
2926
- */
2927
- readonly condition?: FragmentCondition;
2928
- /** Optional read-back projection `{ select, options? }` (accepted for #101 parity). */
2929
- readonly result?: {
2930
- readonly select?: unknown;
2931
- readonly options?: unknown;
2932
- };
2933
- /**
2934
- * Optional custom save contract to adopt (the **whole** {@link
2935
- * EntityWritesDefinition}). Omit to default to the target model's own `writes`.
2936
- * Per the `use:` rule this is never a single lifecycle — the intent picks it.
2937
- */
2938
- readonly use?: EntityWritesDefinition;
2939
- }
2940
- /** The descriptor map a {@link mutation} body returns: alias → {@link WriteDescriptor}. */
2941
- type MutationDescriptorMap = Record<string, WriteDescriptor>;
2942
2491
  declare const COMMAND_PLAN_BRAND: unique symbol;
2943
2492
  /**
2944
2493
  * The recorded **CommandPlan** — a mutation's IR (proposal §3). It carries the
@@ -2954,54 +2503,187 @@ interface CommandPlan {
2954
2503
  /** The declared write fragments, in declaration order (the mutation IR). */
2955
2504
  readonly fragments: readonly MutationFragment[];
2956
2505
  }
2957
- /** Runtime guard: is `value` a {@link CommandPlan} (a `mutation(...)` result)? */
2958
- declare function isCommandPlan(value: unknown): value is CommandPlan;
2506
+
2959
2507
  /**
2960
- * The body of a {@link mutation} in the descriptor-map authoring form (issue #108):
2961
- * `$ => ({ alias: descriptor, … })`. It receives the placeholder proxy `$` and
2962
- * returns an **alias {@link WriteDescriptor}** map. The map's **insertion order**
2963
- * is the fragments' declaration (execution) order, and each alias is the name a
2964
- * 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.
2965
2583
  */
2966
- type MutationBody = ($: MutationInputProxy) => MutationDescriptorMap;
2584
+
2585
+ declare const TX_REF_BRAND: unique symbol;
2967
2586
  /**
2968
- * Declare an internal **mutation** a write-plan composition in the descriptor-map
2969
- * authoring form (issue #108). The body returns an **alias {@link WriteDescriptor}**
2970
- * map; each descriptor names its target via the intent key (`create` / `update` /
2971
- * `remove`: a model or a `() => Model` thunk) and binds `key` / `input` with `$.field`
2972
- * placeholders. Insertion order is execution order, and a later descriptor may read
2973
- * an earlier one's written field via `$.alias.field`.
2974
- *
2975
- * ```ts
2976
- * const JoinGroup = mutation($ => ({
2977
- * membership: { create: GroupMembership, key: { groupId: $.groupId, userId: $.userId }, input: { role: $.role } },
2978
- * permission: { create: Permission, key: { id: $.permissionId }, input: { groupId: $.membership.groupId } },
2979
- * }));
2980
- * ```
2981
- *
2982
- * A leading **name** is optional (it surfaces only in compiler error messages — the
2983
- * alias map already names each fragment): `mutation('JoinGroup', $ => ({ … }))`.
2984
- *
2985
- * The result is a branded {@link CommandPlan} (the mutation IR) — *not* public; it
2986
- * implements a Command IF via `publicCommandModel(...).plan(mutation)`. The IR shape
2987
- * (`{ name, fragments }`) is unchanged from the legacy recorder form, so the compiler,
2988
- * serializer, and every runtime consume it identically.
2989
- *
2990
- * @throws if the body returns a non-object / empty map, a descriptor has no (or
2991
- * multiple) intent key(s), a binding is non-declarative, or a `$.alias.field`
2992
- * 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.
2993
2590
  */
2994
- declare function mutation(body: MutationBody): CommandPlan;
2995
- 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
+ };
2996
2608
  /**
2997
- * Alias of {@link mutation} (proposal naming: the internal DSL is `mutation` /
2998
- * `definePlan`). Identical behavior; provided so a producer may name the plan
2999
- * 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`).
3000
2615
  */
3001
- 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
+ }
3002
2684
 
3003
2685
  /**
3004
- * Contract DSL — `publicQueryModel` / `publicCommandModel` (issue #58, CQRS
2686
+ * Contract DSL — `publishQuery` / `publishCommand` (issue #58, CQRS
3005
2687
  * Contract layer, Epic #57; spec `docs/cqrs-contract.md`).
3006
2688
  *
3007
2689
  * A **QueryModel / CommandModel** is the public, storage-independent interface
@@ -3010,15 +2692,15 @@ declare const definePlan: typeof mutation;
3010
2692
  * `get` + `summary` over the same key, differing only in projection). The Key is
3011
2693
  * the access pattern; the Method is the use case.
3012
2694
  *
3013
- * This module sits **on top of** the existing definition DSL
3014
- * (`defineQuery` / `defineList` / `definePut` / `defineUpdate` / `defineDelete`,
3015
- * `src/define/define.ts`) and shares its internal representation
3016
- * ({@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
3017
2699
  * declarative operation on the underlying model; the closure is **never**
3018
2700
  * serialized — only the resolved declaration is captured into the IR.
3019
2701
  *
3020
2702
  * ```ts
3021
- * const ArticleById = publicQueryModel<ArticleIdKey>()({
2703
+ * const ArticleById = publishQuery<ArticleIdKey>()({
3022
2704
  * get: (keys, params) =>
3023
2705
  * Article.query(keys, { articleId: true, title: true, body: true }, params),
3024
2706
  * });
@@ -3154,7 +2836,6 @@ type QueryMethod<TKey, TParams, TResult> = (key: TKey | readonly TKey[], params:
3154
2836
  */
3155
2837
  type CommandMethod<TKey, TParams, TResult> = (key: TKey | readonly TKey[], params: TParams) => TResult;
3156
2838
  declare const KEY_REF_BRAND: unique symbol;
3157
- declare const KEY_FIELD_REF_BRAND: unique symbol;
3158
2839
  /**
3159
2840
  * The captured `keys` argument of a contract method body. A branded sentinel that
3160
2841
  * supports the two faithful uses the proposal's examples need:
@@ -3173,80 +2854,7 @@ declare const KEY_FIELD_REF_BRAND: unique symbol;
3173
2854
  interface ContractKeyRef {
3174
2855
  readonly [KEY_REF_BRAND]: true;
3175
2856
  }
3176
- /**
3177
- * A captured reference to a single field of the method's `keys` argument
3178
- * (`keys.<field>`), minted when a range body rebuilds a partition key. Branded so
3179
- * the verifier can distinguish a faithful key-field reference from a literal, and
3180
- * so it cannot be silently coerced.
3181
- */
3182
- interface ContractKeyFieldRef {
3183
- readonly [KEY_FIELD_REF_BRAND]: true;
3184
- /** The key field name, e.g. `categoryId`. */
3185
- readonly field: string;
3186
- /** The template token this reference renders to, e.g. `{key.categoryId}`. */
3187
- readonly token: string;
3188
- }
3189
- /** Runtime guard: is `value` a {@link ContractKeyRef} (the whole key sentinel)? */
3190
- declare function isContractKeyRef(value: unknown): value is ContractKeyRef;
3191
- /**
3192
- * Mint a faithful {@link ContractKeyFieldRef} for a named key field — a plain
3193
- * (non-Proxy) reference rendering the `{key.field}` differential token but, at a
3194
- * **value** position (a `put` item field), serialized as `{field}` (see
3195
- * `templateLeaf`, `src/spec/operations.ts`). Used by the **mutation compiler**
3196
- * (#83) to mark which of a `create` fragment's item fields are the model's
3197
- * primary-key (contract Key) fields, so the existing serializer recovers the
3198
- * contract Key from the put item (`keyFieldsOf`) exactly as it does for a
3199
- * hand-written #64 `put` that binds `keys.<field>` into the item. Mirrors
3200
- * {@link mintContractParamRef}.
3201
- *
3202
- * @param field The key field name (rendered `{field}` at a value position).
3203
- */
3204
- declare function mintContractKeyFieldRef(field: string): ContractKeyFieldRef;
3205
- /**
3206
- * The whole-`keys` sentinel value an `update` / `delete` op records in its key
3207
- * slot when the body passed `keys` whole into the op's key position. Exposed so
3208
- * the **mutation compiler** (#83) can build an equivalent planned `update` /
3209
- * `delete` op without re-entering the hardening machinery (a mutation has no
3210
- * `keys` argument — its key fields are bound from `$.input.*`, captured via
3211
- * {@link ContractMethodOp.keyFields}).
3212
- */
3213
- declare function wholeKeysSentinel(): ContractKeyRef;
3214
- /** Runtime guard: is `value` a {@link ContractKeyFieldRef}? */
3215
- declare function isContractKeyFieldRef(value: unknown): value is ContractKeyFieldRef;
3216
- declare const PARAM_REF_BRAND$1: unique symbol;
3217
- /**
3218
- * A captured reference to a field of the method's `params` argument (`{<field>}`).
3219
- * Branded so the recorder can distinguish a faithful reference from a concrete
3220
- * literal, and so a stray reference cannot be silently coerced.
3221
- */
3222
- interface ContractParamRef {
3223
- readonly [PARAM_REF_BRAND$1]: true;
3224
- /** The template token this reference renders to, e.g. `{limit}`. */
3225
- readonly token: string;
3226
- /** The field name on `params`, e.g. `limit`. */
3227
- readonly field: string;
3228
- }
3229
- /** Runtime guard: is `value` a {@link ContractParamRef}? */
3230
- declare function isContractParamRef(value: unknown): value is ContractParamRef;
3231
- /**
3232
- * Mint a faithful {@link ContractParamRef} for a named field — a concrete
3233
- * (non-Proxy) reference rendering the `{field}` token. Unlike the hardening
3234
- * sentinels {@link makeParamFieldRef} builds during a contract method body (which
3235
- * throw on any transform to detect non-declarative use), this is a *plain* ref
3236
- * used by the **mutation compiler** (#83, `src/spec/mutation-command.ts`): a
3237
- * mutation fragment's `input` binding is captured as a {@link MutationInputRef}
3238
- * (its own declarative sentinel), and the compiler translates each one into this
3239
- * `ContractParamRef` so the **existing** contract serializer
3240
- * ({@link opToDefinition} → {@link buildCommandSpec}) and TS runtime
3241
- * ({@link renderLeaf}) consume it with **zero** new template / param machinery —
3242
- * the param name and `{token}` are the input field name, exactly as a hand-written
3243
- * `params.field` reference would render.
3244
- *
3245
- * @param field The param / input field name (the rendered token is `{field}`).
3246
- */
3247
- declare function mintContractParamRef(field: string): ContractParamRef;
3248
2857
  declare const FROM_REF_BRAND: unique symbol;
3249
- declare const COMPOSE_NODE_BRAND: unique symbol;
3250
2858
  /**
3251
2859
  * A parent-result source path produced by {@link from} (proposal "External
3252
2860
  * Query"). It names the field of the **parent** method's result that supplies a
@@ -3262,93 +2870,8 @@ declare const COMPOSE_NODE_BRAND: unique symbol;
3262
2870
  interface ContractFromRef {
3263
2871
  readonly [FROM_REF_BRAND]: true;
3264
2872
  /** The `$`-rooted source path on the parent result, e.g. `$.billingAccountId`. */
3265
- readonly path: string;
3266
- }
3267
- /** Runtime guard: is `value` a {@link ContractFromRef} (a `from(...)` binding)? */
3268
- declare function isContractFromRef(value: unknown): value is ContractFromRef;
3269
- /**
3270
- * A resolved External Query composition node produced by {@link query} and placed
3271
- * at a `select` property of a contract method body (proposal "External Query"):
3272
- *
3273
- * ```ts
3274
- * Account.query(keys, {
3275
- * accountId: true,
3276
- * billing: query(BillingPlanQueries.get, { accountId: from("$.billingAccountId") }),
3277
- * })
3278
- * ```
3279
- *
3280
- * It carries a faithful reference to the **referenced method spec** (so the
3281
- * serializer can recover the referenced contract's *name* by identity against the
3282
- * contract map, and its derived `resolution` / `cardinality` facts) plus the
3283
- * declarative child-key binding (child key field → a {@link ContractFromRef}).
3284
- * The owning `select` key becomes the composition's `as` property at record time
3285
- * (it is not known to {@link query} itself).
3286
- */
3287
- interface ContractComposeNode {
3288
- readonly [COMPOSE_NODE_BRAND]: true;
3289
- /** The referenced query method spec (`OtherContract.method`). */
3290
- readonly method: QueryMethodSpec<unknown, unknown, unknown>;
3291
- /** The child-key binding: child key field → parent-result `from` path. */
3292
- readonly bind: Readonly<Record<string, ContractFromRef>>;
3293
- }
3294
- /** Runtime guard: is `value` a {@link ContractComposeNode} (a `query(...)` node)? */
3295
- declare function isContractComposeNode(value: unknown): value is ContractComposeNode;
3296
- /**
3297
- * Declare a parent-result source path for an External Query child key binding
3298
- * (proposal "External Query"). Used **only** inside a {@link query} binding:
3299
- *
3300
- * ```ts
3301
- * query(BillingPlanQueries.get, { accountId: from("$.billingAccountId") })
3302
- * ```
3303
- *
3304
- * The path is `$`-rooted (the parent record) and dotted; the bound child-key
3305
- * field is read from that path of each resolved parent record at execution time.
3306
- * The binding is **declarative** — `from` accepts only a literal path string and
3307
- * rejects any other shape, so no arbitrary JS can sneak into a key binding.
3308
- *
3309
- * @param path A `$`-rooted dotted path, e.g. `"$.billingAccountId"` or `"$.a.b"`.
3310
- * @throws if `path` is not a non-empty `$`-rooted dotted field path.
3311
- */
3312
- declare function from(path: string): ContractFromRef;
3313
- /**
3314
- * Reference another query contract's method as an **External Query** composition
3315
- * child, bound to the parent result by a declarative `from` mapping (proposal
3316
- * "Query Composition" / "External Query"). Place the result at a `select`
3317
- * property of a contract method body; the property name becomes the composed
3318
- * value's `as`:
3319
- *
3320
- * ```ts
3321
- * export const AccountAccess = publicQueryModel<AccountKey>()({
3322
- * get: (keys, params) => Account.query(keys, {
3323
- * accountId: true,
3324
- * billing: query(BillingPlanQueries.get, { accountId: from("$.billingAccountId") }),
3325
- * }, params),
3326
- * });
3327
- * ```
3328
- *
3329
- * This is **build-time-resolved, in-process** contract chaining — the relation
3330
- * primitive extended to point at another contract's method instead of a model
3331
- * relation. There is no protocol / transport: the runtime collects every bound
3332
- * key produced by the parent step and resolves the referenced contract **once,
3333
- * batched** (proposal "External Query"). The child MUST be `point` (the parent
3334
- * may yield N records, so a `range` child would be an N+1 fan-out) — that rule is
3335
- * owned by the N+1 checker (#60), which rejects a `range` child at build time.
3336
- *
3337
- * @param method The referenced query method (`OtherContract.method`), a resolved
3338
- * {@link QueryMethodSpec}.
3339
- * @param bind The child-key binding: child key field → a {@link from} path on
3340
- * the parent result. Must be non-empty and every value a `from(...)` ref.
3341
- * @throws if `method` is not a query method spec, or `bind` is empty / carries a
3342
- * non-`from` value.
3343
- */
3344
- declare function query(method: QueryMethodSpec<any, any, any>, bind: Record<string, ContractFromRef>): ContractComposeNode;
3345
- /**
3346
- * The {@link QueryModelContract} that owns a resolved query method spec, or
3347
- * `undefined` if the spec was not produced by {@link publicQueryModel} (so it has
3348
- * no registered owner). Used by the serializer to resolve a composition's
3349
- * referenced contract by identity.
3350
- */
3351
- declare function contractOfMethodSpec(method: QueryMethodSpec<unknown, unknown, unknown>): QueryModelContract<unknown, Record<string, QueryMethodSpec<unknown, unknown, unknown>>> | undefined;
2873
+ readonly path: string;
2874
+ }
3352
2875
  /**
3353
2876
  * The declarative internal operation a contract method body resolves to — the
3354
2877
  * resolved declaration captured into the IR (the closure itself is discarded).
@@ -3375,7 +2898,7 @@ interface ContractMethodOp<Op extends OperationKind = OperationKind> {
3375
2898
  readonly keys: ContractKeyRef | Readonly<Record<string, unknown>>;
3376
2899
  /**
3377
2900
  * The **explicit contract Key field names** captured from the factory's
3378
- * key-field argument (`publicQueryModel<EmailKey>(['email'])(...)`, issue #71).
2901
+ * key-field argument (`publishQuery<EmailKey>(['email'])(...)`, issue #71).
3379
2902
  * Present only for a **whole-`keys`** op (the {@link keys} slot is the whole
3380
2903
  * {@link ContractKeyRef} sentinel) when the author supplied a field list, or
3381
2904
  * when the model-derived factory form defaulted it to the model's primary-key
@@ -3387,7 +2910,7 @@ interface ContractMethodOp<Op extends OperationKind = OperationKind> {
3387
2910
  *
3388
2911
  * Absent when the body rebuilds a partition key from `keys.<field>` references
3389
2912
  * (the range form already captures field names in the {@link keys} record) or
3390
- * 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
3391
2914
  * (the legacy backward-compatible path, which then defaults to the primary key).
3392
2915
  */
3393
2916
  readonly keyFields?: readonly string[];
@@ -3414,6 +2937,15 @@ interface ContractMethodOp<Op extends OperationKind = OperationKind> {
3414
2937
  * recorded operation, key, or runtime behaviour.
3415
2938
  */
3416
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>>;
3417
2949
  }
3418
2950
  /**
3419
2951
  * A recorded External Query composition edge on a contract method op (#63). The
@@ -3432,33 +2964,6 @@ interface RecordedCompose {
3432
2964
  /** The child-key binding: child key field → parent-result `from` path. */
3433
2965
  readonly bind: Readonly<Record<string, ContractFromRef>>;
3434
2966
  }
3435
- /**
3436
- * The **call signature** a contract method exposes to a caller, narrowed by its
3437
- * decided {@link InputArity} — the type-level half of N+1 rule (a) ("array into a
3438
- * `range` method", #60). Given a method's `inputArity`:
3439
- *
3440
- * - `'single'` (a `range` method) — the key argument is a **single** `TKey` only;
3441
- * passing an array (`readonly TKey[]`) is a **type error** by construction, so a
3442
- * `range` method's array overload does not type-check.
3443
- * - `'either'` (a `point` read / known-key write) — a single key **or** an array
3444
- * (the array form coalesces to one `BatchGetItem` / batched write).
3445
- * - `'array'` — an array only (reserved; not produced by the current resolvers).
3446
- *
3447
- * This mirrors the proposal's "the generated binding's types encode `inputArity`
3448
- * (single → bare argument, array → array argument)". It is the type-level narrowing
3449
- * used by the runtime call surface (`executeQueryMethod`, `src/runtime/contract-runtime.ts`):
3450
- * the executor keys its key-argument type on the named method's **literal** arity
3451
- * `A` (carried on {@link QueryMethodSpec} when the body is tagged with
3452
- * {@link point} / {@link range}), so feeding an array into a `'single'` method is a
3453
- * `tsc` compile error — the **primary** N+1 rule (a) layer, complementing the
3454
- * build-time SSoT check ({@link assertContractN1Safe}) and the runtime backstop.
3455
- *
3456
- * @typeParam A - The decided input arity (a literal `InputArity`).
3457
- * @typeParam TKey - The contract Key.
3458
- * @typeParam TParams - The method's params type.
3459
- * @typeParam TResult - The method's result type.
3460
- */
3461
- type ContractCallSignature<A extends InputArity, TKey, TParams, TResult> = (key: A extends 'single' ? TKey : A extends 'array' ? readonly TKey[] : TKey | readonly TKey[], params: TParams) => TResult;
3462
2967
  /**
3463
2968
  * The resolved IR of one **query** method. Carries the formal method type at the
3464
2969
  * type level (`QueryMethod<TKey, TParams, TResult>`) and, at runtime, the resolved
@@ -3516,8 +3021,29 @@ interface QueryMethodSpec<TKey, TParams, TResult, A extends InputArity = InputAr
3516
3021
  interface CommandMethodSpec<TKey, TParams, TResult> {
3517
3022
  /** @internal Marks this object as a command method spec. */
3518
3023
  readonly __methodKind: 'command';
3519
- /** The resolved internal write operation (closure discarded). */
3520
- 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;
3521
3047
  /**
3522
3048
  * Derived input arity: a single key maps to one write op; an array maps to a
3523
3049
  * batched write (a transaction / `BatchWriteItem`). Writes accept `'either'`.
@@ -3699,10 +3225,6 @@ interface CommandModelContract<TKey, M extends Record<string, CommandMethodSpec<
3699
3225
  /** @internal Phantom carrier retaining the contract Key type `TKey`. */
3700
3226
  readonly __keyType?: (value: TKey) => void;
3701
3227
  }
3702
- /** Runtime guard: is `value` a {@link QueryModelContract}? */
3703
- declare function isQueryModelContract(value: unknown): value is QueryModelContract<unknown, Record<string, QueryMethodSpec<unknown, unknown, unknown>>>;
3704
- /** Runtime guard: is `value` a {@link CommandModelContract}? */
3705
- declare function isCommandModelContract(value: unknown): value is CommandModelContract<unknown, Record<string, CommandMethodSpec<unknown, unknown, unknown>>>;
3706
3228
  /** The entity type a descriptor {@link ModelRef} (model class / `.asModel()` / thunk) targets. */
3707
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;
3708
3230
  /** The projected item type for an entity `T` and a select `S` (relation-nest aware). */
@@ -3713,6 +3235,82 @@ type LeafValueOf<L> = L extends Param<infer V> ? V : L;
3713
3235
  type KeyOf<KeyRec> = {
3714
3236
  readonly [F in keyof KeyRec]: LeafValueOf<KeyRec[F]>;
3715
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
+ };
3716
3314
  /** The params a descriptor method accepts: the union of its `key` + `input` + `condition` leaf value types. */
3717
3315
  type DescriptorParamsOf<D> = (D extends {
3718
3316
  readonly key: infer K;
@@ -3799,7 +3397,7 @@ type WriteDescriptorKeyOf<D> = D extends {
3799
3397
  } ? KeyOf<K> : unknown;
3800
3398
  /**
3801
3399
  * The query-method map accepted by the #101 **direct** form
3802
- * (`publicQueryModel({ get: descriptor, … })`): each value is a
3400
+ * (`publishQuery({ get: descriptor, … })`): each value is a
3803
3401
  * {@link PublicReadDescriptor}. The contract Key and per-method result are inferred
3804
3402
  * from each descriptor's `key` / `select`.
3805
3403
  */
@@ -3810,12 +3408,13 @@ type ResolvedReadDescriptors<M extends ReadDescriptorMap> = QueryModelContract<M
3810
3408
  }>;
3811
3409
  /**
3812
3410
  * The command-method map accepted by the #101 **direct** form
3813
- * (`publicCommandModel({ create: descriptor, … })`): each value is a
3411
+ * (`publishCommand({ create: descriptor, … })`): each value is a
3814
3412
  * {@link PublicWriteDescriptor}, a {@link PublicComposeDescriptor} (a `mutation()`
3815
- * wrapped with `input` / `result` / `mode`), or a bare composite `mutation()`
3816
- * {@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).
3817
3416
  */
3818
- type WriteDescriptorMap = Record<string, PublicWriteDescriptor | PublicComposeDescriptor | CommandPlan>;
3417
+ type WriteDescriptorMap = Record<string, PublicWriteDescriptor | PublicComposeDescriptor | CommandPlan | TransactionDefinition>;
3819
3418
  /** Resolve a write-descriptor map to its {@link CommandModelContract}. */
3820
3419
  type ResolvedWriteDescriptors<M extends WriteDescriptorMap> = CommandModelContract<M[keyof M] extends infer D ? WriteDescriptorKeyOf<D> : unknown, {
3821
3420
  readonly [K in keyof M]: CommandMethodSpec<WriteDescriptorKeyOf<M[K]>, DescriptorParamsOf<M[K]>, WriteDescriptorResultOf<M[K]>>;
@@ -3824,7 +3423,7 @@ type ResolvedWriteDescriptors<M extends WriteDescriptorMap> = CommandModelContra
3824
3423
  * Create a **QueryModel** from a **descriptor map** (issue #101, the only form):
3825
3424
  *
3826
3425
  * ```ts
3827
- * export const UserQueries = publicQueryModel({
3426
+ * export const UserQueries = publishQuery({
3828
3427
  * get: { query: User, key: { userId: param.string() }, select: { name: true } },
3829
3428
  * list: { list: GroupMembership, key: { groupId: param.string() }, select: { role: true } },
3830
3429
  * });
@@ -3833,7 +3432,7 @@ type ResolvedWriteDescriptors<M extends WriteDescriptorMap> = CommandModelContra
3833
3432
  * The single argument is the method map itself; the contract Key and each method's
3834
3433
  * result type are **inferred** from the descriptors' `key` / `select` — there is no
3835
3434
  * generic key parameter, no curried factory call, and no explicit Key-field list
3836
- * (the legacy `publicQueryModel<TKey>(['email'])` / model-derived overloads were
3435
+ * (the legacy `publishQuery<TKey>(['email'])` / model-derived overloads were
3837
3436
  * withdrawn in #101). A GSI-keyed point read is expressed by its descriptor `key`
3838
3437
  * (e.g. `key: { email: param.string() }`); the build resolves the access pattern
3839
3438
  * from the model metadata, exactly as the curried key-field list once did. Each
@@ -3841,12 +3440,12 @@ type ResolvedWriteDescriptors<M extends WriteDescriptorMap> = CommandModelContra
3841
3440
  * a `select` is the existing query projection (relation-nest aware), and may carry
3842
3441
  * External Query `query(...)` nodes.
3843
3442
  */
3844
- declare function publicQueryModel<const M extends ReadDescriptorMap>(methods: M): ResolvedReadDescriptors<M>;
3443
+ declare function publishQuery<const M extends ReadDescriptorMap>(methods: M): ResolvedReadDescriptors<M>;
3845
3444
  /**
3846
3445
  * Create a **CommandModel** from a **descriptor map** (issue #101, the only form):
3847
3446
  *
3848
3447
  * ```ts
3849
- * export const MembershipCommands = publicCommandModel({
3448
+ * export const MembershipCommands = publishCommand({
3850
3449
  * create: { create: GroupMembership, key: { groupId: param.string(), userId: param.string() },
3851
3450
  * input: { role: param.string() }, result: { select: { role: true } } },
3852
3451
  * addMany: { create: GroupMembership, key: { … }, input: { … }, mode: 'parallel' },
@@ -3857,53 +3456,46 @@ declare function publicQueryModel<const M extends ReadDescriptorMap>(methods: M)
3857
3456
  * The single argument is the method map itself; the contract Key and each method's
3858
3457
  * read-back result are **inferred** from the descriptors' `key` / `result.select` —
3859
3458
  * there is no generic key parameter, no curried factory call, and no explicit
3860
- * Key-field list (the legacy `publicCommandModel<TKey>()` / model-derived overloads
3459
+ * Key-field list (the legacy `publishCommand<TKey>()` / model-derived overloads
3861
3460
  * were withdrawn in #101). A descriptor's `mode` declares the per-call execution
3862
3461
  * mode (`'transaction'` default | `'parallel'`); `result` presence drives whether
3863
3462
  * the method reads back and returns the written entity.
3864
3463
  */
3865
- declare function publicCommandModel<const M extends WriteDescriptorMap>(methods: M): ResolvedWriteDescriptors<M>;
3866
- declare const PLANNED_COMMAND_BRAND: unique symbol;
3867
- /**
3868
- * The input param shape a {@link command} declares: a record of named
3869
- * {@link Param} placeholders (`param.string()` / `param.number()` /
3870
- * `param.literal(...)`). These are the **external** params of the public Command
3871
- * IF the method's caller-supplied input. The mutation fragment binds them by
3872
- * name into the written entity (`$.<field>`), so each input field name must match
3873
- * a fragment `$.field` reference / a model field the fragment writes.
3874
- */
3875
- type CommandInputShape = Readonly<Record<string, Param<unknown>>>;
3876
- /**
3877
- * The return projection a {@link command} declares (proposal §3: "return = read
3878
- * projection"): a JSON-safe boolean field map applied to the written entity as a
3879
- * **consistent read-back** after the write commits. Omit for a fire-and-forget
3880
- * command (no projected item is returned).
3881
- */
3882
- type CommandSelectShape = Readonly<Record<string, boolean>>;
3883
- /**
3884
- * A **planned command method** (#83): the branded result of
3885
- * `command({ input, select }).plan(mutation)`. It carries the captured fragment
3886
- * IR (the {@link CommandPlan}), the public input param shape, and the return
3887
- * `select`. {@link buildCommandContract} detects the brand and routes it to the
3888
- * single-fragment mutation compiler (NOT through #64's closure path).
3889
- *
3890
- * The external surface is **params in / result out only** — the internal mutation
3891
- * document is never exposed.
3892
- *
3893
- * @typeParam TInput - The input param shape (`{ field: Param<…> }`).
3894
- * @typeParam TSelect - The return projection (`{ field: boolean }`), or `undefined`.
3895
- */
3896
- interface PlannedCommandMethod<TInput extends CommandInputShape = CommandInputShape, TSelect extends CommandSelectShape | undefined = CommandSelectShape | undefined> {
3897
- readonly [PLANNED_COMMAND_BRAND]: true;
3898
- /** The captured mutation IR (the fragment list). */
3899
- readonly plan: CommandPlan;
3900
- /** The public input param shape. */
3901
- readonly input: TInput;
3902
- /** The return projection (consistent read-back), or `undefined`. */
3903
- 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;
3904
3475
  }
3905
- /** Runtime guard: is `value` a {@link PlannedCommandMethod} (#83)? */
3906
- 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;
3907
3499
  /** The per-call execution mode of a command method (#101): replaces `batch`. */
3908
3500
  type CommandMode = 'transaction' | 'parallel';
3909
3501
  /** A public **read** descriptor (issue #101): `{ query | list: Model, key, select, options? }`. */
@@ -3921,11 +3513,21 @@ interface PublicReadDescriptor {
3921
3513
  */
3922
3514
  readonly description?: string;
3923
3515
  }
3924
- /** 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? }`. */
3925
3517
  interface PublicWriteDescriptor {
3926
3518
  readonly create?: ModelRef;
3927
3519
  readonly update?: ModelRef;
3928
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;
3929
3531
  readonly key: Readonly<Record<string, unknown>>;
3930
3532
  readonly input?: Readonly<Record<string, unknown>>;
3931
3533
  readonly condition?: Readonly<Record<string, unknown>> | RawCondition<any, any>;
@@ -4112,16 +3714,6 @@ interface MaintenanceGraph {
4112
3714
  */
4113
3715
  readonly multiMaintainerTargets: ReadonlyMap<string, readonly MaintainItem[]>;
4114
3716
  }
4115
- /**
4116
- * Build the {@link MaintenanceGraph} from every registered model's relation-side
4117
- * maintenance declarations (`MetadataRegistry.getAll()`), validating each as it is
4118
- * collected (round-trip, unresolved trigger) and the whole graph (cycle) before
4119
- * returning. Throws a loud, actionable error on any invalid declaration.
4120
- *
4121
- * @param registry The models to scan. Defaults to `MetadataRegistry.getAll()`;
4122
- * an explicit map is accepted for tests / scoped builds.
4123
- */
4124
- declare function buildMaintenanceGraph(registry?: ReadonlyMap<Function, EntityMetadata>, views?: readonly ViewDefinition[]): MaintenanceGraph;
4125
3717
 
4126
3718
  /**
4127
3719
  * A **referential-integrity assertion** derived from a `requires`
@@ -4750,225 +4342,6 @@ interface CompiledMutationPlan {
4750
4342
  */
4751
4343
  declare function compileMutationPlan(plan: CommandPlan): CompiledMutationPlan;
4752
4344
 
4753
- /**
4754
- * Single-service contract **Runtime** — execution of CQRS query-contract methods
4755
- * (issue #62, Epic #57; spec `docs/cqrs-contract.md`, "Query Method
4756
- * — Common Interface" + "Cardinality matrix"). READS only; command/write
4757
- * execution is #64 and single-contract composition / External Query is #63 — both
4758
- * out of scope here.
4759
- *
4760
- * The Runtime is the **execution engine**: given a resolved query contract (a
4761
- * {@link QueryModelContract} from #58, carrying per-method `resolution` /
4762
- * `inputArity` and a resolved {@link ContractMethodOp}) it executes one method
4763
- * for a single key **or** an array of keys and returns the correct result shape
4764
- * from the proposal's cardinality matrix:
4765
- *
4766
- * | Input | `resolution` | Result shape | Notation |
4767
- * | -------------- | ------------ | ------------------------------------- | ---------------- |
4768
- * | `key` (single) | `point` | `Item \| null` | `key1 : result1` |
4769
- * | `key` (single) | `range` | `Connection` (`{ items, cursor }`) | `key1 : resultM` |
4770
- * | `keys[]` (N) | `point` | keyed `Map<keyId, Item \| null>` | `keyN : resultN` |
4771
- * | `keys[]` (N) | `range` | **unreachable for a single contract** | `keyN : resultN×M` |
4772
- *
4773
- * ## On `keyN : resultN×M` (range + array) — unreachable here, by design
4774
- *
4775
- * The fourth matrix cell — a keyed map of connections — is **not reachable for a
4776
- * single contract** at the direct-call surface. The proposal's N+1 Safety rule
4777
- * fixes a `range` method's `inputArity` to `'single'` ("A `range` method is
4778
- * necessarily `'single'`"; "feeding an array of keys into a range method … is an
4779
- * N+1 fan-out and is rejected at build time, with no opt-in escape hatch"). #58's
4780
- * {@link inputArityOf} derives exactly that, and #60's N+1 checker rejects any
4781
- * contract that violates it before it reaches the SSoT. So a range method only
4782
- * ever accepts one key, and this runtime **rejects** an array fed into a `range`
4783
- * method ({@link executeQueryMethod}) rather than fanning out. `keyN : resultN×M`
4784
- * arises only under cross-contract composition (a `point` parent referencing a
4785
- * child whose per-key result is a connection) — that is #63's External Query
4786
- * territory, explicitly out of scope for #62. We therefore implement the **three
4787
- * reachable shapes** and guard the fourth with a clear error, rather than
4788
- * silently fanning out a forbidden N+1.
4789
- *
4790
- * ## Reuse, not reinvention
4791
- *
4792
- * Execution delegates to the proven machinery:
4793
- *
4794
- * - `point` single → {@link executeQuery} (GetItem / unique Query).
4795
- * - `point` array → {@link executeKeyedBatchGet} below, which reuses the
4796
- * `batch-retry` primitives (chunk ≤100, dedup, UnprocessedKeys retry) and
4797
- * builds the **keyed** map (BatchGet neither preserves order nor returns absent
4798
- * keys, so missing keys are filled with explicit `null`).
4799
- * - `range` single → {@link executeListInternal} for a `{ items, cursor }`
4800
- * connection, with the cursor wrapped in a per-key envelope
4801
- * ({@link encodePerKeyCursor}) so it carries the key it belongs to.
4802
- *
4803
- * The result-shape typing (single → bare, array → keyed) is expressed as call
4804
- * overloads on {@link executeQueryMethod}.
4805
- */
4806
-
4807
- /**
4808
- * The **structural** shape of a resolved query contract the runtime executes.
4809
- * It depends only on the facts the executor reads — the `query` discriminant and
4810
- * the per-method `resolution` and internal `op` — and is deliberately
4811
- * **independent of the phantom Key / params / result types** carried by
4812
- * {@link QueryModelContract}. Those phantom types are invariant /
4813
- * contravariant on the full interface, so a concrete `QueryModelContract<{…}>`
4814
- * is not assignable to `QueryModelContract<unknown, …>`; this minimal structural
4815
- * type sidesteps that variance while accepting every real contract (a
4816
- * {@link QueryModelContract} is structurally assignable to it). The runtime never
4817
- * needs the method param / result types — it returns the dynamic
4818
- * cardinality-matrix shapes ({@link ContractItem} / {@link Connection} /
4819
- * {@link KeyedResult}).
4820
- */
4821
- interface ExecutableQueryContract {
4822
- readonly kind: 'query';
4823
- readonly methods: Readonly<Record<string, {
4824
- readonly resolution: Resolution;
4825
- /**
4826
- * The method's decided input arity (issue #71): `'either'` for a
4827
- * coalescible base-table `point` (a key array → one `BatchGetItem`),
4828
- * `'single'` for a `range` (partition `Query`) **or** a unique-GSI `point`
4829
- * (a per-key GSI `Query`, not coalescible). The executor honors it to
4830
- * reject an array fed into any `'single'` method rather than fanning out.
4831
- */
4832
- readonly inputArity: InputArity;
4833
- readonly op: ContractMethodOp;
4834
- }>>;
4835
- }
4836
- /** A plain contract key (the access pattern / join key), e.g. `{ articleId }`. */
4837
- type ContractKeyInput = Record<string, unknown>;
4838
- /**
4839
- * The literal {@link InputArity} the contract `C` carries for method `K`. A
4840
- * {@link QueryMethodSpec} carries the method's literal arity in its `inputArity`
4841
- * type when the body was tagged with `point` / `range` (`src/define/contract.ts`);
4842
- * an untagged method keeps the wide `InputArity`. Falls back to `InputArity` for a
4843
- * structurally-typed contract (e.g. the bare {@link ExecutableQueryContract}),
4844
- * which keeps the call surface array-capable (the runtime backstop still rejects an
4845
- * array fed into a `'single'` method).
4846
- */
4847
- type ArityOfMethod<C extends ExecutableQueryContract, K extends keyof C['methods']> = C['methods'][K] extends {
4848
- readonly inputArity: infer A extends InputArity;
4849
- } ? A : InputArity;
4850
- /**
4851
- * The **array** key-argument type accepted by `executeQueryMethod` for method `K`
4852
- * of contract `C`. A method whose literal arity is `'single'` (a `range` read **or**
4853
- * a unique-GSI `point` — both one request per key) accepts **no** array: the type
4854
- * collapses to `never`, so passing an array is a `tsc` compile error at the
4855
- * user-facing surface (issue #71, N+1 rule (a), the **primary** enforcement layer).
4856
- * Any other arity (`'either'` / `'array'`, or the wide `InputArity` for an untagged
4857
- * / structural contract) accepts an array of keys (it coalesces to one batched
4858
- * call). This is the array-overload half of {@link ContractCallSignature}'s
4859
- * narrowing, applied at the real execution surface.
4860
- */
4861
- type ArrayKeyArg<C extends ExecutableQueryContract, K extends keyof C['methods']> = ArityOfMethod<C, K> extends 'single' ? never : readonly ContractKeyInput[];
4862
- /** A single hydrated result item (only the method's projected fields). */
4863
- type ContractItem = Record<string, unknown>;
4864
- /**
4865
- * A pagination connection: a page of items plus an opaque `cursor` for the next
4866
- * page (`null` when the page is the last). For a `range` contract method the
4867
- * cursor is a **per-key envelope** ({@link encodePerKeyCursor}) that identifies
4868
- * the key it paginates.
4869
- */
4870
- interface Connection<TItem = ContractItem> {
4871
- readonly items: TItem[];
4872
- readonly cursor: string | null;
4873
- }
4874
- /**
4875
- * The keyed result of a batched contract read: input-key identity (the canonical
4876
- * {@link serializeContractKey} string) → per-key result. Backed by a `Map` so
4877
- * the association is explicit and order-independent; missing keys are present
4878
- * with an explicit `null` value (`point`) rather than omitted.
4879
- *
4880
- * @typeParam TValue The per-key value (`Item | null` for `point`).
4881
- */
4882
- type KeyedResult<TValue> = Map<string, TValue>;
4883
- /**
4884
- * Retrieval options accepted by a contract query method (the serializable subset
4885
- * from the proposal). All optional; a `point` method ignores the pagination
4886
- * fields, a `range` method honors them.
4887
- */
4888
- interface ContractQueryParams {
4889
- /** Strongly-consistent read (point reads on the base table only). */
4890
- readonly consistentRead?: boolean;
4891
- /** Page size for a `range` (`list`) method. */
4892
- readonly limit?: number;
4893
- /** Resume token — a per-key cursor envelope from a prior `range` page. */
4894
- readonly after?: string;
4895
- /** Sort direction for a `range` (`list`) method. */
4896
- readonly order?: 'ASC' | 'DESC';
4897
- }
4898
- /**
4899
- * Maximum number of in-flight per-key queries for a batched `range` fan-out.
4900
- * Reuses the relation-traversal cap — the same rationale (bound in-flight
4901
- * requests, ride out throttling via UnprocessedKeys retry beneath the cap). Note
4902
- * that a single-contract `range` method is `inputArity: 'single'`, so the
4903
- * fan-out path is only exercised by the (out-of-scope, #63) composition runtime;
4904
- * the bound is defined here so the seam is ready and tested in isolation.
4905
- */
4906
- declare const CONTRACT_RANGE_FANOUT_CONCURRENCY = 16;
4907
- /**
4908
- * Keyed BatchGet: resolve `keys` against `modelClass` in one batched call and
4909
- * return a `Map<keyId, Item | null>` with every input key present (misses →
4910
- * `null`). Reuses the `batch-retry` primitives (chunk ≤100, dedup, UnprocessedKeys
4911
- * retry) — never reinvented.
4912
- *
4913
- * Implementation notes:
4914
- * - **Dedup**: the same key supplied twice is fetched once; both map entries
4915
- * point at the same resolved item (or `null`).
4916
- * - **Order / absence**: BatchGet returns matched items unordered and omits
4917
- * misses, so each returned raw item is indexed by its `PK::SK` and looked up
4918
- * per input key; an unmatched key gets `null`.
4919
- * - **Projection**: items are fetched whole (DynamoDB BatchGet has no reliable
4920
- * server-side projection for mixed keys here) and hydrated against the
4921
- * method's `select`, so the per-key item carries exactly the projected fields.
4922
- */
4923
- declare function executeKeyedBatchGet(modelClass: Function, keys: readonly ContractKeyInput[], select: Record<string, unknown>): Promise<KeyedResult<ContractItem | null>>;
4924
- /**
4925
- * Execute a query-contract method for a **single** key.
4926
- *
4927
- * The result shape is determined by the method's derived `resolution`:
4928
- * - `point` → `Item | null` (`key1 : result1`);
4929
- * - `range` → `Connection` (`key1 : resultM`).
4930
- *
4931
- * A single key is accepted for **every** method regardless of arity.
4932
- *
4933
- * @param contract The resolved query contract (a {@link QueryModelContract}).
4934
- * @param methodName The method to execute (a method name of `contract`).
4935
- * @param key A single contract key.
4936
- * @param params Retrieval options (pagination / consistency).
4937
- */
4938
- declare function executeQueryMethod<C extends ExecutableQueryContract, K extends keyof C['methods'] & string>(contract: C, methodName: K, key: ContractKeyInput, params?: ContractQueryParams): Promise<ContractItem | null | Connection>;
4939
- /**
4940
- * Execute a query-contract method for an **array** of keys (a single batched
4941
- * call). Valid **only** for a method whose literal arity is not `'single'` — a
4942
- * coalescible base-table `point` (`inputArity: 'either'`, a key array → one
4943
- * `BatchGetItem`). A `'single'` method (a `range` read **or** a unique-GSI `point`)
4944
- * makes the array key argument `never`, so passing an array is a **`tsc` compile
4945
- * error** by construction (issue #71, N+1 rule (a) — the primary enforcement
4946
- * layer; the build-time checker and runtime backstop remain). The result is a keyed
4947
- * `Map<keyId, Item | null>` (`keyN : resultN`).
4948
- *
4949
- * @param contract The resolved query contract.
4950
- * @param methodName The method to execute (a method name of `contract`).
4951
- * @param keys An array of contract keys (rejected for a `'single'` method).
4952
- * @param params Retrieval options.
4953
- */
4954
- 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>>;
4955
- /**
4956
- * The runtime fan-out seam for a batched `range` (`keyN : resultN×M`). NOT
4957
- * reachable for a single contract under #62 (a `range` method's `inputArity` is
4958
- * `'single'`, so {@link executeQueryMethod} rejects an array). It is the
4959
- * cross-contract composition runtime (#63) that drives a per-key range fan-out;
4960
- * this function implements that fan-out — N bounded-parallel Queries grouped by
4961
- * input key, each connection carrying its own per-key cursor — so the seam is
4962
- * defined, reused (via {@link mapWithConcurrency}), and unit-tested in isolation
4963
- * ahead of #63, rather than left as a stub. #62 does not call it on any contract
4964
- * surface.
4965
- *
4966
- * @param op The `range` method op to run per key.
4967
- * @param keys The parent keys to fan out over.
4968
- * @param params Retrieval options applied to **every** key's connection.
4969
- */
4970
- declare function executeRangeFanout(op: ContractMethodOp, keys: readonly ContractKeyInput[], params?: ContractQueryParams): Promise<KeyedResult<Connection>>;
4971
-
4972
4345
  /**
4973
4346
  * Single-service contract **Runtime** — execution of CQRS command-contract
4974
4347
  * methods (issue #64, Epic #57; spec `docs/cqrs-contract.md`,
@@ -5021,188 +4394,12 @@ declare function executeRangeFanout(op: ContractMethodOp, keys: readonly Contrac
5021
4394
  * The TS and Python runtimes produce identical effects for the same SSoT.
5022
4395
  */
5023
4396
 
5024
- /**
5025
- * The **structural** shape of a resolved command contract the runtime executes.
5026
- * It depends only on the facts the executor reads — the `command` discriminant
5027
- * and the per-method `op` / `batch` / `result` — and is deliberately
5028
- * **independent of the phantom Key / params / result types** carried by
5029
- * {@link CommandModelContract} (those are invariant on the full interface, so a
5030
- * concrete `CommandModelContract<{…}>` is not assignable to
5031
- * `CommandModelContract<unknown, …>`). Every real command contract is
5032
- * structurally assignable to this minimal type.
5033
- */
5034
- interface ExecutableCommandContract {
5035
- readonly kind: 'command';
5036
- readonly methods: Readonly<Record<string, {
5037
- readonly op: ContractMethodOp;
5038
- readonly result: CommandResultKind;
5039
- /**
5040
- * The #101 first-class per-call execution mode (`'transaction'` |
5041
- * `'parallel'`). Drives the key-array bulk form directly (`'transaction'`
5042
- * → atomic `TransactWriteItems`; `'parallel'` → non-atomic per-key fan-out
5043
- * with partial success, coalescing unconditioned put/delete into a
5044
- * `BatchWriteItem`).
5045
- */
5046
- readonly mode?: CommandMode;
5047
- /**
5048
- * The composed write ops of a **multi-fragment** `mutation`-derived method
5049
- * (issue #90). When present (N≥2), a **single-key** call executes ALL of
5050
- * them as **one atomic `TransactWriteItems`** (not sequential writes), so a
5051
- * condition failure on any fragment rolls back the whole transaction. Absent
5052
- * for a single-fragment / #83 / hand-written #64 method (which executes its
5053
- * one {@link op}). The read-back stays keyed on the primary op ({@link op}).
5054
- */
5055
- readonly ops?: readonly ContractMethodOp[];
5056
- /**
5057
- * The referential-integrity assertions derived from the mutation's
5058
- * `requires` effects (issue #84). When present (non-empty), a
5059
- * **single-key** call composes the base write(s) **and** one read-only
5060
- * `ConditionCheck` (`attribute_exists`) per assertion into **one atomic
5061
- * `TransactWriteItems`** — so a single-op write is **promoted** to a
5062
- * transaction. If any referenced entity is absent, its assertion fails and
5063
- * the WHOLE transaction rolls back (the entity write never persists). Absent
5064
- * on a mutation with no `requires` / a #64 hand-written method.
5065
- */
5066
- readonly conditionChecks?: readonly DerivedConditionCheck[];
5067
- /**
5068
- * The adjacency edge writes derived from the mutation's `edges` effects
5069
- * (issue #85). When present, a **single-key** call composes each edge's
5070
- * adjacency `Put` / `Delete` item(s) into the **same atomic
5071
- * `TransactWriteItems`** as the entity write (and any ConditionChecks /
5072
- * derived updates), so the edge is created / removed / moved atomically with
5073
- * the entity. Absent on a mutation with no `edges` / a #64 hand-written method.
5074
- */
5075
- readonly edgeWrites?: readonly DerivedEdgeWrite[];
5076
- /**
5077
- * The derived counter updates from the mutation's `derive` effects (issue
5078
- * #85): each an atomic `ADD` (`UpdateItem`) on a target counter (e.g.
5079
- * `User.postCount += 1`). When present, a **single-key** call composes each
5080
- * `ADD` into the same atomic `TransactWriteItems` as the entity write, so the
5081
- * counter moves atomically (and never clobbers a concurrent increment — an
5082
- * `ADD` needs no prior read). Absent on a mutation with no `derive`.
5083
- */
5084
- readonly derivedUpdates?: readonly DerivedUpdate[];
5085
- /**
5086
- * The uniqueness guards derived from the mutation's `unique` effects (issue
5087
- * #86): each a marker-row `Put` (`attribute_not_exists`) / `Delete` /
5088
- * `Delete(old)+Put(new)` swap. When present, a **single-key** call composes
5089
- * the guard item(s) into the same atomic `TransactWriteItems` as the entity
5090
- * write, so a duplicate scoped value (the guard `Put`'s `attribute_not_exists`
5091
- * failing) rolls the WHOLE transaction back — the entity is never created.
5092
- * Absent on a mutation with no `unique` / a #64 hand-written method.
5093
- */
5094
- readonly uniqueGuards?: readonly DerivedUniqueGuard[];
5095
- /**
5096
- * The outbox events derived from the mutation's `emits` effects (issue #87):
5097
- * each a transactional-outbox `Put`. When present, a **single-key** call
5098
- * composes each event's `Put` into the same atomic `TransactWriteItems` as the
5099
- * entity write, so the event RECORD is written ATOMICALLY with the state it
5100
- * announces (they can never diverge). The row is drainable from real
5101
- * DynamoDB Streams (captured at the table layer regardless of code path);
5102
- * delivery / handlers are OUT of scope. Since #94 the derived-command
5103
- * transaction also feeds every committed item (including this outbox `Put`)
5104
- * into the in-process `ChangeCaptureRegistry` seam, so the dev/test
5105
- * `src/cdc/` emulator now observes this path too — production Streams and the
5106
- * emulator agree. Absent on a mutation with no `emits`.
5107
- */
5108
- readonly outboxEvents?: readonly DerivedOutboxEvent[];
5109
- /**
5110
- * The client-token idempotency guard derived from the mutation's `idempotency`
5111
- * effect (issue #87): a marker-row `Put` (`attribute_not_exists`). When present,
5112
- * a **single-key** call composes the guard `Put` into the same atomic
5113
- * `TransactWriteItems` as the entity write, so a same-token re-execution fails
5114
- * the guard and rolls the WHOLE transaction back — no effect is double-applied.
5115
- * Absent on a mutation with no `idempotency` / a #64 hand-written method.
5116
- */
5117
- readonly idempotencyGuard?: DerivedIdempotencyGuard;
5118
- /**
5119
- * The maintenance writes derived from the relation-side `write.maintainedOn`
5120
- * declarations (issue #125 → #127; the D/#124 → compile lowering). When
5121
- * present (non-empty), a **single-key** call composes each maintenance write
5122
- * — a projected snapshot (`SET`) or a bounded-collection append
5123
- * (`SET … = list_append(…)`) on a SEPARATE owner row — into the same atomic
5124
- * `TransactWriteItems` as the source entity write (and every other effect),
5125
- * so the maintained row moves ATOMICALLY with the source: a failure on any
5126
- * item rolls the WHOLE transaction back (the snapshot / collection never
5127
- * partially persists). The owner row is upserted (a bare `UpdateItem`
5128
- * creates it if absent — the additive maintenance semantics). This is the
5129
- * synchronous (`updateMode: 'mutation'`) path only; the async stream path is
5130
- * #130 (loud-rejected at compile time). Absent on a mutation that fires no
5131
- * maintainer / a #64 hand-written method.
5132
- */
5133
- readonly maintainWrites?: readonly DerivedMaintainWrite[];
5134
- /**
5135
- * The maintenance-outbox events for `updateMode: 'stream'` maintainers (issue
5136
- * #130). Each is a transactional-outbox `Put` (the same `literalKey` marker
5137
- * mechanism as #87's outbox) composed into the method's atomic
5138
- * `TransactWriteItems` — recording the maintenance intent ATOMICALLY with the
5139
- * source write. The `src/cdc/` drain selects these (`PK begins_with
5140
- * 'OUTBOX#MAINT#'`) and applies the owner-row write asynchronously (so a
5141
- * snapshot SET / collection append+trim / counter ADD / running-max conditional
5142
- * SET happens off the write path, at-least-once, per-shard ordered). Absent on a
5143
- * mutation with no stream maintainer.
5144
- */
5145
- readonly maintainOutbox?: readonly DerivedMaintainOutbox[];
5146
- /**
5147
- * The return projection of a `mutation`-derived method (issue #83). When
5148
- * present, a **single-key** call performs the write then issues a
5149
- * **consistent read-back** (`GetItem` with `ConsistentRead`) of the
5150
- * written entity's primary key and returns the projected item via the
5151
- * existing read-projection machinery. Absent on a #64 hand-written method.
5152
- */
5153
- readonly returnSelection?: Readonly<Record<string, boolean>>;
5154
- }>>;
5155
- }
5156
- /** The params a command method consumes (the body's `params` argument). */
5157
- type ContractCommandParams = Record<string, unknown>;
5158
4397
  /**
5159
4398
  * The projected read-back item a `mutation`-derived command method returns, or
5160
4399
  * `null` when the written row was absent at read-back (e.g. a `remove`). A plain
5161
4400
  * record of the projected fields.
5162
4401
  */
5163
4402
  type CommandReturn = Record<string, unknown> | null;
5164
- /**
5165
- * #101 Phase 3 — the per-op outcome of a `mode: 'parallel'` key-array bulk write.
5166
- * A non-atomic fan-out reports each key's result independently: `{ ok: true }` on
5167
- * success, `{ ok: false, error }` on a per-op failure (a failed condition / a
5168
- * `BatchWriteItem` item that stayed unprocessed after retry). The array index
5169
- * matches the input key index, and a per-op failure NEVER aborts the others.
5170
- */
5171
- interface ParallelOpResult$1 {
5172
- readonly ok: boolean;
5173
- readonly error?: string;
5174
- }
5175
- /** The partial-success return of a `mode: 'parallel'` key-array bulk write (#101). */
5176
- interface CommandParallelReturn {
5177
- readonly results: readonly ParallelOpResult$1[];
5178
- }
5179
- /**
5180
- * Execute a command-contract method for a **single** key — one write op
5181
- * (`put` / `update` / `delete`).
5182
- *
5183
- * @param contract The resolved command contract (a {@link CommandModelContract}).
5184
- * @param methodName The method to execute.
5185
- * @param key A single contract key (the mutation target).
5186
- * @param params The mutation params (the body's `params` argument).
5187
- */
5188
- declare function executeCommandMethod(contract: ExecutableCommandContract, methodName: string, key: ContractKeyInput, params?: ContractCommandParams): Promise<void | CommandReturn>;
5189
- /**
5190
- * Execute a command-contract method for an **array** of keys — a batched write
5191
- * per the method's declared {@link CommandBatchMode}:
5192
- *
5193
- * - `'transact'` → one atomic `TransactWriteItems` (≤25, condition-capable); a
5194
- * >25-key array is **rejected** (an atomic transaction cannot be split).
5195
- * - `'batchWrite'` → a `BatchWriteItem` (no conditions; chunked ≤25 with
5196
- * `UnprocessedItems` retry).
5197
- *
5198
- * A method that declared no batched form rejects an array with a clear error.
5199
- *
5200
- * @param contract The resolved command contract.
5201
- * @param methodName The method to execute.
5202
- * @param keys An array of contract keys.
5203
- * @param params The mutation params shared across every key.
5204
- */
5205
- declare function executeCommandMethod(contract: ExecutableCommandContract, methodName: string, keys: readonly ContractKeyInput[], params?: ContractCommandParams): Promise<void | CommandParallelReturn>;
5206
4403
 
5207
4404
  /**
5208
4405
  * The **unified envelope** in-process execution engine (issue #101).
@@ -5260,17 +4457,30 @@ interface ReadRouteOptions {
5260
4457
  interface ReadRouteDescriptor {
5261
4458
  readonly query?: ModelRef;
5262
4459
  readonly list?: ModelRef;
5263
- 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>[];
5264
4479
  readonly select: Record<string, unknown>;
5265
4480
  readonly options?: ReadRouteOptions;
5266
4481
  }
5267
4482
  /** The read envelope: alias → {@link ReadRouteDescriptor}. */
5268
4483
  type ReadEnvelope = Record<string, ReadRouteDescriptor>;
5269
- /** The result of a read route — a `query` item (or `null`) or a `list` connection. */
5270
- type ReadRouteResult = Record<string, unknown> | null | {
5271
- items: Record<string, unknown>[];
5272
- cursor: string | null;
5273
- };
5274
4484
  /** The write execution mode: atomic transaction (default) or non-atomic parallel. */
5275
4485
  type MutateMode = 'transaction' | 'parallel';
5276
4486
  /** A read-back projection for a write op (the EXISTING query select shape). */
@@ -5286,6 +4496,12 @@ interface InProcessWriteDescriptor {
5286
4496
  readonly create?: ModelRef;
5287
4497
  readonly update?: ModelRef;
5288
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;
5289
4505
  /**
5290
4506
  * The target's primary-key binding. A single key object applies the op once; an
5291
4507
  * **array** of key objects (#101 `key: K | K[]`) applies the SAME op to every key
@@ -5300,25 +4516,6 @@ interface InProcessWriteDescriptor {
5300
4516
  }
5301
4517
  /** The write envelope: alias → {@link InProcessWriteDescriptor}. */
5302
4518
  type WriteEnvelope = Record<string, InProcessWriteDescriptor>;
5303
- /** Options for {@link executeWriteEnvelope}. */
5304
- interface MutateOptions {
5305
- readonly mode?: MutateMode;
5306
- /**
5307
- * Per-call throttle / transient-error retry override (issue #111), applied to
5308
- * every write this envelope composes (the atomic `TransactWriteItems` in
5309
- * `transaction` mode, or each op's send in `parallel` mode). A {@link RetryPolicy}
5310
- * overrides the global policy; `false` disables retry. Omit ⇒ the configured /
5311
- * built-in default policy.
5312
- */
5313
- readonly retry?: RetryOverride;
5314
- /**
5315
- * Host-injected per-call write context (issue #50 / #139) — exposed to every
5316
- * write hook (W1–W5) as `ctx.context`. In `transaction` mode every composed op
5317
- * shares it and the persist hooks fire once for the atomic batch; in `parallel`
5318
- * mode each independent op sees it. NEVER serialized. Absent ⇒ `{}`.
5319
- */
5320
- readonly context?: RequestContext;
5321
- }
5322
4519
  /** A per-alias result in `parallel` mode: success carries the read-back, else error. */
5323
4520
  type ParallelOpResult = {
5324
4521
  readonly ok: true;
@@ -5327,7 +4524,7 @@ type ParallelOpResult = {
5327
4524
  readonly ok: false;
5328
4525
  readonly error: Error;
5329
4526
  };
5330
- declare const INTENT_KEYS: readonly ["create", "update", "remove"];
4527
+ declare const INTENT_KEYS: readonly ["create", "update", "remove", "upsert"];
5331
4528
  type Intent = (typeof INTENT_KEYS)[number];
5332
4529
 
5333
4530
  /**
@@ -5339,11 +4536,17 @@ type Intent = (typeof INTENT_KEYS)[number];
5339
4536
 
5340
4537
  /** The entity type a {@link ModelStatic} value (or model class) carries. */
5341
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;
5342
- /** 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
+ */
5343
4544
  type ReadRouteResultOf<R> = R extends {
5344
4545
  readonly query: infer M;
5345
4546
  readonly select: infer S;
5346
- } ? 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 {
5347
4550
  readonly list: infer M;
5348
4551
  readonly select: infer S;
5349
4552
  } ? {
@@ -5499,7 +4702,7 @@ interface CompiledReadRoute {
5499
4702
  * ## Why "prepare once, execute many"
5500
4703
  *
5501
4704
  * `DDBModel.mutate` / `Model.query` recompile the plan on every call (`#203`'s
5502
- * "軽量だが per-call 再コンパイル" cell). `publicCommandModel` / `publicQueryModel`
4705
+ * "軽量だが per-call 再コンパイル" cell). `publishCommand` / `publishQuery`
5503
4706
  * precompile but weld it to a named CQRS contract. `prepare` is the missing
5504
4707
  * middle: precompiled **without** the contract ceremony.
5505
4708
  *
@@ -5514,11 +4717,11 @@ interface CompiledReadRoute {
5514
4717
  * computed once. `execute` binds the concrete key VALUES + per-call pagination /
5515
4718
  * consistency and runs the unified read-plan executor
5516
4719
  * (`executeCompiledReadRoute`, issue #249) — the identical core `Model.query` /
5517
- * `Model.list` and `publicQueryModel` use. (The
4720
+ * `Model.list` and `publishQuery` use. (The
5518
4721
  * `ExecutionPlan` bakes concrete key values into its `keyCondition`, so a single
5519
4722
  * compiled operation object is NOT reusable across param sets; the precompiled
5520
4723
  * product is the resolved model + normalized projection — exactly what the public
5521
- * `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
5522
4725
  * that public path.)
5523
4726
  *
5524
4727
  * ## No runtime capture (the design's key constraint)
@@ -5563,8 +4766,6 @@ interface PreparedParamRef {
5563
4766
  /** The param name, e.g. `userId`. */
5564
4767
  readonly name: string;
5565
4768
  }
5566
- /** Runtime guard: is `value` a {@link PreparedParamRef} (a `$.<name>` read)? */
5567
- declare function isPreparedParamRef(value: unknown): value is PreparedParamRef;
5568
4769
  /** The placeholder proxy handed to a `prepare` body as `$`. Every `$.<name>` read
5569
4770
  * mints a {@link PreparedParamRef}; any further access / coercion throws so the
5570
4771
  * body stays a pure, params-independent template (no runtime capture).
@@ -5582,6 +4783,8 @@ interface PreparedWriteRoute {
5582
4783
  readonly create?: ModelRef;
5583
4784
  readonly update?: ModelRef;
5584
4785
  readonly remove?: ModelRef;
4786
+ /** `upsert` — a `PutItem` with no `attribute_not_exists(PK)` guard (unconditional overwrite; the `definePut` semantics). */
4787
+ readonly upsert?: ModelRef;
5585
4788
  readonly key: Record<string, unknown>;
5586
4789
  readonly input?: Record<string, unknown>;
5587
4790
  readonly condition?: Record<string, unknown>;
@@ -5657,22 +4860,6 @@ declare class PreparedReadStatement {
5657
4860
  private runRoute;
5658
4861
  }
5659
4862
  type PreparedStatement = PreparedReadStatement | PreparedWriteStatement;
5660
- /**
5661
- * The max number of distinct compiled prepared statements the fallback cache
5662
- * retains. Bounded so an unbounded variety of inline `prepare` shapes cannot grow
5663
- * the cache without limit (issue #205 AC). LRU eviction: least-recently-used first.
5664
- */
5665
- declare const PREPARE_CACHE_MAX = 256;
5666
- /**
5667
- * Compile a declarative read/write route body into a reusable prepared statement
5668
- * (issue #205). Backs {@link DDBModel.prepare}.
5669
- *
5670
- * The body is evaluated ONCE with a recording `$` proxy; the resulting routes are
5671
- * classified (all-read or all-write), a structure key is computed, and the compiled
5672
- * handle is memoized in the bounded fallback cache so an inline `prepare(fn)` of the
5673
- * same shape recompiles at most once.
5674
- */
5675
- declare function prepare(body: PreparedBody): PreparedStatement;
5676
4863
 
5677
4864
  /**
5678
4865
  * `subscribe` — the declarative, batch CDC consumer builder (issue #153).
@@ -5747,66 +4934,6 @@ type CdcSubscribeHandlers = keyof CdcModelRegistry extends never ? Record<string
5747
4934
  declare abstract class DDBModel {
5748
4935
  /** @internal Type brand — prevents primitives from structurally matching DDBModel. */
5749
4936
  private readonly __brand;
5750
- /**
5751
- * Register the `DynamoDBClient` GraphDDB sends through.
5752
- *
5753
- * IMPORTANT (issue #111): GraphDDB now owns single-op throttle / transient-error
5754
- * retry (see {@link setRetryPolicy}), so a client left at the AWS SDK default
5755
- * `maxAttempts: 3` retries MULTIPLICATIVELY underneath the library. Construct the
5756
- * client with `maxAttempts: 1` so the library is the single source of truth for
5757
- * retry — e.g. `new DynamoDBClient({ maxAttempts: 1 })`. GraphDDB does NOT mutate
5758
- * a client you pass in.
5759
- */
5760
- static setClient(client: DynamoDBClient): void;
5761
- /**
5762
- * Read back the registered `DynamoDBClient` — the resolved client last passed to
5763
- * {@link setClient}. Symmetric with {@link setClient} and backed by the same
5764
- * {@link ClientManager} store, so it always reflects the client GraphDDB sends
5765
- * through.
5766
- *
5767
- * Throws a clear error if no client has been registered yet (call
5768
- * {@link setClient} first) — matching {@link ClientManager.getClient}'s "loud,
5769
- * never silently `undefined`" convention shared with the rest of the client API.
5770
- */
5771
- static getClient(): DynamoDBClient;
5772
- static setTableMapping(mapping: Record<string, string>): void;
5773
- /**
5774
- * Configure the global single-op retry policy (issue #111) — exponential backoff
5775
- * + jitter with a bounded attempt cap, applied to every GetItem / Query / Put /
5776
- * Update / Delete / relation-fan-out send and to `TransactWriteItems`. Mirrors
5777
- * {@link setClient} / {@link setTableMapping}. A sensible default is ALWAYS on; call
5778
- * this only to tune it (or pass `null` to restore the default). A per-call
5779
- * `retry?: RetryPolicy | false` on an operation's options overrides this globally
5780
- * for that call (`false` disables retry).
5781
- */
5782
- static setRetryPolicy(policy: RetryPolicy | null): void;
5783
- /**
5784
- * Register a {@link Middleware} (issue #50; read path #138, write path #139).
5785
- * One registered object may carry read and/or write hooks.
5786
- *
5787
- * - **Read** R1–R5 — request entry (`read.before`), each physical op before send
5788
- * / after raw fetch (`read.op.before` / `read.op.afterFetch`, INCLUDING every
5789
- * relation fan-out fetch), the final hydrated result (`read.afterFetch`), and on
5790
- * error (`read.op.onError` / `read.onError`).
5791
- * - **Write** W1–W5 — the logical write before effect derivation (`write.before`,
5792
- * may mutate `ctx.input` / `ctx.kind` incl. a delete→update rewrite), after commit
5793
- * (`write.after`), the physical persist on the fully composed batch
5794
- * (`write.persist.before` / `write.persist.after`, fires ONCE per atomic
5795
- * transaction), and on error (`write.persist.onError` / `write.onError`).
5796
- *
5797
- * Hooks may observe, mutate `ctx`, `throw` to cancel, or (on `onError`) recover by
5798
- * returning a value; the library imposes no semantics. Ordering: `before*` runs
5799
- * first-registered-first (FIFO); `after*` / `onError` run last-registered-first
5800
- * (LIFO). Mirrors {@link setRetryPolicy} — a host-runtime global stored on
5801
- * {@link ClientManager}; never serialized (the bridge #48 is unaffected). Per-call
5802
- * host context flows in via the read's / write's `{ context }` option.
5803
- */
5804
- static use(mw: Middleware): void;
5805
- /** Remove every registered middleware (read AND write; teardown / tests). See {@link use}. */
5806
- static clearMiddleware(): void;
5807
- static transaction(fn: (tx: TransactionContext) => void | Promise<void>, options?: {
5808
- context?: RequestContext;
5809
- }): Promise<void>;
5810
4937
  /**
5811
4938
  * In-process multi-route READ (issue #101 — the unified envelope). Each alias
5812
4939
  * route (`{ query | list: Model, key, select, options? }`) runs **independently
@@ -5837,8 +4964,8 @@ declare abstract class DDBModel {
5837
4964
  /**
5838
4965
  * **Prepared statement** (issue #205 / design #203) — the read/write-unified
5839
4966
  * middle tier between the ad-hoc `DDBModel.mutate` / `DDBModel.query` (per-call
5840
- * recompiled) and the public CQRS contract (`publicCommandModel` /
5841
- * `publicQueryModel`, precompiled but contract-welded).
4967
+ * recompiled) and the public CQRS contract (`publishCommand` /
4968
+ * `publishQuery`, precompiled but contract-welded).
5842
4969
  *
5843
4970
  * `prepare($ => ({...}))` **compiles the declarative route body once** and returns
5844
4971
  * a handle whose `.execute(params)` binds the per-call values and runs through the
@@ -5908,20 +5035,6 @@ declare abstract class DDBModel {
5908
5035
  * registered `@cdcProjected` model throws at build time.
5909
5036
  */
5910
5037
  static subscribe(handlers: CdcSubscribeHandlers): ChangeHandler;
5911
- /**
5912
- * Multi-key, multi-model batch read. Each {@link BatchGetRequest} contributes its
5913
- * keys to a chunked `BatchGetItem` per physical table; results are re-grouped by
5914
- * model on the returned {@link BatchGetResult}.
5915
- *
5916
- * Fires the read hooks (issue #142): request-level R1/R4/R5 with kind
5917
- * `'batchGet'`, and op-level R2/R3/R5 for each underlying physical `BatchGetItem`
5918
- * op. The optional `{ context }` is exposed to every hook as `ctx.context` and
5919
- * threaded UNCHANGED to every op (absent ⇒ `{}`), so a global hook (e.g. a
5920
- * tenant-scope R2 FilterExpression) applies across the whole batch. With no
5921
- * middleware registered the read path is unchanged (zero-overhead fast path).
5922
- */
5923
- static batchGet(requests: BatchGetRequest[], options?: BatchGetOptions): Promise<BatchGetResult>;
5924
- static batchWrite(requests: BatchWriteRequest[]): Promise<void>;
5925
5038
  static asModel<C extends new (...args: never[]) => DDBModel>(this: C): ModelStatic<InstanceType<C>, C>;
5926
5039
  }
5927
5040
 
@@ -5950,8 +5063,6 @@ interface Column<V = unknown, M = unknown> {
5950
5063
  /** @internal phantom — owning-model brand. */
5951
5064
  readonly __model?: M;
5952
5065
  }
5953
- /** Runtime guard: is this value a {@link Column} reference? */
5954
- declare function isColumn(value: unknown): value is Column;
5955
5066
  /**
5956
5067
  * The `Model.col` map: one {@link Column} per scalar field of the entity, each
5957
5068
  * typed with the field's declared value type and branded to the model.
@@ -5984,8 +5095,6 @@ interface KeySegment {
5984
5095
  /** The interpolated column references, in order. */
5985
5096
  readonly columns: readonly Column[];
5986
5097
  }
5987
- /** Runtime guard: is this value a {@link KeySegment}? */
5988
- declare function isKeySegment(value: unknown): value is KeySegment;
5989
5098
  /**
5990
5099
  * The interpolation slot accepted by {@link k}: a typed column reference
5991
5100
  * (`c.field`). Bare strings are NOT accepted — a key field must be named via a
@@ -6051,519 +5160,4 @@ declare function key<T extends Record<string, unknown>>(builder: (c: {
6051
5160
  readonly [K in keyof T]-?: Column<T[K], T>;
6052
5161
  }) => KeyStructure): KeyDefinitionMarker<T>;
6053
5162
 
6054
- type DynamoType = 'S' | 'N' | 'BOOL' | 'B' | 'SS' | 'NS' | 'L' | 'M';
6055
- interface FieldOptions {
6056
- format?: 'datetime' | 'date';
6057
- readonly?: boolean;
6058
- serialize?: (value: unknown) => unknown;
6059
- deserialize?: (value: unknown) => unknown;
6060
- /**
6061
- * Optional human-readable description of the field (issue #154). Pure
6062
- * documentation metadata — it does NOT affect storage, hydration, key handling,
6063
- * or any runtime behaviour. When present it is propagated to the field entry in
6064
- * `manifest.json` ({@link ManifestField.description}) and surfaces as a field
6065
- * comment in the generated Python `types.py`. Absent → byte-for-byte unchanged
6066
- * output (backward compatible).
6067
- */
6068
- description?: string;
6069
- }
6070
- interface FieldMetadata {
6071
- propertyName: string;
6072
- dynamoType: DynamoType;
6073
- options?: FieldOptions;
6074
- /**
6075
- * For a `@literal(...)` field — a `string`-stored field whose value is one of a
6076
- * known, finite set — the allowed literal values, in declaration order (issue
6077
- * #75). Recorded purely as model metadata so a Contract param bound into the
6078
- * field can recover the precise `literal` param-kind from its bind position
6079
- * (`src/spec/contracts.ts`), serializing as `{ type: 'literal', literals: [...] }`
6080
- * instead of the imprecise `'string'`. JSON-safe by construction (a non-empty
6081
- * array of strings). `undefined` for every other field decorator (`@string`,
6082
- * `@number`, …), so plain fields are byte-for-byte unchanged (backward compat).
6083
- */
6084
- literals?: readonly string[];
6085
- /**
6086
- * For an `@ttl` field (issue #172, Epic #167 — CFn generator C4) — marks this
6087
- * field as the model's **DynamoDB Time-To-Live** attribute. `@ttl` is a
6088
- * standalone semantic decorator pinned to `number` (it records `dynamoType: 'N'`
6089
- * itself, so `@ttl expiresAt!: string` is a compile error), because DynamoDB TTL
6090
- * requires a Number attribute holding **epoch seconds**. It is a **physical
6091
- * schema** fact (unlike the maintenance signals kept off the manifest): the flag
6092
- * flows through {@link EntityMetadata} into `manifest.json`
6093
- * ({@link ManifestEntity.ttlAttribute}), which the CloudFormation emitter consumes
6094
- * to render `TimeToLiveSpecification`. DynamoDB allows exactly one TTL attribute
6095
- * per table, enforced loudly at model registration (`@model`). `undefined` for
6096
- * every other field decorator, so non-TTL fields are byte-for-byte unchanged.
6097
- */
6098
- ttl?: boolean;
6099
- }
6100
-
6101
- /**
6102
- * The maintenance kind of a model (issue #152). `'entity'` (the default) is a plain
6103
- * stored entity. `'materializedView'` / `'sparseView'` mark a model as a maintained
6104
- * **view** row whose `@maintainedFrom` declarations feed the maintenance graph (the
6105
- * adapter that replaces the old `defineView` / `ViewRegistry` path). A `sparseView`
6106
- * requires every `@maintainedFrom` to carry a `when` membership predicate.
6107
- */
6108
- type ModelKind = 'entity' | 'materializedView' | 'sparseView';
6109
- /**
6110
- * One lowered `@maintainedFrom(source, (self, source) => options)` declaration on a
6111
- * view model (issue #152). It is the decorator-declarative replacement for one
6112
- * `defineView` source slice: the symbolic `(self, source) =>` callback has already
6113
- * been evaluated into the SAME payload-rooted IR (`keyBind` → `$.entity.*` key
6114
- * binding, `project` → {@link ProjectionMap}), so the adapter
6115
- * (`src/relation/maintenance-view-adapter.ts`) reconstructs a byte-identical
6116
- * `ViewSourceSlice` from it.
6117
- *
6118
- * `collection` and `when` are mutually exclusive (a row either holds a maintained
6119
- * list or appears/disappears as a whole — the same rule the old builder enforced).
6120
- */
6121
- interface MaintainedFromMetadata {
6122
- /** Resolves the source entity whose lifecycle events maintain the view row. */
6123
- sourceFactory: () => new (...args: unknown[]) => unknown;
6124
- /** The lifecycle events that maintain the view row (`created`/`updated`/`removed`). */
6125
- on: readonly MaintainEvent[];
6126
- /** The view-row key binding (view-row key field → payload-rooted source value). */
6127
- keyBind: Readonly<Record<string, EffectPath>>;
6128
- /** The projection of the source payload into the view row (target attr → transform). */
6129
- project: ProjectionMap;
6130
- /** Bounded/ordered collection options when this declaration maintains a list. */
6131
- collection?: {
6132
- /** The view-row attribute that holds the maintained list (a `self.<field>`). */
6133
- field: string;
6134
- maxItems?: number;
6135
- order?: 'ASC' | 'DESC';
6136
- /** The source value the collection orders by / trims on (`$.entity.<field>`). */
6137
- orderBy?: EffectPath;
6138
- };
6139
- /** Sparse-view membership predicate (mutually exclusive with `collection`). */
6140
- when?: MembershipPredicate;
6141
- consistency?: MaintainConsistency;
6142
- updateMode?: MaintainUpdateMode;
6143
- /**
6144
- * Optional human-readable description of this maintained-from source slice (issue
6145
- * #166, follow-up of #154), supplied via
6146
- * `@maintainedFrom(() => Source, (self, source) => ({ description, ... }))`. Pure
6147
- * documentation metadata — it does NOT affect the maintenance IR (`keyBind` /
6148
- * `project` / `on` / `collection` / `when`) or any runtime behaviour.
6149
- *
6150
- * NOTE (representation): the maintenance IR is deliberately kept OFF the
6151
- * serializable `manifest.json` / `operations.json` — the {@link Manifest} carries
6152
- * only the physical shape (table / keys / GSIs / relation navigation), NOT the
6153
- * maintenance graph (see `detectStreamMaintenance` in `src/codegen/cloudformation.ts`).
6154
- * There is therefore no manifest / operations / generated-Python site for a
6155
- * maintained-from source; this description is captured at the metadata layer only
6156
- * (mirroring where #154 captured its descriptions before propagation), available to
6157
- * any future consumer that DOES surface `@maintainedFrom` (e.g. the CDC-projection
6158
- * typed-consumer path, #153). Absent → byte-for-byte unchanged metadata.
6159
- */
6160
- description?: string;
6161
- }
6162
- interface KeyDefinition {
6163
- /** The canonical structured key (PK/SK segment lists). */
6164
- segmented: SegmentedKey;
6165
- inputFieldNames: string[];
6166
- }
6167
- interface GsiDefinition {
6168
- indexName: string;
6169
- /** The canonical structured key (PK/SK segment lists). */
6170
- segmented: SegmentedKey;
6171
- inputFieldNames: string[];
6172
- unique: boolean;
6173
- /**
6174
- * Optional human-readable description of the GSI (issue #166, follow-up of #154),
6175
- * supplied via `gsi(name, key, { description })`. Pure documentation metadata — it
6176
- * does NOT affect the index key, projection, or any runtime behaviour. When present
6177
- * it is propagated to the index entry in `manifest.json`
6178
- * ({@link ManifestGsi.description}) and surfaces as the docstring of any generated
6179
- * Python query method that reads through this index (a query carrying the GSI's
6180
- * `indexName`, when the query itself declares no description). Absent →
6181
- * byte-for-byte unchanged output (backward compatible).
6182
- */
6183
- description?: string;
6184
- }
6185
- interface RelationLimitOptions {
6186
- default: number;
6187
- max: number;
6188
- }
6189
- /**
6190
- * The named maintenance pattern a relation participates in (Epic #118 §5.1).
6191
- *
6192
- * `pattern` is the *preset* selector: omitting it leaves the relation a plain
6193
- * read-only navigation (the historical `hasMany`/`belongsTo`/`hasOne`
6194
- * behaviour), exactly backward compatible. When present it names the AWS
6195
- * design pattern the relation lowers to — the lowering itself (maintenance
6196
- * graph / compile injection) is out of scope for issue #121 and lands in
6197
- * #124/#125; here the value is recorded purely as declaration metadata.
6198
- *
6199
- * Typed as a `string` union of the known presets but kept open-ended via the
6200
- * `(string & {})` tail so a future preset can be authored before this union is
6201
- * widened, without a breaking change to callers.
6202
- *
6203
- * The union lists only the presets that are actually *reachable* as a recorded
6204
- * `RelationOptions.pattern` / `AggregateOptions.pattern` value: `samePartition`,
6205
- * `counter`, `embeddedSnapshot`, and the two versioned discriminators
6206
- * `versionedLatest` / `versionedHistory` (issue #152). `materializedView` /
6207
- * `sparseView` migrated to the model-level `@model({ kind })` selector (they are
6208
- * a {@link ModelKind}, not a relation preset); the placeholder `edge` /
6209
- * `dualEdge` / `versioned` / `externalProjection` names are not authored anywhere
6210
- * and were removed. A future preset can still be written against the `(string & {})`
6211
- * tail before it is added to this union.
6212
- */
6213
- type RelationPattern = 'samePartition' | 'counter' | 'embeddedSnapshot' | 'versionedLatest' | 'versionedHistory' | (string & {});
6214
- /**
6215
- * Consistency boundary a maintained write is declared against (Epic #118 §4.A.7):
6216
- * `transactional` = same `TransactWriteItems`; `eventual` = asynchronous catch-up.
6217
- */
6218
- type RelationConsistency = 'transactional' | 'eventual';
6219
- /**
6220
- * How a maintained write is applied (Epic #118 §4.A.7):
6221
- * `mutation` = composed into the same mutation; `stream` = driven asynchronously
6222
- * off a stream / outbox.
6223
- */
6224
- type RelationUpdateMode = 'mutation' | 'stream';
6225
- /**
6226
- * Read-strategy hints for a maintained relation (Epic #118 §5.1 `read`).
6227
- *
6228
- * All fields optional — present only when a `pattern` declares a read shape
6229
- * (e.g. an `embeddedSnapshot` keeping the latest N items). A bare relation
6230
- * carries no `read` block.
6231
- */
6232
- interface RelationReadOptions {
6233
- /** Cap on materialised items kept inline (e.g. "latest 3 posts"). */
6234
- maxItems?: number;
6235
- /** Ordering of the maintained collection. */
6236
- order?: 'ASC' | 'DESC';
6237
- }
6238
- /**
6239
- * Write-maintenance declaration for a relation (Epic #118 §5.1 `write`).
6240
- *
6241
- * `maintainedOn` is the cross-entity trigger list (`"<Entity>.<event>"`, e.g.
6242
- * `"Post.created"`) that the maintenance graph (#124/#125) will key off. Recorded
6243
- * here as declaration metadata only.
6244
- */
6245
- interface RelationWriteOptions {
6246
- /**
6247
- * Cross-entity triggers, e.g. `['Post.created', 'Post.removed']`. The event
6248
- * segment is the confirmed shared lifecycle vocabulary (epic #118 A/B
6249
- * reconciliation): `created` / `updated` / `removed`.
6250
- */
6251
- maintainedOn?: string[];
6252
- consistency?: RelationConsistency;
6253
- updateMode?: RelationUpdateMode;
6254
- }
6255
- /**
6256
- * Snapshot / projection capture map for a maintained relation
6257
- * (Epic #118 §5.1 / §5.2 `projection`).
6258
- *
6259
- * Maps each captured field name on the maintaining side to **how** the source
6260
- * value is projected. The confirmed shared vocabulary (epic #118 A/B
6261
- * reconciliation) is the **function-form IR** — the same {@link
6262
- * ProjectionTransform} node A (#120) defines on `src/define/entity-writes.ts`,
6263
- * re-used here (NOT re-defined) so the two surfaces never re-diverge. Each value
6264
- * is either:
6265
- *
6266
- * - a {@link ProjectionTransform} (built with the standalone authoring helpers,
6267
- * e.g. `preview('body', 120)` — RFC §5.2 `textPreview: preview('body', 120)`),
6268
- * or
6269
- * - a bare `string` — the **identity shorthand**: project that source path
6270
- * through unchanged (e.g. `{ postId: 'postId' }`).
6271
- *
6272
- * `Readonly` so the recorded declaration metadata is not mutated downstream.
6273
- * Issue #121 scopes this to the declaration shape; the maintenance graph /
6274
- * compile injection that consumes the IR is #124/#125.
6275
- *
6276
- * @example `{ postId: 'postId', textPreview: preview('body', 120) }`
6277
- */
6278
- type RelationProjection = Readonly<Record<string, ProjectionTransform | string>>;
6279
- interface RelationOptions {
6280
- limit?: RelationLimitOptions;
6281
- order?: 'ASC' | 'DESC';
6282
- /**
6283
- * Optional human-readable description of the relation (issue #166, follow-up of
6284
- * #154), supplied via `@hasMany/@belongsTo/@hasOne(() => T, keyBind, { description })`.
6285
- * Pure documentation metadata — it does NOT affect navigation, key binding, or any
6286
- * runtime behaviour. When present it is propagated to the relation entry in
6287
- * `manifest.json` ({@link ManifestRelation.description}) and surfaces as a trailing
6288
- * `# …` comment on the relation's field in the generated Python result TypedDict /
6289
- * dataclass. Absent → byte-for-byte unchanged output (backward compatible).
6290
- */
6291
- description?: string;
6292
- /**
6293
- * Named maintenance preset (Epic #118). Omitted = plain read-only navigation,
6294
- * fully backward compatible with the historical relation decorators.
6295
- */
6296
- pattern?: RelationPattern;
6297
- read?: RelationReadOptions;
6298
- write?: RelationWriteOptions;
6299
- projection?: RelationProjection;
6300
- /**
6301
- * Lowered versioned-pattern declaration (issue #152, section B). Present only when
6302
- * the relation declares `pattern: 'versionedLatest' | 'versionedHistory'` via the
6303
- * `@hasOne/@hasMany(() => Target, (self, source) => ({...}))` callback form. The
6304
- * declaring model is the SOURCE (a revision); the relation target is the
6305
- * DESTINATION view row. The adapter
6306
- * (`src/relation/maintenance-view-adapter.ts`) lowers this to the SAME `defineView`
6307
- * snapshot IR the old `defineVersioned` produced (latest = overwrite, history =
6308
- * append). `on` are the lifecycle events; `project` is the lowered projection map.
6309
- */
6310
- versioned?: {
6311
- readonly mode: 'latest' | 'history';
6312
- readonly on: readonly MaintainEvent[];
6313
- readonly project: ProjectionMap;
6314
- readonly consistency?: MaintainConsistency;
6315
- readonly updateMode?: MaintainUpdateMode;
6316
- };
6317
- }
6318
- /**
6319
- * Descriptor for a `refs` relation (issue #197, Pattern 2 Embedded Refs read
6320
- * side). A `refs` relation resolves an **inline id-list** held on the parent row
6321
- * — e.g. `PPost.tagRefs: { tagId: string }[]` — into the referenced child
6322
- * bodies, in ONE deduped `BatchGetItem` (never per-ref `GetItem`).
6323
- *
6324
- * Unlike `hasMany`/`belongsTo`/`hasOne`, whose key is bound from a parent
6325
- * **scalar** field ({@link RelationMetadata.keyBinding} — `target key ← parent
6326
- * scalar`), a `refs` key is bound from EACH ELEMENT of a parent LIST attribute:
6327
- * the traversal reads `parent[from]` (a list), pulls `.key` off every element,
6328
- * and fans those out into a single BatchGet against the target. This descriptor
6329
- * captures exactly that list-valued source shape, which the scalar `keyBinding`
6330
- * `Record<string,string>` cannot express.
6331
- *
6332
- * `keyBinding` on a `refs` relation still records the **target child key field ←
6333
- * element key name** mapping (e.g. `{ tagId: 'tagId' }`), so key building /
6334
- * projection reuse the existing scalar machinery per resolved element; `refs`
6335
- * only adds *where the scalar values come from* (the parent list), not a new key
6336
- * grammar. That keeps `refs` byte-compatible with every scalar-keyBinding
6337
- * consumer (manifest, planner, dedupe) — an unknown-to-them relation kind is
6338
- * simply carried through with a valid scalar `keyBinding`.
6339
- */
6340
- interface RefsBinding {
6341
- /** The parent LIST attribute holding the inline reference elements (e.g. `'tagRefs'`). */
6342
- from: string;
6343
- /** The key field to read off EACH list element (e.g. `'tagId'`). */
6344
- key: string;
6345
- }
6346
- interface RelationMetadata {
6347
- type: 'hasMany' | 'hasOne' | 'belongsTo' | 'refs';
6348
- propertyName: string;
6349
- targetFactory: () => new (...args: unknown[]) => unknown;
6350
- keyBinding: Record<string, string>;
6351
- /**
6352
- * List-valued source descriptor for a `refs` relation (issue #197). Present
6353
- * IFF `type === 'refs'`; the traversal reads the parent list `refs.from`, pulls
6354
- * `refs.key` off each element, and resolves the referenced child bodies in one
6355
- * deduped BatchGet. Absent for every scalar-keyed relation (byte-compatible).
6356
- */
6357
- refs?: RefsBinding;
6358
- options?: RelationOptions;
6359
- }
6360
- /**
6361
- * The scalar-aggregate value an `@aggregate` field derives from its source entity
6362
- * (Epic #118 §5.2 counter / latest preset; issue #122). It is the function-form
6363
- * IR built by the {@link count} / {@link max} value helpers — the aggregation an
6364
- * `@aggregate(() => Source, keyBinding, { pattern, value })` field maintains:
6365
- *
6366
- * - `count` — the cardinality of source rows matched by the key binding (no
6367
- * source field; e.g. `postCount!: number` ← `count()`).
6368
- * - `max` — the maximum value of a named source `field` (e.g. `lastPostAt!:
6369
- * string` ← `max('createdAt')`).
6370
- *
6371
- * Recorded purely as declaration metadata (issue #122 scope = declaration
6372
- * surface): the maintenance graph (#124) / compile injection (#125) consume it;
6373
- * the effect itself is out of scope here. Kept open-ended via the discriminated
6374
- * `op` so a future aggregation (`min`, `sum`, …) extends the union without a
6375
- * breaking change.
6376
- *
6377
- * @example `count()` → `{ op: 'count' }`
6378
- * @example `max('createdAt')` → `{ op: 'max', field: 'createdAt' }`
6379
- */
6380
- type AggregateValue = {
6381
- readonly op: 'count';
6382
- } | {
6383
- readonly op: 'max';
6384
- readonly field: string;
6385
- };
6386
- /**
6387
- * Options for an `@aggregate` field (Epic #118 §5.2 counter / latest preset;
6388
- * issue #122). Mirrors the maintenance vocabulary a relation declares so the two
6389
- * authoring surfaces stay in lockstep:
6390
- *
6391
- * - `pattern` reuses {@link RelationPattern} (e.g. `'counter'`) — the named
6392
- * maintenance preset, NOT re-defined here.
6393
- * - `value` is the {@link AggregateValue} the field maintains (`count()` /
6394
- * `max('createdAt')`).
6395
- * - `write` reuses {@link RelationWriteOptions} verbatim — `maintainedOn`
6396
- * triggers (`Post.created` / `Post.removed`, the confirmed shared
6397
- * `created|updated|removed` event vocabulary) plus `consistency`
6398
- * (`transactional|eventual`) / `updateMode` (`mutation|stream`). These are NOT
6399
- * re-defined; they are the same shared types B (#121) added.
6400
- */
6401
- interface AggregateOptions {
6402
- /** Named maintenance preset (typically `'counter'`); reuses {@link RelationPattern}. */
6403
- pattern?: RelationPattern;
6404
- /** The scalar aggregation this field maintains (`count()` / `max('createdAt')`). */
6405
- value: AggregateValue;
6406
- /** Cross-entity maintenance triggers / consistency / update-mode (reused from B). */
6407
- write?: RelationWriteOptions;
6408
- }
6409
- /**
6410
- * Declaration metadata recorded by an `@aggregate` field decorator (issue #122).
6411
- *
6412
- * An `@aggregate` is a **scalar** field (`postCount!: number`,
6413
- * `lastPostAt!: string`) whose value is derived — by aggregating rows of a source
6414
- * entity resolved by {@link targetFactory} + {@link keyBinding} — rather than
6415
- * stored directly. It is therefore recorded in its **own** metadata array
6416
- * (`EntityMetadata.aggregates`), distinct from both {@link FieldMetadata} (a
6417
- * plain stored attribute) and {@link RelationMetadata} (a navigation yielding
6418
- * model instances): an aggregate carries a source binding like a relation, but
6419
- * surfaces a scalar value like a field. The collector path mirrors relations
6420
- * (decorator pushes, `@model` drains).
6421
- *
6422
- * Issue #122 scope is the declaration surface + metadata pass-through only; the
6423
- * aggregation effect's compile / runtime (#124/#125/#127) is out of scope.
6424
- */
6425
- interface AggregateMetadata {
6426
- /** The scalar field the aggregate value is written to. */
6427
- propertyName: string;
6428
- /** Resolves the source entity whose rows are aggregated. */
6429
- targetFactory: () => new (...args: unknown[]) => unknown;
6430
- /** The key binding selecting the source rows (target key field → source field). */
6431
- keyBinding: Record<string, string>;
6432
- /** The aggregate preset / value / maintenance-write declaration. */
6433
- options: AggregateOptions;
6434
- }
6435
- interface EmbeddedMetadata {
6436
- propertyName: string;
6437
- modelFactory: () => new (...args: unknown[]) => unknown;
6438
- }
6439
- interface EntityMetadata {
6440
- tableName: string;
6441
- prefix: string;
6442
- fields: FieldMetadata[];
6443
- primaryKey: KeyDefinition | null;
6444
- gsiDefinitions: GsiDefinition[];
6445
- relations: RelationMetadata[];
6446
- /** Scalar `@aggregate` fields (Epic #118 §5.2 counter / latest; issue #122). */
6447
- aggregates: AggregateMetadata[];
6448
- embeddedFields: EmbeddedMetadata[];
6449
- /**
6450
- * The model's maintenance kind (issue #152). `'entity'` (default; also assumed
6451
- * when absent, for hand-built metadata in tests) is a plain stored entity;
6452
- * `'materializedView'` / `'sparseView'` mark it a maintained view row fed by
6453
- * {@link maintainedFrom}.
6454
- */
6455
- kind?: ModelKind;
6456
- /**
6457
- * Optional human-readable description of the entity (issue #154), supplied via
6458
- * `@model({ description })`. Pure documentation metadata — it does NOT affect
6459
- * storage, keys, or any runtime behaviour. When present it is propagated to the
6460
- * entity entry in `manifest.json` ({@link ManifestEntity.description}) and
6461
- * surfaces as the class docstring in the generated Python `types.py`. Absent →
6462
- * byte-for-byte unchanged output (backward compatible).
6463
- */
6464
- description?: string;
6465
- /**
6466
- * Class-level `@maintainedFrom(...)` declarations (issue #152). Non-empty only on
6467
- * a `materializedView` / `sparseView` model; each is one source slice the view
6468
- * row is maintained from. The adapter lowers these to the maintenance graph's
6469
- * `ViewDefinition` IR (replacing the old `defineView` / `ViewRegistry` path).
6470
- * Absent / empty for a plain entity.
6471
- */
6472
- maintainedFrom?: MaintainedFromMetadata[];
6473
- /**
6474
- * Whether the model is flagged **CDC-parseable** by `@cdcProjected()` (issue
6475
- * #153). It carries NO runtime side effect — it is purely a compile-/registration-
6476
- * time capability marker: `Model.fromChange(event)` (and, transitively,
6477
- * `DDBModel.subscribe` routing to this model) is gated on it, and it is the SSoT
6478
- * the codegen reads to emit the `CdcModelRegistry` module augmentation that types
6479
- * `subscribe`'s handlers by model name. `true` only on a model carrying the
6480
- * decorator; absent (⇒ falsy) for every plain entity, so a non-`@cdcProjected`
6481
- * model is byte-for-byte unchanged (backward compatible). Deliberately kept OFF
6482
- * the manifest / bridge (it is a host-runtime + TS-typing concern, like the
6483
- * maintenance signals — not a physical-schema fact like {@link ttlAttribute}).
6484
- */
6485
- cdcProjected?: boolean;
6486
- /**
6487
- * The physical attribute name of the model's `@ttl` field, when one is declared
6488
- * (issue #172, Epic #167 — CFn generator C4). DynamoDB TTL requires a single
6489
- * Number attribute holding epoch seconds; `@model` derives this from the (unique)
6490
- * field carrying {@link FieldMetadata.ttl} and throws if more than one field on the
6491
- * SAME model is `@ttl` (the cross-entity, same-physical-table single-TTL rule is
6492
- * enforced downstream, at manifest build, where all entities on a table are known).
6493
- * A non-key field's physical attribute name is its property name, so this is the
6494
- * `@ttl` field's property name. Propagated to the manifest
6495
- * ({@link ManifestEntity.ttlAttribute}) — the SSoT the CloudFormation emitter reads
6496
- * to render `TimeToLiveSpecification`. Absent when the model declares no `@ttl`.
6497
- */
6498
- ttlAttribute?: string;
6499
- }
6500
-
6501
- /**
6502
- * Decorator-metadata → maintenance-graph `ViewDefinition` adapter (issue #152).
6503
- *
6504
- * ## What this replaces
6505
- *
6506
- * The old `defineView` / `defineVersioned` builders (deleted) registered
6507
- * `ViewDefinition`s into a side `ViewRegistry`, which the maintenance graph read.
6508
- * That builder carried a registration side-effect + a `generation` counter (a
6509
- * silent-drop guard). This adapter removes that whole path: it derives the SAME
6510
- * `ViewDefinition` IR purely from the declarative `MetadataRegistry` — the
6511
- * Contract-layer SSoT, whose own `generation` counter is the single
6512
- * invalidation source — so a view declared via `@model({ kind })` + `@maintainedFrom`
6513
- * (and a versioned relation declared via `@hasOne/@hasMany` `pattern`) is discovered
6514
- * with no separate registry.
6515
- *
6516
- * ## IR invariance
6517
- *
6518
- * The `ViewDefinition` / `ViewSourceSlice` shapes are UNCHANGED. The maintenance
6519
- * graph, the K (#130) stream/drain, and the L (#131) rebuild consume the adapter's
6520
- * output exactly as they consumed `ViewRegistry.getAll()`. Only the SOURCE of the
6521
- * definitions moved from a builder side-effect to declarative metadata.
6522
- *
6523
- * ## Order independence (issue #152 hardening)
6524
- *
6525
- * Stacked `@maintainedFrom` declarations carry no order meaning. The adapter rejects
6526
- * — symmetrically, so the error is identical whichever order the decorators are
6527
- * stacked — any two declarations on the same view that:
6528
- * 1. write the SAME projection target attribute (ambiguous duplicate projection);
6529
- * 2. maintain the SAME `collection.field`;
6530
- * 3. bind the SAME view-key field to a DIFFERENT source path (the view-row identity
6531
- * would be inconsistent across sources).
6532
- * A legitimate multi-source view (different sources filling DIFFERENT fields, all
6533
- * binding the SAME key the SAME way) is fine.
6534
- */
6535
-
6536
- type AnyModelClass = abstract new (...args: any[]) => any;
6537
- /** The view source slice IR the maintenance graph consumes (one per source × view). */
6538
- interface ViewSourceSlice {
6539
- readonly sourceClass: AnyModelClass;
6540
- readonly maintainedOn: readonly MaintainTrigger[];
6541
- readonly keys: Readonly<Record<string, EffectPath>>;
6542
- readonly project: ProjectionMap;
6543
- readonly collection?: {
6544
- readonly field: string;
6545
- readonly maxItems?: number;
6546
- readonly orderBy?: EffectPath;
6547
- readonly orderDir?: 'ASC' | 'DESC';
6548
- };
6549
- readonly predicate?: MembershipPredicate;
6550
- }
6551
- /** The view definition IR — an independent view row maintained from one or more sources. */
6552
- interface ViewDefinition {
6553
- readonly name: string;
6554
- readonly viewClass: AnyModelClass;
6555
- readonly pattern: 'materializedView' | 'sparseView';
6556
- readonly slices: readonly ViewSourceSlice[];
6557
- readonly updateMode?: 'mutation' | 'stream';
6558
- readonly consistency?: 'transactional' | 'eventual';
6559
- }
6560
- /**
6561
- * Build every {@link ViewDefinition} for a registry — from `@model({ kind })` +
6562
- * `@maintainedFrom` view models AND from versioned-pattern relations. This is the
6563
- * adapter the maintenance graph calls in place of the deleted `ViewRegistry.getAll()`.
6564
- *
6565
- * @param registry The models to scan. Defaults to `MetadataRegistry.getAll()`.
6566
- */
6567
- declare function collectViewDefinitions(registry?: ReadonlyMap<Function, EntityMetadata>): ViewDefinition[];
6568
-
6569
- export { type DerivedOutboxEvent as $, type ParamDescriptor as A, type BatchResult as B, type CdcEmulatorOptions as C, type DynamoDBOperation as D, type EventLog as E, type FaultSpec as F, type EntityRef as G, type ConditionInput as H, type DefinitionMap as I, type AnyOperationDefinition as J, type PreparedBody as K, type CompiledFragment as L, type ModelStatic as M, type CompiledMutationPlan as N, type DerivedConditionCheck 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 DerivedEdgeWrite as X, type DerivedIdempotencyGuard as Y, type DerivedMaintainOutbox as Z, type DerivedMaintainWrite as _, type CdcMode as a, type CdcModelRegistry as a$, type DerivedUniqueGuard as a0, type DerivedUpdate as a1, type EntityRefResolver as a2, MAX_TRANSACT_COMPOSE_ITEMS as a3, assertNoCrossFragmentMaintainCollision as a4, compileFragment as a5, compileMutationPlan as a6, compileSingleFragmentPlan as a7, resetMaintenanceGraphCache as a8, resolveLifecycle as a9, type SelectBuilderSpec as aA, type RawCondition as aB, type ExecutionPlan as aC, type FieldMetadata as aD, type ResolvedKey as aE, type Slot as aF, type PreparedWriteExecOptions as aG, type CommandReturn as aH, type ParallelOpResult as aI, type PreparedStatement as aJ, type RelationMetadata as aK, type MaintainEffect as aL, type OperationDefinition as aM, type WriteDefinitionOptions as aN, type PartialQueryKeyOf as aO, type StrictSelectSpec as aP, type ReadDefinitionOptions as aQ, type EntityInput as aR, type UniqueQueryKeyOf as aS, type AggregateMetadata as aT, type BatchDeleteRequest as aU, type BatchGetOptions as aV, type BatchGetRequest as aW, BatchGetResult as aX, type BatchPutRequest as aY, type BatchWriteRequest as aZ, CONTRACT_RANGE_FANOUT_CONCURRENCY as a_, resolveMaintainers as aa, type SelectableOf as ab, type PrimaryKeyOf as ac, type RequestContext as ad, type Middleware as ae, type ReadRequestKind as af, type CtxModel as ag, type ReadParams as ah, type ReadRequestCtx as ai, type Item as aj, type RetryPolicy as ak, type RetryOverride as al, type KeyDefinition as am, type GsiDefinition as an, type ModelKind as ao, type FieldOptions as ap, type DynamoType as aq, type ProjectionTransform as ar, type MaintainEvent as as, type MembershipPredicate as at, type MaintainConsistency as au, type MaintainUpdateMode as av, type MembershipPredicateOp as aw, type RelationOptions as ax, type AggregateOptions as ay, type AggregateValue as az, type ChangeBatch as b, type MembershipEffect as b$, type CdcSubscribeHandlers as b0, type Change as b1, type CollectionEffect as b2, type CollectionOptions as b3, type Column as b4, type ColumnMap as b5, type CommandInputShape as b6, type CommandMethod as b7, type CommandPlan as b8, type CommandResultKind as b9, type EmbeddedMetadata as bA, type EmitEffect as bB, type EntityWritesDefinition as bC, type EntityWritesShape as bD, type ExecutableCommandContract as bE, type ExecutableQueryContract as bF, type FilterInput as bG, type FragmentCondition as bH, type FragmentConditionOperatorObject as bI, type FragmentInput as bJ, type GsiDefinitionMarker as bK, type GsiOptions as bL, type IdempotencyEffect as bM, type InProcessWriteDescriptor as bN, type InlineSnapshotSpec as bO, type InputArity as bP, type KeyDefinitionMarker as bQ, type KeySegment as bR, type KeySlot as bS, type KeyStructure as bT, type KeyedResult as bU, LIFECYCLE_CONTRACT_MARKER as bV, type LifecycleContract as bW, type LifecycleEffects as bX, type MaintainItem as bY, type MaintainTrigger as bZ, type MaintenanceGraph as b_, type CommandSelectShape as ba, type CondSlot as bb, type ConditionCheckInput as bc, type Connection as bd, type ContractCallSignature as be, type ContractCommandParams as bf, type ContractComposeNode as bg, type ContractFromRef as bh, type ContractItem as bi, type ContractKeyFieldRef as bj, type ContractKeyInput as bk, type ContractKeyRef as bl, type ContractMethodOp as bm, type ContractParamRef as bn, type ContractQueryParams as bo, type CounterAggregate as bp, type CounterEffect as bq, type CtxBase as br, DEFAULT_MAX_ATTEMPTS as bs, DEFAULT_RETRY_POLICY as bt, type DeleteOptions as bu, type DeriveEffect as bv, type DescriptorBinding as bw, ENTITY_WRITES_MARKER as bx, type EdgeEffect as by, type EffectPath as bz, type ChangeEvent as c, type WriteDescriptor as c$, type ModelRef as c0, type MutateMode as c1, type MutateOptions as c2, type MutateParallelResult as c3, type MutateTransactionResult as c4, type MutationBody as c5, type MutationDescriptorMap as c6, type MutationFragment as c7, type MutationInputProxy as c8, type MutationInputRef as c9, type ReadRouteResult as cA, type RecordedCompose as cB, type RelationBuilder as cC, type RelationConsistency as cD, type RelationLimitOptions as cE, type RelationPattern as cF, type RelationProjection as cG, type RelationReadOptions as cH, type RelationSelect as cI, type RelationSpec as cJ, type RelationUpdateMode as cK, type RelationWriteOptions as cL, type RequiresEffect as cM, type Resolution as cN, type RetryInfo as cO, type RetryOperationKind as cP, type SegmentSpec as cQ, type SegmentedKey as cR, type SelectBuilder as cS, type SelectOf as cT, type SnapshotEffect as cU, TransactionContext as cV, type UniqueEffect as cW, type Updatable as cX, type UpdateOptions as cY, type ViewSourceSlice as cZ, type WriteCtx as c_, type MutationIntent as ca, type OperationKind as cb, PREPARE_CACHE_MAX as cc, type ParamStructure as cd, type PersistCtx as ce, type PersistOrigin as cf, type PlannedCommandMethod as cg, type PreparedInputProxy as ch, type PreparedParamRef as ci, type PreparedReadExecOptions as cj, type PreparedReadRoute as ck, PreparedReadStatement as cl, type PreparedWriteRoute as cm, PreparedWriteStatement as cn, type ProjectionMap as co, type ProjectionTransformOp as cp, type PutOptions as cq, type QueryEnvelopeResult as cr, type QueryKeyOf as cs, type QueryMethod as ct, type QueryResult as cu, type ReadEnvelope as cv, type ReadOpCtx as cw, type ReadOpKind as cx, type ReadRouteDescriptor as cy, type ReadRouteOptions as cz, type ChangeEventName as d, wholeKeysSentinel as d$, type WriteEnvelope as d0, type WriteInput as d1, type WriteKind as d2, type WriteLifecyclePhase as d3, type WriteMiddleware as d4, type WriteRecorder as d5, type WriteResultProjection as d6, attachModelClass as d7, buildDeleteInput as d8, buildMaintenanceGraph as d9, isContractFromRef as dA, isContractKeyFieldRef as dB, isContractKeyRef as dC, isContractParamRef as dD, isEntityWritesDefinition as dE, isKeySegment as dF, isLifecycleContract as dG, isMaintainTrigger as dH, isMutationFragment as dI, isMutationInputRef as dJ, isPlannedCommandMethod as dK, isPreparedParamRef as dL, isQueryModelContract as dM, isRetryableError as dN, isRetryableTransactionCancellation as dO, k as dP, key as dQ, lifecyclePhaseForIntent as dR, maintainTrigger as dS, mintContractKeyFieldRef as dT, mintContractParamRef as dU, mutation as dV, prepare as dW, preview as dX, publicCommandModel as dY, publicQueryModel as dZ, query as d_, buildPutInput as da, buildUpdateInput as db, collectViewDefinitions as dc, cond as dd, contractOfMethodSpec as de, definePlan as df, entityWrites as dg, executeBatchGet as dh, executeBatchWrite as di, executeCommandMethod as dj, executeDelete as dk, executeKeyedBatchGet as dl, executePut as dm, executeQueryMethod as dn, executeRangeFanout as dp, executeTransaction as dq, executeUpdate as dr, from as ds, getEntityWrites as dt, gsi as du, identity as dv, isColumn as dw, isCommandModelContract as dx, isCommandPlan as dy, isContractComposeNode as dz, type ChangeHandler as e, 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 };