graphddb 0.7.10 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/dist/cdc/index.d.ts +389 -5
- package/dist/cdc/index.js +4 -4
- package/dist/{chunk-AD6ZQTTE.js → chunk-GS4C5VGO.js} +2 -6
- package/dist/{chunk-DFUKGU2Q.js → chunk-HNY2EJPV.js} +216 -229
- package/dist/{chunk-EOJDN3SA.js → chunk-I4LEJ4TF.js} +3744 -6144
- package/dist/{chunk-3ZU2VW3L.js → chunk-L2NEDS7U.js} +582 -781
- package/dist/chunk-L4QRCHRQ.js +278 -0
- package/dist/chunk-LGHSZIEE.js +187 -0
- package/dist/chunk-N4NWYNGZ.js +1987 -0
- package/dist/{chunk-PDUVTYC5.js → chunk-XTWXMOHD.js} +0 -1
- package/dist/cli.js +63 -254
- package/dist/index.d.ts +23 -1550
- package/dist/index.js +94 -1850
- package/dist/internal/index.d.ts +84 -0
- package/dist/internal/index.js +701 -0
- package/dist/{maintenance-view-adapter-BAZ9uBGe.d.ts → key-DR7_lpyk.d.ts} +504 -1910
- package/dist/linter/index.d.ts +39 -7
- package/dist/linter/index.js +22 -4
- package/dist/{registry-LWE54Sdc.d.ts → linter-C-vypgut.d.ts} +22 -22
- package/dist/prepared-artifact-BpPgkXEo.d.ts +281 -0
- package/dist/spec/index.d.ts +506 -5
- package/dist/spec/index.js +22 -18
- package/dist/testing/index.d.ts +2 -3
- package/dist/testing/index.js +4 -4
- package/dist/transform/index.d.ts +1 -1
- package/dist/transform/index.js +4 -4
- package/dist/{types-BQLzTEqh.d.ts → types-2PMXEn5x.d.ts} +8 -10
- package/dist/types-BXLzIcQD.d.ts +450 -0
- package/docs/cdc-projection.md +5 -5
- package/docs/class-hydration.md +1 -1
- package/docs/cqrs-contract.md +28 -20
- package/docs/design-patterns.md +5 -5
- package/docs/docs-generation.md +6 -6
- package/docs/middleware.md +15 -15
- package/docs/mutation-command-derivation.md +52 -42
- package/docs/prepared-statements.md +14 -14
- package/docs/python-bridge.md +96 -65
- package/docs/spec.md +153 -124
- package/docs/testing.md +9 -8
- package/package.json +14 -4
- package/dist/chunk-3UD3XIF2.js +0 -860
- package/dist/chunk-MMVHOUM4.js +0 -24
- package/dist/from-change-Ty95KA8C.d.ts +0 -327
- package/dist/index-Dc7d8mWI.d.ts +0 -1089
- package/dist/relation-depth-BRS513Tq.d.ts +0 -36
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
918
|
-
|
|
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
|
-
* `
|
|
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
|
-
* `
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1900
|
-
* `
|
|
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
|
|
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 (`
|
|
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
|
|
2719
|
-
* save contract, and the base write op (create → `PutItem`, update →
|
|
2720
|
-
* `UpdateItem`, remove → `DeleteItem`).
|
|
2721
|
-
|
|
2722
|
-
|
|
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
|
|
2824
|
-
*
|
|
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
|
-
|
|
2958
|
-
declare function isCommandPlan(value: unknown): value is CommandPlan;
|
|
2506
|
+
|
|
2959
2507
|
/**
|
|
2960
|
-
*
|
|
2961
|
-
*
|
|
2962
|
-
*
|
|
2963
|
-
*
|
|
2964
|
-
*
|
|
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
|
-
|
|
2584
|
+
|
|
2585
|
+
declare const TX_REF_BRAND: unique symbol;
|
|
2967
2586
|
/**
|
|
2968
|
-
*
|
|
2969
|
-
*
|
|
2970
|
-
*
|
|
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
|
-
|
|
2995
|
-
|
|
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
|
-
*
|
|
2998
|
-
* `
|
|
2999
|
-
*
|
|
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
|
-
|
|
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 — `
|
|
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
|
|
3014
|
-
*
|
|
3015
|
-
*
|
|
3016
|
-
*
|
|
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 =
|
|
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 (`
|
|
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 `
|
|
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
|
-
/**
|
|
3520
|
-
|
|
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
|
-
* (`
|
|
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
|
-
* (`
|
|
3411
|
+
* (`publishCommand({ create: descriptor, … })`): each value is a
|
|
3814
3412
|
* {@link PublicWriteDescriptor}, a {@link PublicComposeDescriptor} (a `mutation()`
|
|
3815
|
-
* wrapped with `input` / `result` / `mode`),
|
|
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 =
|
|
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 `
|
|
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
|
|
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 =
|
|
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 `
|
|
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
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
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
|
-
/**
|
|
3906
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
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
|
-
} ?
|
|
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). `
|
|
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 `
|
|
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
|
-
* `
|
|
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 (`
|
|
5841
|
-
* `
|
|
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
|
|
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 };
|