graphddb 0.2.3 → 0.2.5

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.
@@ -1,5 +1,131 @@
1
1
  import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
2
2
 
3
+ /**
4
+ * App-level throttle / transient-error retry for single-op sends (issue #111).
5
+ *
6
+ * GraphDDB's single-item ops (GetItem / Query / Put / Update / Delete) and the
7
+ * read fan-out (relation resolution) used to throw on throttling
8
+ * (`ProvisionedThroughputExceededException` / `ThrottlingException`) and rely
9
+ * solely on the AWS SDK v3 default retry, so the LIBRARY itself guaranteed no
10
+ * throttle-resilience. The batch ops already retry partial completion
11
+ * (`UnprocessedKeys` / `UnprocessedItems`, see `src/operations/batch-retry.ts`);
12
+ * this module is the SEPARATE, library-owned ERROR retry that wraps every
13
+ * `docClient.send()` for the single ops via {@link RetryingExecutor}.
14
+ *
15
+ * The two retry layers are DISTINCT and must not be merged:
16
+ * - (A) partial-batch retry (`batch-retry.ts`) — DynamoDB accepted the request
17
+ * but could not process every key/item; the request itself did NOT error.
18
+ * - (B) error retry (this module) — `send()` REJECTED with a throttle / transient
19
+ * error and is retried whole.
20
+ * They share {@link computeBackoffDelay} so backoff is consistent across both,
21
+ * but they are different mechanisms.
22
+ *
23
+ * The default policy is ENABLED out of the box (see {@link DEFAULT_RETRY_POLICY}):
24
+ * even with zero configuration a sensible exponential-backoff-with-jitter,
25
+ * bounded-attempts retry applies. Users tune it globally via
26
+ * `DDBModel.setRetryPolicy(...)` or per-call via the `retry?:` option on the
27
+ * operation option bags; `retry: false` disables retry for that one call.
28
+ *
29
+ * NOTE on the AWS SDK's OWN retry: now that the library owns retry, a
30
+ * `DynamoDBClient` left at the SDK default `maxAttempts: 3` retries
31
+ * MULTIPLICATIVELY underneath this layer. Users should construct their client
32
+ * with `maxAttempts: 1` so the library is the single source of truth for retry
33
+ * (documented at `DDBModel.setClient` and in the README). The library does NOT
34
+ * silently mutate a user-provided client.
35
+ */
36
+ /** The single-op kind a retry is wrapping — surfaced to {@link RetryPolicy.onRetry}. */
37
+ type RetryOperationKind = 'get' | 'query' | 'batchGet' | 'put' | 'update' | 'delete' | 'batchWrite' | 'transactWrite';
38
+ /** The context handed to {@link RetryPolicy.onRetry} before each backoff sleep. */
39
+ interface RetryInfo {
40
+ /** 1-based attempt number that just FAILED and is about to be retried. */
41
+ readonly attempt: number;
42
+ /** The throttle / transient error that triggered this retry. */
43
+ readonly error: unknown;
44
+ /** The backoff delay (ms) about to be slept before the next attempt. */
45
+ readonly delayMs: number;
46
+ /** Which single-op kind is being retried. */
47
+ readonly operation: RetryOperationKind;
48
+ }
49
+ /**
50
+ * A retry policy for the single-op error-retry layer (issue #111). Every field is
51
+ * optional and resolved INDEPENDENTLY, in layers (see {@link resolveRetryPolicy}):
52
+ * a per-call override field wins, else the globally-configured policy's field
53
+ * (from {@link import('../model/DDBModel.js').DDBModel.setRetryPolicy}), else
54
+ * {@link DEFAULT_RETRY_POLICY}. So a partial per-call override (e.g.
55
+ * `{ maxAttempts: 3 }`) overrides ONLY that field and INHERITS the rest from the
56
+ * global policy — a global `onRetry` / `jitter` is NOT dropped. A policy value of
57
+ * `false` on an option bag disables retry entirely for that call.
58
+ */
59
+ interface RetryPolicy {
60
+ /**
61
+ * Maximum number of attempts (the FIRST try counts as attempt 1). `1` disables
62
+ * retry (a single try, no backoff). Default {@link DEFAULT_MAX_ATTEMPTS}.
63
+ */
64
+ readonly maxAttempts?: number;
65
+ /**
66
+ * Compute the base backoff delay (ms) for a 1-based retry attempt, BEFORE jitter.
67
+ * Defaults to {@link computeBackoffDelay} (`min(1000, 50*2^(attempt-1))`), the
68
+ * same ramp the batch partial-retry layer uses, so backoff is consistent across
69
+ * the two layers.
70
+ */
71
+ readonly computeDelay?: (attempt: number) => number;
72
+ /**
73
+ * Jitter strategy applied to the base delay:
74
+ * - `'full'` (default) — uniform random in `[0, base]` (AWS "full jitter"),
75
+ * the recommended strategy to de-correlate retries across clients.
76
+ * - `'none'` — use the base delay verbatim (deterministic; handy in tests).
77
+ */
78
+ readonly jitter?: 'full' | 'none';
79
+ /**
80
+ * Classify whether an error is retryable. Defaults to {@link isRetryableError}
81
+ * (throttle + 5xx / transient). A custom classifier fully replaces the default —
82
+ * it should still avoid retrying validation / conditional-check failures.
83
+ */
84
+ readonly isRetryable?: (error: unknown) => boolean;
85
+ /**
86
+ * Observability hook (issue #111 §8): invoked BEFORE each backoff sleep with the
87
+ * attempt number, the triggering error, the computed (post-jitter) delay, and the
88
+ * operation kind. Never invoked on the terminal success or the terminal failure.
89
+ */
90
+ readonly onRetry?: (info: RetryInfo) => void;
91
+ }
92
+ /** Default cap on attempts: the first try plus up to 9 retries (mirrors the batch cap). */
93
+ declare const DEFAULT_MAX_ATTEMPTS = 10;
94
+ /**
95
+ * The default, always-on retry policy (issue #111 §3). Exponential backoff (the
96
+ * shared {@link computeBackoffDelay} ramp) + full jitter, capped at
97
+ * {@link DEFAULT_MAX_ATTEMPTS} attempts, retrying throttle / 5xx / transient
98
+ * errors only (never validation / conditional-check failures).
99
+ */
100
+ declare const DEFAULT_RETRY_POLICY: Required<Pick<RetryPolicy, 'maxAttempts' | 'computeDelay' | 'jitter' | 'isRetryable'>>;
101
+ /**
102
+ * Default error classifier (issue #111 §5). Retryable when the error is a known
103
+ * throttle / transient fault, an AWS SDK error flagged `$retryable`, or a 5xx /
104
+ * 429 status. NEVER retryable for validation / conditional-check / not-found /
105
+ * access-denied — those are deterministic and would just burn the attempt budget.
106
+ *
107
+ * NOTE: a {@link https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactWriteItems.html
108
+ * TransactionCanceledException} is handled SEPARATELY by
109
+ * {@link isRetryableTransactionCancellation}; this function treats it as
110
+ * non-retryable so the transact path can inspect its `CancellationReasons` first.
111
+ */
112
+ declare function isRetryableError(error: unknown): boolean;
113
+ /**
114
+ * Classify a `TransactionCanceledException` (issue #111 §6). Inspects the
115
+ * `CancellationReasons`:
116
+ * - ANY `ConditionalCheckFailed` reason ⇒ NOT retryable (a deterministic
117
+ * condition failure; retrying would just fail again) — return `false`.
118
+ * - Otherwise, when EVERY non-`None` reason is a throttle / conflict code
119
+ * (`ThrottlingError` / `ProvisionedThroughputExceeded` / `TransactionConflict`)
120
+ * ⇒ retryable — return `true`.
121
+ * - A cancellation with no inspectable reasons, or any other reason code, is
122
+ * treated conservatively as NOT retryable.
123
+ *
124
+ * Only meaningful for an error whose name is `TransactionCanceledException`; the
125
+ * caller checks that first.
126
+ */
127
+ declare function isRetryableTransactionCancellation(error: unknown): boolean;
128
+
3
129
  interface RangeCondition {
4
130
  operator: 'begins_with';
5
131
  key: string;
@@ -652,6 +778,552 @@ type AtLeastOneKey<T> = {
652
778
  */
653
779
  type PartialQueryKeyOf<C> = QueryKeyOf<C> extends infer U ? U extends Record<string, unknown> ? AtLeastOneKey<U> : never : never;
654
780
 
781
+ interface PutOptions<T = unknown> {
782
+ condition?: WriteCondition<T>;
783
+ /**
784
+ * Per-call throttle / transient-error retry override (issue #111). A
785
+ * {@link RetryPolicy} replaces the global policy for this call; `false` disables
786
+ * retry for this call. Omit to use the configured (or built-in default) policy.
787
+ */
788
+ retry?: RetryOverride;
789
+ /**
790
+ * Host-injected per-call write context (issue #50 / #139) — exposed to every
791
+ * write hook (W1–W5) as `ctx.context`. A host-language value, NEVER serialized
792
+ * into the SSoT / `operations.json` (the bridge #48 is unaffected). Absent ⇒ `{}`.
793
+ */
794
+ context?: RequestContext;
795
+ }
796
+ interface UpdateOptions<T = unknown> {
797
+ condition?: WriteCondition<T>;
798
+ /**
799
+ * Per-call throttle / transient-error retry override (issue #111). A
800
+ * {@link RetryPolicy} replaces the global policy for this call; `false` disables
801
+ * retry for this call.
802
+ */
803
+ retry?: RetryOverride;
804
+ /**
805
+ * How to re-derive a GSI key whose composing fields are changed but **not all**
806
+ * available from `{ ...key, ...changes }` (issue #115).
807
+ *
808
+ * - **unset (default)** — refuse with an explicit error rather than let the
809
+ * index silently rot. The error names the index, the changed field, and the
810
+ * missing field(s). The happy path (all composing fields available) re-derives
811
+ * in the SAME `UpdateExpression` and is unaffected by this option.
812
+ * - `'read-modify-write'` — read the current item, merge `{ ...item, ...changes }`,
813
+ * re-derive every affected GSI key from the merged image, and write back under
814
+ * an optimistic condition (the item must still exist / be unchanged). This costs
815
+ * one extra read and is NOT atomic with the original update, but always re-derives
816
+ * correctly.
817
+ */
818
+ rederive?: 'read-modify-write';
819
+ /**
820
+ * Host-injected per-call write context (issue #50 / #139) — exposed to every
821
+ * write hook (W1–W5) as `ctx.context`. NEVER serialized. Absent ⇒ `{}`.
822
+ */
823
+ context?: RequestContext;
824
+ }
825
+ interface DeleteOptions<T = unknown> {
826
+ condition?: WriteCondition<T>;
827
+ /**
828
+ * Per-call throttle / transient-error retry override (issue #111). A
829
+ * {@link RetryPolicy} replaces the global policy for this call; `false` disables
830
+ * retry for this call.
831
+ */
832
+ retry?: RetryOverride;
833
+ /**
834
+ * Host-injected per-call write context (issue #50 / #139) — exposed to every
835
+ * write hook (W1–W5) as `ctx.context`. NEVER serialized. Absent ⇒ `{}`.
836
+ */
837
+ context?: RequestContext;
838
+ }
839
+
840
+ interface PutInput {
841
+ TableName: string;
842
+ Item: Record<string, unknown>;
843
+ ConditionExpression?: string;
844
+ ExpressionAttributeNames?: Record<string, string>;
845
+ ExpressionAttributeValues?: Record<string, unknown>;
846
+ }
847
+ declare function buildPutInput(modelClass: Function, item: Record<string, unknown>, options?: PutOptions): PutInput;
848
+ declare function executePut(modelClass: Function, item: Record<string, unknown>, options?: PutOptions): Promise<void>;
849
+
850
+ interface UpdateInput {
851
+ TableName: string;
852
+ Key: Record<string, unknown>;
853
+ UpdateExpression: string;
854
+ ExpressionAttributeNames: Record<string, string>;
855
+ ExpressionAttributeValues?: Record<string, unknown>;
856
+ ConditionExpression?: string;
857
+ }
858
+ declare function buildUpdateInput(modelClass: Function, entity: Record<string, unknown>, changes: Record<string, unknown>, options?: UpdateOptions): UpdateInput;
859
+ declare function executeUpdate(modelClass: Function, entity: Record<string, unknown>, changes: Record<string, unknown>, options?: UpdateOptions): Promise<void>;
860
+
861
+ interface DeleteInput {
862
+ TableName: string;
863
+ Key: Record<string, unknown>;
864
+ ConditionExpression?: string;
865
+ ExpressionAttributeNames?: Record<string, string>;
866
+ ExpressionAttributeValues?: Record<string, unknown>;
867
+ }
868
+ declare function buildDeleteInput(modelClass: Function, keyObj: Record<string, unknown>, options?: DeleteOptions): DeleteInput;
869
+ declare function executeDelete(modelClass: Function, keyObj: Record<string, unknown>, options?: DeleteOptions): Promise<void>;
870
+
871
+ /** Result of a read operation (GetItem / Query / BatchGetItem). */
872
+ interface ExecutorResult {
873
+ items: Record<string, unknown>[];
874
+ lastEvaluatedKey?: Record<string, unknown>;
875
+ }
876
+ /**
877
+ * Result of a single structured write. `oldItem` is the pre-write image,
878
+ * present only when the caller requested it (`returnOldImage: true`, mapped to
879
+ * DynamoDB `ReturnValues: ALL_OLD`) and an item existed before the write. The
880
+ * write-capture seam (issue #72) reads it to derive INSERT vs MODIFY and the
881
+ * REMOVE OldImage.
882
+ */
883
+ interface WriteResult {
884
+ oldItem?: Record<string, unknown>;
885
+ }
886
+ /**
887
+ * A per-call retry override threaded down to {@link RetryingExecutor} (issue
888
+ * #111). `undefined` ⇒ use the globally-configured (or default) policy; a
889
+ * {@link RetryPolicy} ⇒ use that for this call; `false` ⇒ disable retry for this
890
+ * call. The default {@link DynamoExecutor} and the test `MemoryExecutor` ignore
891
+ * it — only {@link RetryingExecutor} acts on it.
892
+ */
893
+ type RetryOverride = RetryPolicy | false;
894
+ /**
895
+ * Per-call options for the READ path (issue #111). Carries the per-op retry
896
+ * override down to {@link RetryingExecutor}. Extended (rather than passing the
897
+ * override positionally) so future per-call read knobs have a home.
898
+ */
899
+ interface ReadExecOptions {
900
+ /** Per-call retry override; see {@link RetryOverride}. */
901
+ retry?: RetryOverride;
902
+ }
903
+ /** Options shared by the single-item structured write methods. */
904
+ interface WriteExecOptions {
905
+ /** Request the pre-write image (DynamoDB `ReturnValues: ALL_OLD`). */
906
+ returnOldImage?: boolean;
907
+ /** Per-call retry override (issue #111); see {@link RetryOverride}. */
908
+ retry?: RetryOverride;
909
+ }
910
+ /** Per-call options for the batch / transact paths (issue #111). */
911
+ interface BatchExecOptions {
912
+ /** Per-call retry override; see {@link RetryOverride}. */
913
+ retry?: RetryOverride;
914
+ }
915
+ /**
916
+ * One BatchGetItem sub-request: a single physical table plus its keys and an
917
+ * optional projection. Mirrors {@link import('../planner/types.js').BatchGetItemOperation}
918
+ * without the discriminant `type` field, so callers that already chunked keys
919
+ * (relation traversal) and the planner-driven read path share one entry point.
920
+ */
921
+ interface BatchGetExecInput {
922
+ tableName: string;
923
+ keys: Record<string, unknown>[];
924
+ projectionExpression?: string;
925
+ expressionAttributeNames?: Record<string, string>;
926
+ consistentRead?: boolean;
927
+ }
928
+ /** A single item in a {@link Executor.batchWrite} request, grouped by table. */
929
+ type BatchWriteExecItem = {
930
+ type: 'put';
931
+ item: Record<string, unknown>;
932
+ } | {
933
+ type: 'delete';
934
+ key: Record<string, unknown>;
935
+ };
936
+ /**
937
+ * A read-only `ConditionCheck` entry for {@link Executor.transactWrite} (issue
938
+ * #81): it asserts a `ConditionExpression` holds for the keyed item without
939
+ * mutating it. A failed assertion cancels the **whole** `TransactWriteItems`
940
+ * atomically — the foundation for referential-integrity derivation.
941
+ */
942
+ interface ConditionCheckInput {
943
+ TableName: string;
944
+ Key: Record<string, unknown>;
945
+ ConditionExpression: string;
946
+ ExpressionAttributeNames?: Record<string, string>;
947
+ ExpressionAttributeValues?: Record<string, unknown>;
948
+ }
949
+ /**
950
+ * A `{ Put | Update | Delete | ConditionCheck }` entry for
951
+ * {@link Executor.transactWrite}. The first three mutate an item; `ConditionCheck`
952
+ * (issue #81) is a read-only assertion on another item.
953
+ */
954
+ type TransactWriteExecItem = {
955
+ Put: PutInput;
956
+ } | {
957
+ Update: UpdateInput;
958
+ } | {
959
+ Delete: DeleteInput;
960
+ } | {
961
+ ConditionCheck: ConditionCheckInput;
962
+ };
963
+ /**
964
+ * The unified I/O seam (issue #76). All of GraphDDB's read and write I/O is
965
+ * funneled through one injectable executor so the engine (planner / hydrator /
966
+ * relation traversal / write paths) can run unchanged against either real
967
+ * DynamoDB ({@link DynamoExecutor}, the default) or an in-process memory store
968
+ * (`MemoryExecutor`, the test adapter).
969
+ *
970
+ * Reads take a structured {@link DynamoDBOperation}; writes take the structured
971
+ * inputs the operation layer already builds (`PutInput` / `UpdateInput` / …),
972
+ * never an AWS SDK command. This keeps the seam at the *structured operation
973
+ * boundary* (proposal option B): no expression-string parsing is required to
974
+ * implement a fake.
975
+ */
976
+ interface Executor {
977
+ /**
978
+ * Read path: GetItem / Query / BatchGetItem. The optional {@link ReadExecOptions}
979
+ * carries a per-call retry override (issue #111); the default
980
+ * {@link DynamoExecutor} and `MemoryExecutor` ignore it, only `RetryingExecutor`
981
+ * acts on it.
982
+ */
983
+ execute(operation: DynamoDBOperation, options?: ReadExecOptions): Promise<ExecutorResult>;
984
+ /**
985
+ * BatchGetItem for a single table. The caller is responsible for chunking
986
+ * keys ≤100 (the relation traversal and the planner-driven path both do).
987
+ */
988
+ batchGet(input: BatchGetExecInput, options?: ReadExecOptions): Promise<ExecutorResult>;
989
+ /** Put one item. */
990
+ put(input: PutInput, options?: WriteExecOptions): Promise<WriteResult>;
991
+ /** Update one item (applies its structured UpdateExpression). */
992
+ update(input: UpdateInput, options?: WriteExecOptions): Promise<WriteResult>;
993
+ /** Delete one item. */
994
+ delete(input: DeleteInput, options?: WriteExecOptions): Promise<WriteResult>;
995
+ /**
996
+ * BatchWriteItem for a single table (non-atomic). Keys/items are chunked ≤25
997
+ * with UnprocessedItems retry by the implementation.
998
+ */
999
+ batchWrite(tableName: string, items: BatchWriteExecItem[], options?: BatchExecOptions): Promise<void>;
1000
+ /** TransactWriteItems — all-or-nothing atomic. */
1001
+ transactWrite(items: TransactWriteExecItem[], options?: BatchExecOptions): Promise<void>;
1002
+ }
1003
+
1004
+ /**
1005
+ * Write middleware / hook public surface (issue #50 write path, #139). The merged
1006
+ * design is `docs/proposals/read-middleware.md`; this module implements the
1007
+ * **write** hook points W1–W5 and their context objects, layered on the SAME
1008
+ * registry / runtime machinery the read hooks (#138) introduced.
1009
+ *
1010
+ * A write {@link Middleware} hook is host-only, runtime-registered (via
1011
+ * `DDBModel.use`), and **never serialized** — like the read hooks it lives only on
1012
+ * the {@link import('../client/ClientManager.js').ClientManager} host-runtime
1013
+ * singleton and never touches the planner / spec generator / `operations.json`, so
1014
+ * the TS↔Python bridge (#48) is unaffected. It is **unrestricted by intent**: a
1015
+ * hook may observe, mutate `ctx`, `throw` to cancel, or (on `onError`) recover by
1016
+ * returning a value. The library does NOT validate hook logic or impose semantics
1017
+ * (proposal appendix A). The internal CDC / capture mechanism (`src/capture/`,
1018
+ * `src/cdc/`) is a SEPARATE surface — write hooks are not a substitute for it nor
1019
+ * it for them (proposal appendix C).
1020
+ *
1021
+ * @see docs/proposals/read-middleware.md (hook points W1–W5)
1022
+ */
1023
+
1024
+ /**
1025
+ * The logical write kinds a {@link WriteCtx} may carry (W1 / W2 / W5). A W1 hook
1026
+ * may REWRITE this in place — the canonical case is a `delete` → `update`
1027
+ * (soft-delete-by-rewrite); derivation then runs on the rewritten op, so the
1028
+ * stored effect is the update, not the delete (proposal W1 + examples).
1029
+ */
1030
+ type WriteKind = 'put' | 'update' | 'delete';
1031
+ /**
1032
+ * The mutable logical write input a W1 hook observes / mutates. This is the
1033
+ * declarative write payload — NOT a serialized spec — at the granularity the
1034
+ * logical op carries:
1035
+ *
1036
+ * - `put` — the full `item` is in {@link WriteInput.item};
1037
+ * - `update` — the target `key` (entity) is in {@link WriteInput.key} and the
1038
+ * field `changes` in {@link WriteInput.changes};
1039
+ * - `delete` — the target `key` is in {@link WriteInput.key}.
1040
+ *
1041
+ * A W1 hook may mutate these in place (e.g. inject `updatedBy`), and may rewrite
1042
+ * `ctx.kind` together with these fields (a `delete` → `update` sets `changes`).
1043
+ * Keeping `input` and `kind` consistent is the host's responsibility (appendix A).
1044
+ */
1045
+ interface WriteInput {
1046
+ /** The item written by a `put` (its key fields appear here). Mutable in W1. */
1047
+ item?: Record<string, unknown>;
1048
+ /** The target entity / key of an `update` / `delete`. Mutable in W1. */
1049
+ key?: Record<string, unknown>;
1050
+ /** The field changes of an `update`. Mutable in W1 (and when rewriting delete→update). */
1051
+ changes?: Record<string, unknown>;
1052
+ /** The per-call write options (`condition` / `retry` / `rederive` / …). Mutable in W1. */
1053
+ options?: Record<string, unknown>;
1054
+ }
1055
+ /**
1056
+ * W1 / W2 / W5 (logical-write-level) context. `before` (W1) may mutate
1057
+ * {@link WriteCtx.input} and {@link WriteCtx.kind} (incl. the delete→update
1058
+ * rewrite) and may `throw` to cancel the write (in a transaction, aborting ALL
1059
+ * ops); `after` (W2) observes the committed change `{ oldImage?, newImage? }`;
1060
+ * `onError` (W5) sees the logical-level failure and may recover by returning a
1061
+ * value (symmetric with the read `onError`).
1062
+ */
1063
+ interface WriteCtx extends CtxBase {
1064
+ /** The logical write kind. Mutable in W1 (incl. delete→update rewrite). */
1065
+ kind: WriteKind;
1066
+ /** The mutable logical write payload (W1). */
1067
+ input: WriteInput;
1068
+ /**
1069
+ * Present when this logical write is part of an atomic transaction
1070
+ * (`DDBModel.transaction` / `mutate({ mode: 'transaction' })`). Its `id` symbol
1071
+ * is shared by every logical op in the same atomic batch, so a hook can
1072
+ * correlate the W1/W2 ops that compose one transaction. Absent for a
1073
+ * non-transactional single-op write (and for `parallel`-mode ops, which are
1074
+ * independent).
1075
+ */
1076
+ readonly transaction?: {
1077
+ readonly id: symbol;
1078
+ };
1079
+ }
1080
+ /**
1081
+ * The committed change handed to a W2 (`write.after`) hook. A
1082
+ * `TransactWriteItems` returns no item images (spec §14), so within a transaction
1083
+ * the images are best-effort (often absent); a single-op write may carry the
1084
+ * pre-write `oldImage` when one was fetched.
1085
+ */
1086
+ interface Change {
1087
+ readonly oldImage?: Record<string, unknown>;
1088
+ readonly newImage?: Record<string, unknown>;
1089
+ }
1090
+ /** One origin entry describing a logical op that contributed to a persist batch. */
1091
+ interface PersistOrigin {
1092
+ readonly model: CtxModel;
1093
+ readonly kind: WriteKind;
1094
+ }
1095
+ /**
1096
+ * W3 / W4 / W5 (physical-persist-level) context — fires AFTER derivation, on the
1097
+ * REAL composed batch. `before` (W3) may mutate {@link PersistCtx.items} (the
1098
+ * fully composed `Put` / `Update` / `Delete` / `ConditionCheck` set: the user
1099
+ * write(s) PLUS every derived edge / counter / GSI re-derivation / referential
1100
+ * ConditionCheck) and may `throw` to abort the batch — mutations are honored on
1101
+ * the ACTUAL send. `after` (W4) observes the executor's results; `onError` (W5)
1102
+ * sees the persist-level failure and may recover by returning a value.
1103
+ *
1104
+ * In a transaction the persist hooks fire ONCE for the whole atomic batch; for a
1105
+ * non-transactional single-op write `items` is the one-element `[{ Put|Update|Delete }]`.
1106
+ */
1107
+ interface PersistCtx {
1108
+ /** The fully composed physical batch (user + derived). Mutable in W3. */
1109
+ items: TransactWriteExecItem[];
1110
+ /** The logical ops that composed this batch (one per contributing W-level op). */
1111
+ readonly origins: readonly PersistOrigin[];
1112
+ /** The host-injected per-call context (`{}` when the write passed none). */
1113
+ readonly context: RequestContext;
1114
+ /** Scratch shared across this persist op's W3 → W4 → W5 phases. */
1115
+ readonly state: Record<string, unknown>;
1116
+ /** Present (with the shared `id`) when this batch is an atomic transaction. */
1117
+ readonly transaction?: {
1118
+ readonly id: symbol;
1119
+ };
1120
+ }
1121
+ /**
1122
+ * The write half of a registered {@link import('./types.js').Middleware}. Any
1123
+ * subset of the write hook points may be set; an unset hook is skipped. All hooks
1124
+ * are async-first — the runtime always `await`s, so sync and async hooks compose
1125
+ * uniformly (proposal "sync / async").
1126
+ *
1127
+ * @see docs/proposals/read-middleware.md (hook points W1–W5)
1128
+ */
1129
+ interface WriteMiddleware {
1130
+ /** W1 — logical write, before effect derivation. May mutate `ctx.input` / `ctx.kind`; `throw` cancels (in a tx, aborts all). */
1131
+ before?(ctx: WriteCtx): void | Promise<void>;
1132
+ /** W2 — logical write, after commit. Observes the committed `{ oldImage?, newImage? }`. */
1133
+ after?(ctx: WriteCtx, change: Change): void | Promise<void>;
1134
+ persist?: {
1135
+ /** W3 — physical persist, after derivation, on the real batch. May mutate `ctx.items`; `throw` aborts the batch. */
1136
+ before?(ctx: PersistCtx): void | Promise<void>;
1137
+ /** W4 — physical persist, after the executor returns. Observes `results`. */
1138
+ after?(ctx: PersistCtx, results: unknown): void | Promise<void>;
1139
+ /**
1140
+ * W5 (persist-level) — the persist failed. Runs LIFO. **Recover** by RETURNING
1141
+ * a non-`undefined` value: the persist is treated as succeeded and the value is
1142
+ * available to W4 (the first hook that returns a value wins and short-circuits
1143
+ * the chain). Return `void` / `undefined` to decline — if no hook recovers, the
1144
+ * original error rethrows (symmetric with the read `op.onError`).
1145
+ */
1146
+ onError?(ctx: PersistCtx, err: unknown): unknown;
1147
+ };
1148
+ /**
1149
+ * W5 (logical-level) — the logical write failed. Runs LIFO. **Recover** by
1150
+ * RETURNING a non-`undefined` value: it becomes the write's resolved value in
1151
+ * place of throwing (the first hook that returns a value wins). Return `void` /
1152
+ * `undefined` to decline — if no hook recovers, the original error rethrows
1153
+ * (symmetric with the read `onError`).
1154
+ */
1155
+ onError?(ctx: WriteCtx, err: unknown): unknown;
1156
+ }
1157
+
1158
+ /**
1159
+ * Read middleware / hook public surface (issue #50, implemented for the read
1160
+ * path in #138). The merged design is `docs/proposals/read-middleware.md`; this
1161
+ * module implements the **read** hook points R1–R5 and their context objects.
1162
+ *
1163
+ * A {@link Middleware} is a host-only, runtime-registered object (registered via
1164
+ * `DDBModel.use`) that runs at fixed seams in the read pipeline. Hooks are
1165
+ * **never serialized** — they live only on the {@link import('../client/ClientManager.js').ClientManager}
1166
+ * host-runtime singleton and never touch the planner / spec generator /
1167
+ * `operations.json`, so the TS↔Python bridge (#48) is unaffected. Hooks are
1168
+ * **unrestricted by intent**: a hook may observe, mutate `ctx`, `throw` to
1169
+ * cancel, or return a replacement value. The library does NOT validate hook
1170
+ * logic or impose semantics — see the proposal appendix A.
1171
+ *
1172
+ * @see docs/proposals/read-middleware.md
1173
+ */
1174
+
1175
+ /** A raw DynamoDB item (the shape R3 sees, per-op). */
1176
+ type Item = Record<string, unknown>;
1177
+ /**
1178
+ * Host-injected, library-opaque per-call context — the value passed as
1179
+ * `{ context }` on a read. Threaded unchanged to every R2/R3 fan-out op (issue
1180
+ * #138). A read issued with no `{ context }` sees `{}`.
1181
+ */
1182
+ type RequestContext = Record<string, unknown>;
1183
+ /**
1184
+ * The `ModelStatic` shape the library hands to a hook's `ctx.model`. The
1185
+ * concrete entity type is erased at the hook boundary (a hook is registered
1186
+ * globally and may see any model), so it is the permissive
1187
+ * `ModelStatic<DDBModel>`.
1188
+ */
1189
+ type CtxModel = ModelStatic<DDBModel>;
1190
+ /** Fields shared by every read context object. */
1191
+ interface CtxBase {
1192
+ /** The model the read targets. */
1193
+ readonly model: CtxModel;
1194
+ /** The host-injected per-call context (`{}` when the read passed none). */
1195
+ readonly context: RequestContext;
1196
+ /**
1197
+ * Scratch space shared across this op's phases (R2 → R3 → R5 for one op, or
1198
+ * R1 → R4 → R5 for the request). A fresh object per request context object;
1199
+ * each fan-out op gets its own `state` (see proposal appendix D — siblings run
1200
+ * concurrently, so a shared accumulator should live on a host object via
1201
+ * `context`, updated commutatively).
1202
+ */
1203
+ readonly state: Record<string, unknown>;
1204
+ }
1205
+ /**
1206
+ * The logical read kinds an {@link ReadRequestCtx} may carry (R1 / R4 / R5).
1207
+ *
1208
+ * Only the kinds the runtime actually emits are advertised. A `query` route in
1209
+ * the read envelope delegates to {@link import('../operations/query.js').executeQuery}
1210
+ * (kind `'query'`), a `list` route to {@link import('../operations/list.js').executeList}
1211
+ * (kind `'list'`), so there is no distinct `'envelopeRoute'` kind. `batchGet`
1212
+ * (`DDBModel.batchGet`, the public multi-key read primitive) fires request-level
1213
+ * hooks since issue #142 — its R1/R4/R5 carry kind `'batchGet'`; each underlying
1214
+ * physical `BatchGetItem` op fires R2/R3/R5 with op kind `'BatchGetItem'`.
1215
+ */
1216
+ type ReadRequestKind = 'query' | 'list' | 'batchGet';
1217
+ /**
1218
+ * The mutable request-level parameters R1 may observe / mutate. This is the
1219
+ * declarative read input — `key` / `select` / and per-call read options — NOT a
1220
+ * serialized spec. A hook may mutate these in place before key-resolution /
1221
+ * planning (R1).
1222
+ *
1223
+ * For a `batchGet` read (kind `'batchGet'`, issue #142) there is no single
1224
+ * `key` / `select` — the read spans multiple models/keys — so `key` / `select`
1225
+ * are empty placeholders and the mutable batch input lives on
1226
+ * {@link ReadParams.requests}; the library re-reads `requests` after R1 runs.
1227
+ */
1228
+ interface ReadParams {
1229
+ /** The key input (PK / GSI fields). Mutable in R1. Empty `{}` for `batchGet`. */
1230
+ key: Record<string, unknown>;
1231
+ /** The select / projection spec (plain object or a `project(...)` builder). Mutable in R1. Empty `{}` for `batchGet`. */
1232
+ select: Record<string, unknown>;
1233
+ /**
1234
+ * The per-call read options (`consistentRead` / `maxDepth` / `limit` /
1235
+ * `after` / `order` / `filter` / `updatable` / …) for this read kind. Mutable
1236
+ * in R1. The library re-reads these after R1 runs.
1237
+ */
1238
+ options: Record<string, unknown>;
1239
+ /**
1240
+ * Only for a `batchGet` read (issue #142): the mutable list of batch-get
1241
+ * requests (each `{ model, keys }`). A hook may add / drop / mutate requests
1242
+ * or their keys in place before the underlying `BatchGetItem` ops are issued;
1243
+ * the library re-reads this after R1 runs. `undefined` for `query` / `list`.
1244
+ */
1245
+ requests?: unknown[];
1246
+ }
1247
+ /**
1248
+ * R1 / R4 / R5 (request-level) context. `before` (R1) may mutate
1249
+ * {@link ReadParams} and may `throw` to cancel the whole read; `afterFetch` (R4)
1250
+ * receives the final hydrated, relation-merged result and may return a
1251
+ * replacement; `onError` (R5) sees a request-level failure.
1252
+ */
1253
+ interface ReadRequestCtx extends CtxBase {
1254
+ readonly kind: ReadRequestKind;
1255
+ /** Mutable in R1 (request entry, before key-resolution / plan). */
1256
+ params: ReadParams;
1257
+ }
1258
+ /** The physical op kinds an {@link ReadOpCtx} may carry (R2 / R3 / R5). */
1259
+ type ReadOpKind = 'GetItem' | 'Query' | 'BatchGetItem';
1260
+ /**
1261
+ * R2 / R3 / R5 (op-level) context — fires for the root read AND for every
1262
+ * relation fan-out fetch (incl. nested recursion). `before` (R2) may mutate
1263
+ * {@link ReadOpCtx.operation} before it is sent and may `throw` to cancel;
1264
+ * `afterFetch` (R3) receives the op's raw items and may return a replacement
1265
+ * array; `onError` (R5) sees the op-level failure.
1266
+ */
1267
+ interface ReadOpCtx extends CtxBase {
1268
+ readonly kind: ReadOpKind;
1269
+ /** The physical operation about to be sent. Mutable in R2. */
1270
+ operation: DynamoDBOperation;
1271
+ /**
1272
+ * The relation path from the root read to this op: `[]` for the root read,
1273
+ * `['orders']` for a direct relation fetch, `['orders', 'product']` for a
1274
+ * nested one. Lets a hook scope behavior to a specific fan-out leg.
1275
+ */
1276
+ readonly relationPath: readonly string[];
1277
+ }
1278
+ /**
1279
+ * A registered read middleware object. Any subset of the read hook points may
1280
+ * be set; an unset hook is simply skipped. All hooks may be synchronous or
1281
+ * return a `Promise` — the runtime always `await`s (async-first, see proposal
1282
+ * "sync / async").
1283
+ *
1284
+ * @see docs/proposals/read-middleware.md (the write hooks W1–W5 are a later phase)
1285
+ */
1286
+ interface Middleware {
1287
+ /** Optional name (for host-side debugging / ordering introspection). */
1288
+ name?: string;
1289
+ read?: {
1290
+ /** R1 — request entry, before key-resolution / plan. May mutate `ctx.params`; `throw` cancels. */
1291
+ before?(ctx: ReadRequestCtx): void | Promise<void>;
1292
+ /** R4 — final hydrated, relation-merged result. Returns the (possibly replaced) result. */
1293
+ afterFetch?<T>(ctx: ReadRequestCtx, result: T): T | Promise<T>;
1294
+ op?: {
1295
+ /** R2 — each physical op, before send. May mutate `ctx.operation`; `throw` cancels. */
1296
+ before?(ctx: ReadOpCtx): void | Promise<void>;
1297
+ /** R3 — each physical op's raw items. Returns the (possibly replaced) items. */
1298
+ afterFetch?(ctx: ReadOpCtx, items: Item[]): Item[] | Promise<Item[]>;
1299
+ /**
1300
+ * R5 (op-level) — a physical op failed. Runs LIFO. **Recover** by RETURNING
1301
+ * an `Item[]`: those become this op's items and the read pipeline continues
1302
+ * (the first hook that returns a value wins and short-circuits the chain).
1303
+ * Return `void` / `undefined` to decline — if no hook recovers, the original
1304
+ * error rethrows. (See proposal R5 + appendix A — hooks are unrestricted.)
1305
+ */
1306
+ onError?(ctx: ReadOpCtx, err: unknown): void | Item[] | Promise<void | Item[]>;
1307
+ };
1308
+ /**
1309
+ * R5 (request-level) — the read failed. Runs LIFO. **Recover** by RETURNING a
1310
+ * value: it becomes the read's result in place of throwing (the first hook
1311
+ * that returns a non-`undefined` value wins and short-circuits the chain).
1312
+ * Return `void` / `undefined` to decline — if no hook recovers, the original
1313
+ * error rethrows. The returned value is the read's resolved value verbatim, so
1314
+ * its shape is the host's responsibility (e.g. `null` / an item for `query`, a
1315
+ * `{ items, cursor }` for `list`).
1316
+ */
1317
+ onError?(ctx: ReadRequestCtx, err: unknown): unknown;
1318
+ };
1319
+ /**
1320
+ * The write hook points W1–W5 (issue #50 write path, #139). Any subset may be
1321
+ * set; one registered object may carry read and/or write hooks. See
1322
+ * {@link WriteMiddleware}.
1323
+ */
1324
+ write?: WriteMiddleware;
1325
+ }
1326
+
655
1327
  /**
656
1328
  * A top-level `select` may be either a plain projection (`SelectableOf<T>`) or
657
1329
  * a {@link SelectBuilder} produced by `Model.project(...)`. The builder is
@@ -721,6 +1393,13 @@ type ListCallOptions<T> = {
721
1393
  * `& Updatable`, so it can be passed straight back to `updateItem()`.
722
1394
  */
723
1395
  updatable?: boolean;
1396
+ /**
1397
+ * Host-injected per-call read context (issue #50 / #138) — passed to every
1398
+ * registered read hook as `ctx.context` and threaded to every relation
1399
+ * fan-out op. A host-language value, never serialized (the bridge #48 is
1400
+ * unaffected). Absent ⇒ `{}`.
1401
+ */
1402
+ context?: RequestContext;
724
1403
  };
725
1404
  /**
726
1405
  * Public, type-inferring API surface produced by {@link DDBModel.asModel}.
@@ -788,6 +1467,8 @@ interface ModelStatic<T extends DDBModel, C = unknown> {
788
1467
  maxDepth?: number;
789
1468
  updatable?: boolean;
790
1469
  hydrate: (raw: QueryResult<T, ProjectionOf<T, Sel>>) => R;
1470
+ /** Host-injected per-call read context (issue #50 / #138); see {@link ListCallOptions.context}. */
1471
+ context?: RequestContext;
791
1472
  }): Promise<R | null>;
792
1473
  /**
793
1474
  * Fetch a single item by a unique key, requesting an **updatable** result.
@@ -801,6 +1482,8 @@ interface ModelStatic<T extends DDBModel, C = unknown> {
801
1482
  consistentRead?: boolean;
802
1483
  maxDepth?: number;
803
1484
  updatable: true;
1485
+ /** Host-injected per-call read context (issue #50 / #138); see {@link ListCallOptions.context}. */
1486
+ context?: RequestContext;
804
1487
  }): Promise<(QueryResult<T, ProjectionOf<T, Sel>> & Updatable) | null>;
805
1488
  /**
806
1489
  * Fetch a single item by a unique key. The return type contains only the
@@ -810,6 +1493,8 @@ interface ModelStatic<T extends DDBModel, C = unknown> {
810
1493
  consistentRead?: boolean;
811
1494
  maxDepth?: number;
812
1495
  updatable?: false;
1496
+ /** Host-injected per-call read context (issue #50 / #138); see {@link ListCallOptions.context}. */
1497
+ context?: RequestContext;
813
1498
  }): Promise<QueryResult<T, ProjectionOf<T, Sel>> | null>;
814
1499
  /**
815
1500
  * List items for a partition, symmetric with {@link ModelStatic.query}:
@@ -859,166 +1544,6 @@ interface ModelStatic<T extends DDBModel, C = unknown> {
859
1544
  deleteItem(key: DeleteKey<C>, options?: WriteOptions<T>): Promise<void>;
860
1545
  }
861
1546
 
862
- interface PutOptions<T = unknown> {
863
- condition?: WriteCondition<T>;
864
- }
865
- interface UpdateOptions<T = unknown> {
866
- condition?: WriteCondition<T>;
867
- /**
868
- * How to re-derive a GSI key whose composing fields are changed but **not all**
869
- * available from `{ ...key, ...changes }` (issue #115).
870
- *
871
- * - **unset (default)** — refuse with an explicit error rather than let the
872
- * index silently rot. The error names the index, the changed field, and the
873
- * missing field(s). The happy path (all composing fields available) re-derives
874
- * in the SAME `UpdateExpression` and is unaffected by this option.
875
- * - `'read-modify-write'` — read the current item, merge `{ ...item, ...changes }`,
876
- * re-derive every affected GSI key from the merged image, and write back under
877
- * an optimistic condition (the item must still exist / be unchanged). This costs
878
- * one extra read and is NOT atomic with the original update, but always re-derives
879
- * correctly.
880
- */
881
- rederive?: 'read-modify-write';
882
- }
883
- interface DeleteOptions<T = unknown> {
884
- condition?: WriteCondition<T>;
885
- }
886
-
887
- interface PutInput {
888
- TableName: string;
889
- Item: Record<string, unknown>;
890
- ConditionExpression?: string;
891
- ExpressionAttributeNames?: Record<string, string>;
892
- ExpressionAttributeValues?: Record<string, unknown>;
893
- }
894
- declare function buildPutInput(modelClass: Function, item: Record<string, unknown>, options?: PutOptions): PutInput;
895
- declare function executePut(modelClass: Function, item: Record<string, unknown>, options?: PutOptions): Promise<void>;
896
-
897
- interface UpdateInput {
898
- TableName: string;
899
- Key: Record<string, unknown>;
900
- UpdateExpression: string;
901
- ExpressionAttributeNames: Record<string, string>;
902
- ExpressionAttributeValues?: Record<string, unknown>;
903
- ConditionExpression?: string;
904
- }
905
- declare function buildUpdateInput(modelClass: Function, entity: Record<string, unknown>, changes: Record<string, unknown>, options?: UpdateOptions): UpdateInput;
906
- declare function executeUpdate(modelClass: Function, entity: Record<string, unknown>, changes: Record<string, unknown>, options?: UpdateOptions): Promise<void>;
907
-
908
- interface DeleteInput {
909
- TableName: string;
910
- Key: Record<string, unknown>;
911
- ConditionExpression?: string;
912
- ExpressionAttributeNames?: Record<string, string>;
913
- ExpressionAttributeValues?: Record<string, unknown>;
914
- }
915
- declare function buildDeleteInput(modelClass: Function, keyObj: Record<string, unknown>, options?: DeleteOptions): DeleteInput;
916
- declare function executeDelete(modelClass: Function, keyObj: Record<string, unknown>, options?: DeleteOptions): Promise<void>;
917
-
918
- /** Result of a read operation (GetItem / Query / BatchGetItem). */
919
- interface ExecutorResult {
920
- items: Record<string, unknown>[];
921
- lastEvaluatedKey?: Record<string, unknown>;
922
- }
923
- /**
924
- * Result of a single structured write. `oldItem` is the pre-write image,
925
- * present only when the caller requested it (`returnOldImage: true`, mapped to
926
- * DynamoDB `ReturnValues: ALL_OLD`) and an item existed before the write. The
927
- * write-capture seam (issue #72) reads it to derive INSERT vs MODIFY and the
928
- * REMOVE OldImage.
929
- */
930
- interface WriteResult {
931
- oldItem?: Record<string, unknown>;
932
- }
933
- /** Options shared by the single-item structured write methods. */
934
- interface WriteExecOptions {
935
- /** Request the pre-write image (DynamoDB `ReturnValues: ALL_OLD`). */
936
- returnOldImage?: boolean;
937
- }
938
- /**
939
- * One BatchGetItem sub-request: a single physical table plus its keys and an
940
- * optional projection. Mirrors {@link import('../planner/types.js').BatchGetItemOperation}
941
- * without the discriminant `type` field, so callers that already chunked keys
942
- * (relation traversal) and the planner-driven read path share one entry point.
943
- */
944
- interface BatchGetExecInput {
945
- tableName: string;
946
- keys: Record<string, unknown>[];
947
- projectionExpression?: string;
948
- expressionAttributeNames?: Record<string, string>;
949
- consistentRead?: boolean;
950
- }
951
- /** A single item in a {@link Executor.batchWrite} request, grouped by table. */
952
- type BatchWriteExecItem = {
953
- type: 'put';
954
- item: Record<string, unknown>;
955
- } | {
956
- type: 'delete';
957
- key: Record<string, unknown>;
958
- };
959
- /**
960
- * A read-only `ConditionCheck` entry for {@link Executor.transactWrite} (issue
961
- * #81): it asserts a `ConditionExpression` holds for the keyed item without
962
- * mutating it. A failed assertion cancels the **whole** `TransactWriteItems`
963
- * atomically — the foundation for referential-integrity derivation.
964
- */
965
- interface ConditionCheckInput {
966
- TableName: string;
967
- Key: Record<string, unknown>;
968
- ConditionExpression: string;
969
- ExpressionAttributeNames?: Record<string, string>;
970
- ExpressionAttributeValues?: Record<string, unknown>;
971
- }
972
- /**
973
- * A `{ Put | Update | Delete | ConditionCheck }` entry for
974
- * {@link Executor.transactWrite}. The first three mutate an item; `ConditionCheck`
975
- * (issue #81) is a read-only assertion on another item.
976
- */
977
- type TransactWriteExecItem = {
978
- Put: PutInput;
979
- } | {
980
- Update: UpdateInput;
981
- } | {
982
- Delete: DeleteInput;
983
- } | {
984
- ConditionCheck: ConditionCheckInput;
985
- };
986
- /**
987
- * The unified I/O seam (issue #76). All of GraphDDB's read and write I/O is
988
- * funneled through one injectable executor so the engine (planner / hydrator /
989
- * relation traversal / write paths) can run unchanged against either real
990
- * DynamoDB ({@link DynamoExecutor}, the default) or an in-process memory store
991
- * (`MemoryExecutor`, the test adapter).
992
- *
993
- * Reads take a structured {@link DynamoDBOperation}; writes take the structured
994
- * inputs the operation layer already builds (`PutInput` / `UpdateInput` / …),
995
- * never an AWS SDK command. This keeps the seam at the *structured operation
996
- * boundary* (proposal option B): no expression-string parsing is required to
997
- * implement a fake.
998
- */
999
- interface Executor {
1000
- /** Read path: GetItem / Query / BatchGetItem. */
1001
- execute(operation: DynamoDBOperation): Promise<ExecutorResult>;
1002
- /**
1003
- * BatchGetItem for a single table. The caller is responsible for chunking
1004
- * keys ≤100 (the relation traversal and the planner-driven path both do).
1005
- */
1006
- batchGet(input: BatchGetExecInput): Promise<ExecutorResult>;
1007
- /** Put one item. */
1008
- put(input: PutInput, options?: WriteExecOptions): Promise<WriteResult>;
1009
- /** Update one item (applies its structured UpdateExpression). */
1010
- update(input: UpdateInput, options?: WriteExecOptions): Promise<WriteResult>;
1011
- /** Delete one item. */
1012
- delete(input: DeleteInput, options?: WriteExecOptions): Promise<WriteResult>;
1013
- /**
1014
- * BatchWriteItem for a single table (non-atomic). Keys/items are chunked ≤25
1015
- * with UnprocessedItems retry by the implementation.
1016
- */
1017
- batchWrite(tableName: string, items: BatchWriteExecItem[]): Promise<void>;
1018
- /** TransactWriteItems — all-or-nothing atomic. */
1019
- transactWrite(items: TransactWriteExecItem[]): Promise<void>;
1020
- }
1021
-
1022
1547
  type TransactWriteItemInput = {
1023
1548
  Put: PutInput;
1024
1549
  } | {
@@ -1045,9 +1570,32 @@ interface TransactCaptureMeta {
1045
1570
  };
1046
1571
  newItem?: Record<string, unknown>;
1047
1572
  }
1573
+ /**
1574
+ * One recorded logical write op of an imperative {@link TransactionContext}
1575
+ * (issue #139). Captured at `tx.put/update/delete` call time so the write hooks
1576
+ * W1 (`write.before`) can run on each op — and rebuild its physical item from the
1577
+ * possibly-mutated input — before the batch composes / commits. A `conditionCheck`
1578
+ * records NO logical op (it is a read-only assertion, not a logical write).
1579
+ */
1580
+ interface LogicalWriteOp {
1581
+ kind: WriteKind;
1582
+ modelClass: Function;
1583
+ item?: Record<string, unknown>;
1584
+ key?: Record<string, unknown>;
1585
+ changes?: Record<string, unknown>;
1586
+ options?: PutOptions & UpdateOptions & DeleteOptions;
1587
+ }
1048
1588
  declare class TransactionContext {
1049
1589
  private readonly items;
1050
1590
  private readonly captureMeta;
1591
+ /**
1592
+ * The logical write ops, parallel to {@link items} but EXCLUDING `conditionCheck`
1593
+ * entries (those are read-only assertions, recorded only in `items`). Used by the
1594
+ * write-hook path (#139) to run W1 on each logical op and recompose the batch.
1595
+ * A `null` entry marks an `items` slot that has no logical op (a ConditionCheck),
1596
+ * so the eager and logical arrays can be re-aligned at commit.
1597
+ */
1598
+ private readonly logicalOps;
1051
1599
  get itemCount(): number;
1052
1600
  put(model: ModelStatic<DDBModel>, item: Record<string, unknown>, options?: PutOptions): void;
1053
1601
  update(model: ModelStatic<DDBModel>, entity: Record<string, unknown>, changes: Record<string, unknown>, options?: UpdateOptions): void;
@@ -1069,16 +1617,35 @@ declare class TransactionContext {
1069
1617
  }): void;
1070
1618
  /** @internal */
1071
1619
  getTransactItems(): TransactWriteItemInput[];
1620
+ /** @internal — the recorded logical write ops (issue #139 W1), `null` per ConditionCheck slot. */
1621
+ getLogicalOps(): readonly (LogicalWriteOp | null)[];
1072
1622
  /** @internal — capture descriptors for the write-capture seam (issue #72). */
1073
1623
  getCaptureMeta(): TransactCaptureMeta[];
1074
1624
  private assertWithinLimit;
1075
1625
  }
1076
- declare function executeTransaction(fn: (tx: TransactionContext) => void | Promise<void>): Promise<void>;
1626
+ declare function executeTransaction(fn: (tx: TransactionContext) => void | Promise<void>, options?: {
1627
+ context?: RequestContext;
1628
+ }): Promise<void>;
1077
1629
 
1078
1630
  interface BatchGetRequest {
1079
1631
  model: ModelStatic<DDBModel>;
1080
1632
  keys: Record<string, unknown>[];
1081
1633
  }
1634
+ /**
1635
+ * Per-call options for {@link DDBModel.batchGet} (issue #142).
1636
+ */
1637
+ interface BatchGetOptions {
1638
+ /**
1639
+ * Host-injected per-call read context (issue #50 / #138 / #142) — exposed to
1640
+ * every read hook (R1/R4/R5 at the `batchGet` request level, R2/R3/R5 on each
1641
+ * underlying `BatchGetItem` op) as `ctx.context`, and threaded UNCHANGED to
1642
+ * every physical op so a global hook (e.g. a tenant-scope R2 FilterExpression
1643
+ * or an observability probe) applies across the whole multi-key batch read. A
1644
+ * host-language value, NEVER serialized into the SSoT / `operations.json` (the
1645
+ * bridge #48 is unaffected). Absent ⇒ `{}`.
1646
+ */
1647
+ context?: RequestContext;
1648
+ }
1082
1649
  interface BatchPutRequest {
1083
1650
  type: 'put';
1084
1651
  model: ModelStatic<DDBModel>;
@@ -1097,7 +1664,23 @@ declare class BatchGetResult {
1097
1664
  constructor(groups: Map<Function, Record<string, unknown>[]>);
1098
1665
  get<T extends DDBModel>(model: ModelStatic<T>): Partial<T>[];
1099
1666
  }
1100
- declare function executeBatchGet(requests: BatchGetRequest[]): Promise<BatchGetResult>;
1667
+ /**
1668
+ * The multi-key, multi-model batch-read core (issue #142). Wraps the request
1669
+ * entry in the request-level read hooks R1 (`read.before`) / R4
1670
+ * (`read.afterFetch`) / R5 (`read.onError`) with request kind `'batchGet'`, and
1671
+ * each underlying physical `BatchGetItem` op (per table/chunk) in R2/R3/R5 via
1672
+ * {@link executeBatchGetForTable}. The read middleware runtime is built ONCE here
1673
+ * from `options.context` and threaded UNCHANGED to every op, so a global hook
1674
+ * (e.g. a tenant-scope R2 FilterExpression) applies across the whole batch.
1675
+ *
1676
+ * R1 sees the mutable batch `requests` on `ctx.params.requests`; the library
1677
+ * re-reads them after R1 runs, so a hook may add / drop / mutate requests before
1678
+ * any op is issued and may `throw` to cancel the whole batch. R4 may transform /
1679
+ * replace the assembled {@link BatchGetResult}; R5 may recover by returning one.
1680
+ * With no middleware registered the shared `NO_MIDDLEWARE` runtime keeps the hot
1681
+ * path unchanged (the zero-overhead fast path).
1682
+ */
1683
+ declare function executeBatchGet(requests: BatchGetRequest[], options?: BatchGetOptions): Promise<BatchGetResult>;
1101
1684
  declare function executeBatchWrite(requests: BatchWriteRequest[]): Promise<void>;
1102
1685
 
1103
1686
  /**
@@ -4218,6 +4801,13 @@ interface ReadRouteOptions {
4218
4801
  readonly after?: string;
4219
4802
  readonly order?: 'ASC' | 'DESC';
4220
4803
  readonly filter?: Record<string, unknown>;
4804
+ /**
4805
+ * Host-injected per-call read context (issue #50 / #138). Threaded into the
4806
+ * route's underlying read (`executeQuery` / `executeList`), so the read hooks
4807
+ * R1–R5 fire for the route with this `ctx.context`. Each route runs
4808
+ * independently, so its hooks see only its own context. Absent ⇒ `{}`.
4809
+ */
4810
+ readonly context?: RequestContext;
4221
4811
  }
4222
4812
  /** One read route: a `query` (point/unique) or a `list` (partition) descriptor. */
4223
4813
  interface ReadRouteDescriptor {
@@ -4266,6 +4856,21 @@ type WriteEnvelope = Record<string, InProcessWriteDescriptor>;
4266
4856
  /** Options for {@link executeWriteEnvelope}. */
4267
4857
  interface MutateOptions {
4268
4858
  readonly mode?: MutateMode;
4859
+ /**
4860
+ * Per-call throttle / transient-error retry override (issue #111), applied to
4861
+ * every write this envelope composes (the atomic `TransactWriteItems` in
4862
+ * `transaction` mode, or each op's send in `parallel` mode). A {@link RetryPolicy}
4863
+ * overrides the global policy; `false` disables retry. Omit ⇒ the configured /
4864
+ * built-in default policy.
4865
+ */
4866
+ readonly retry?: RetryOverride;
4867
+ /**
4868
+ * Host-injected per-call write context (issue #50 / #139) — exposed to every
4869
+ * write hook (W1–W5) as `ctx.context`. In `transaction` mode every composed op
4870
+ * shares it and the persist hooks fire once for the atomic batch; in `parallel`
4871
+ * mode each independent op sees it. NEVER serialized. Absent ⇒ `{}`.
4872
+ */
4873
+ readonly context?: RequestContext;
4269
4874
  }
4270
4875
  /** A per-alias result in `parallel` mode: success carries the read-back, else error. */
4271
4876
  type ParallelOpResult = {
@@ -4345,9 +4950,55 @@ type MutateParallelResult<E extends WriteEnvelope> = {
4345
4950
  declare abstract class DDBModel {
4346
4951
  /** @internal Type brand — prevents primitives from structurally matching DDBModel. */
4347
4952
  private readonly __brand;
4953
+ /**
4954
+ * Register the `DynamoDBClient` GraphDDB sends through.
4955
+ *
4956
+ * IMPORTANT (issue #111): GraphDDB now owns single-op throttle / transient-error
4957
+ * retry (see {@link setRetryPolicy}), so a client left at the AWS SDK default
4958
+ * `maxAttempts: 3` retries MULTIPLICATIVELY underneath the library. Construct the
4959
+ * client with `maxAttempts: 1` so the library is the single source of truth for
4960
+ * retry — e.g. `new DynamoDBClient({ maxAttempts: 1 })`. GraphDDB does NOT mutate
4961
+ * a client you pass in.
4962
+ */
4348
4963
  static setClient(client: DynamoDBClient): void;
4349
4964
  static setTableMapping(mapping: Record<string, string>): void;
4350
- static transaction(fn: (tx: TransactionContext) => void | Promise<void>): Promise<void>;
4965
+ /**
4966
+ * Configure the global single-op retry policy (issue #111) — exponential backoff
4967
+ * + jitter with a bounded attempt cap, applied to every GetItem / Query / Put /
4968
+ * Update / Delete / relation-fan-out send and to `TransactWriteItems`. Mirrors
4969
+ * {@link setClient} / {@link setTableMapping}. A sensible default is ALWAYS on; call
4970
+ * this only to tune it (or pass `null` to restore the default). A per-call
4971
+ * `retry?: RetryPolicy | false` on an operation's options overrides this globally
4972
+ * for that call (`false` disables retry).
4973
+ */
4974
+ static setRetryPolicy(policy: RetryPolicy | null): void;
4975
+ /**
4976
+ * Register a {@link Middleware} (issue #50; read path #138, write path #139).
4977
+ * One registered object may carry read and/or write hooks.
4978
+ *
4979
+ * - **Read** R1–R5 — request entry (`read.before`), each physical op before send
4980
+ * / after raw fetch (`read.op.before` / `read.op.afterFetch`, INCLUDING every
4981
+ * relation fan-out fetch), the final hydrated result (`read.afterFetch`), and on
4982
+ * error (`read.op.onError` / `read.onError`).
4983
+ * - **Write** W1–W5 — the logical write before effect derivation (`write.before`,
4984
+ * may mutate `ctx.input` / `ctx.kind` incl. a delete→update rewrite), after commit
4985
+ * (`write.after`), the physical persist on the fully composed batch
4986
+ * (`write.persist.before` / `write.persist.after`, fires ONCE per atomic
4987
+ * transaction), and on error (`write.persist.onError` / `write.onError`).
4988
+ *
4989
+ * Hooks may observe, mutate `ctx`, `throw` to cancel, or (on `onError`) recover by
4990
+ * returning a value; the library imposes no semantics. Ordering: `before*` runs
4991
+ * first-registered-first (FIFO); `after*` / `onError` run last-registered-first
4992
+ * (LIFO). Mirrors {@link setRetryPolicy} — a host-runtime global stored on
4993
+ * {@link ClientManager}; never serialized (the bridge #48 is unaffected). Per-call
4994
+ * host context flows in via the read's / write's `{ context }` option.
4995
+ */
4996
+ static use(mw: Middleware): void;
4997
+ /** Remove every registered middleware (read AND write; teardown / tests). See {@link use}. */
4998
+ static clearMiddleware(): void;
4999
+ static transaction(fn: (tx: TransactionContext) => void | Promise<void>, options?: {
5000
+ context?: RequestContext;
5001
+ }): Promise<void>;
4351
5002
  /**
4352
5003
  * In-process multi-route READ (issue #101 — the unified envelope). Each alias
4353
5004
  * route (`{ query | list: Model, key, select, options? }`) runs **independently
@@ -4375,7 +5026,19 @@ declare abstract class DDBModel {
4375
5026
  static mutate<const E extends WriteEnvelope>(envelope: E, options: {
4376
5027
  mode: 'parallel';
4377
5028
  }): Promise<MutateParallelResult<E>>;
4378
- static batchGet(requests: BatchGetRequest[]): Promise<BatchGetResult>;
5029
+ /**
5030
+ * Multi-key, multi-model batch read. Each {@link BatchGetRequest} contributes its
5031
+ * keys to a chunked `BatchGetItem` per physical table; results are re-grouped by
5032
+ * model on the returned {@link BatchGetResult}.
5033
+ *
5034
+ * Fires the read hooks (issue #142): request-level R1/R4/R5 with kind
5035
+ * `'batchGet'`, and op-level R2/R3/R5 for each underlying physical `BatchGetItem`
5036
+ * op. The optional `{ context }` is exposed to every hook as `ctx.context` and
5037
+ * threaded UNCHANGED to every op (absent ⇒ `{}`), so a global hook (e.g. a
5038
+ * tenant-scope R2 FilterExpression) applies across the whole batch. With no
5039
+ * middleware registered the read path is unchanged (zero-overhead fast path).
5040
+ */
5041
+ static batchGet(requests: BatchGetRequest[], options?: BatchGetOptions): Promise<BatchGetResult>;
4379
5042
  static batchWrite(requests: BatchWriteRequest[]): Promise<void>;
4380
5043
  static asModel<C extends new (...args: never[]) => DDBModel>(this: C): ModelStatic<InstanceType<C>, C>;
4381
5044
  }
@@ -4619,4 +5282,4 @@ interface CdcEmulatorOptions {
4619
5282
  }
4620
5283
  type ShardId = string;
4621
5284
 
4622
- export { type AnyOperationDefinition as $, type StrictSelectSpec as A, type BatchGetExecInput as B, type ChangeEvent as C, type DynamoDBOperation as D, type Executor as E, type FaultSpec as F, type EntityInput as G, type UniqueQueryKeyOf as H, type EntityRef as I, type ConditionInput as J, type QueryMethodSpec as K, type CommandModelContract as L, type ModelStatic as M, type CommandMethodSpec as N, type OperationDefinition as O, type PutInput as P, type QueryModelContract as Q, type RawCondition as R, type SelectableOf as S, type TransactWriteExecItem as T, type UpdateInput as U, type ContractSpec as V, type WriteExecOptions as W, type QuerySpec as X, type CommandSpec as Y, type ContextSpec as Z, type OperationsDocument as _, type ExecutorResult as a, type InProcessWriteDescriptor as a$, type BridgeBundle as a0, type ConditionSpec as a1, type BatchDeleteRequest as a2, type BatchGetRequest as a3, BatchGetResult as a4, type BatchPutRequest as a5, type BatchResult as a6, type BatchWriteRequest as a7, CONTRACT_RANGE_FANOUT_CONCURRENCY as a8, type CdcMode as a9, type ContractKeyInput as aA, type ContractKeyRef as aB, type ContractKeySpec as aC, type ContractKind as aD, type ContractMethodOp as aE, type ContractParamRef as aF, type ContractQueryParams as aG, type ContractResolution as aH, type DeleteOptions as aI, type DeriveEffect as aJ, type DerivedEdgeWrite as aK, type DerivedUpdate as aL, type DescriptorBinding as aM, ENTITY_WRITES_MARKER as aN, type EdgeEffect as aO, type EffectPath as aP, type EmitEffect as aQ, type EntityWritesDefinition as aR, type EntityWritesShape as aS, type ExecutableCommandContract as aT, type ExecutableQueryContract as aU, type FilterInput as aV, type FilterSpec as aW, type FragmentInput as aX, type GsiDefinitionMarker as aY, type GsiOptions as aZ, type IdempotencyEffect as a_, type ChangeBatch as aa, type ChangeEventName as ab, type ClockMode as ac, type Column as ad, type ColumnMap as ae, type CommandContractMethodSpec as af, type CommandInputShape as ag, type CommandMethod as ah, type CommandPlan as ai, type CommandResolutionTarget as aj, type CommandResultKind as ak, type CommandSelectShape as al, type CompiledFragment as am, type ComposeSpec as an, type CondSlot as ao, type ConditionCheckInput as ap, type Connection as aq, type ContractCallSignature as ar, type ContractCardinality as as, type ContractCommandParams as at, type ContractCommandResult as au, type ContractComposeNode as av, type ContractFromRef as aw, type ContractInputArity as ax, type ContractItem as ay, type ContractKeyFieldRef as az, type WriteResult as b, type UniqueEffect as b$, type InputArity as b0, type KeyDefinitionMarker as b1, type KeySegment as b2, type KeySlot as b3, type KeyStructure as b4, type KeyedResult as b5, LIFECYCLE_CONTRACT_MARKER as b6, type LifecycleContract as b7, type LifecycleEffects as b8, type LiteralParam as b9, type PutOptions as bA, type QueryContractMethodSpec as bB, type QueryEnvelopeResult as bC, type QueryKeyOf as bD, type QueryMethod as bE, type QueryResult as bF, type RangeConditionSpec as bG, type ReadEnvelope as bH, type ReadOperationType as bI, type ReadRouteDescriptor as bJ, type ReadRouteOptions as bK, type ReadRouteResult as bL, type RecordedCompose as bM, type RelationBuilder as bN, type RelationSelect as bO, type RelationSpec as bP, type RequiresEffect as bQ, type Resolution as bR, SPEC_VERSION as bS, type SegmentSpec as bT, type SelectBuilder as bU, type SelectOf as bV, type StartingPosition as bW, type StreamViewType as bX, type StringParam as bY, TransactionContext as bZ, type TransactionItemType as b_, type ManifestEntity as ba, type ManifestField as bb, type ManifestFieldType as bc, type ManifestGsi as bd, type ManifestKey as be, type ManifestRelation as bf, type ManifestTable as bg, type ModelRef as bh, type MutateMode as bi, type MutateOptions as bj, type MutateParallelResult as bk, type MutateTransactionResult as bl, type MutationBody as bm, type MutationDescriptorMap as bn, type MutationFragment as bo, type MutationInputProxy as bp, type MutationInputRef as bq, type MutationIntent as br, type NumberParam as bs, type OperationKind as bt, type OperationSpec as bu, type ParallelOpResult as bv, type ParamKind as bw, type ParamSpec as bx, type ParamStructure as by, type PlannedCommandMethod as bz, type DeleteInput as c, type Updatable as c0, type UpdateOptions as c1, type WhenSpec as c2, type WriteDescriptor as c3, type WriteEnvelope as c4, type WriteLifecyclePhase as c5, type WriteOperationType as c6, type WriteRecorder as c7, type WriteResultProjection as c8, attachModelClass as c9, isContractComposeNode as cA, isContractFromRef as cB, isContractKeyFieldRef as cC, isContractKeyRef as cD, isContractParamRef as cE, isEntityWritesDefinition as cF, isKeySegment as cG, isLifecycleContract as cH, isMutationFragment as cI, isMutationInputRef as cJ, isParam as cK, isPlannedCommandMethod as cL, isQueryModelContract as cM, k as cN, key as cO, lifecyclePhaseForIntent as cP, mintContractKeyFieldRef as cQ, mintContractParamRef as cR, mutation as cS, param as cT, publicCommandModel as cU, publicQueryModel as cV, query as cW, resolveLifecycle as cX, wholeKeysSentinel as cY, buildDeleteInput as ca, buildPutInput as cb, buildUpdateInput as cc, compileFragment as cd, compileMutationPlan as ce, compileSingleFragmentPlan as cf, cond as cg, contractOfMethodSpec as ch, definePlan as ci, entityWrites as cj, executeBatchGet as ck, executeBatchWrite as cl, executeCommandMethod as cm, executeDelete as cn, executeKeyedBatchGet as co, executePut as cp, executeQueryMethod as cq, executeRangeFanout as cr, executeTransaction as cs, executeUpdate as ct, from as cu, getEntityWrites as cv, gsi as cw, isColumn as cx, isCommandModelContract as cy, isCommandPlan as cz, type BatchWriteExecItem as d, DDBModel as e, type PrimaryKeyOf as f, type ExecutionPlanSpec as g, type SegmentedKey as h, type SelectBuilderSpec as i, type TransactionSpec as j, type Manifest as k, type ExecutionPlan as l, type ResolvedKey as m, type CdcEmulatorOptions as n, type ChangeHandler as o, type Unsubscribe as p, type ConcurrentRecomputeRef as q, type EventLog as r, type ReplayOptions as s, type ShardId as t, type TransactionItemSpec as u, type Param as v, type ParamDescriptor as w, type DefinitionMap as x, type WriteDefinitionOptions as y, type PartialQueryKeyOf as z };
5285
+ export { type EntityRef as $, type ConcurrentRecomputeRef as A, type BatchGetExecInput as B, type ChangeEvent as C, type DynamoDBOperation as D, type Executor as E, type FaultSpec as F, type EventLog as G, type ReplayOptions as H, type Item as I, type ShardId as J, type TransactionItemSpec as K, type Param as L, type ModelStatic as M, type ParamDescriptor as N, type DefinitionMap as O, type PutInput as P, type OperationDefinition as Q, type ReadExecOptions as R, type SelectableOf as S, type TransactWriteExecItem as T, type UpdateInput as U, type WriteDefinitionOptions as V, type WriteExecOptions as W, type PartialQueryKeyOf as X, type StrictSelectSpec as Y, type EntityInput as Z, type UniqueQueryKeyOf as _, type ExecutorResult as a, type DerivedUpdate as a$, type ConditionInput as a0, type QueryModelContract as a1, type QueryMethodSpec as a2, type CommandModelContract as a3, type CommandMethodSpec as a4, type ContractSpec as a5, type QuerySpec as a6, type CommandSpec as a7, type ContextSpec as a8, type OperationsDocument as a9, type ComposeSpec as aA, type CondSlot as aB, type ConditionCheckInput as aC, type Connection as aD, type ContractCallSignature as aE, type ContractCardinality as aF, type ContractCommandParams as aG, type ContractCommandResult as aH, type ContractComposeNode as aI, type ContractFromRef as aJ, type ContractInputArity as aK, type ContractItem as aL, type ContractKeyFieldRef as aM, type ContractKeyInput as aN, type ContractKeyRef as aO, type ContractKeySpec as aP, type ContractKind as aQ, type ContractMethodOp as aR, type ContractParamRef as aS, type ContractQueryParams as aT, type ContractResolution as aU, type CtxBase as aV, DEFAULT_MAX_ATTEMPTS as aW, DEFAULT_RETRY_POLICY as aX, type DeleteOptions as aY, type DeriveEffect as aZ, type DerivedEdgeWrite as a_, type AnyOperationDefinition as aa, type BridgeBundle as ab, type ConditionSpec as ac, type BatchDeleteRequest as ad, type BatchGetOptions as ae, type BatchGetRequest as af, BatchGetResult as ag, type BatchPutRequest as ah, type BatchResult as ai, type BatchWriteRequest as aj, CONTRACT_RANGE_FANOUT_CONCURRENCY as ak, type CdcMode as al, type Change as am, type ChangeBatch as an, type ChangeEventName as ao, type ClockMode as ap, type Column as aq, type ColumnMap as ar, type CommandContractMethodSpec as as, type CommandInputShape as at, type CommandMethod as au, type CommandPlan as av, type CommandResolutionTarget as aw, type CommandResultKind as ax, type CommandSelectShape as ay, type CompiledFragment as az, type WriteResult as b, type ReadOpKind as b$, type DescriptorBinding as b0, ENTITY_WRITES_MARKER as b1, type EdgeEffect as b2, type EffectPath as b3, type EmitEffect as b4, type EntityWritesDefinition as b5, type EntityWritesShape as b6, type ExecutableCommandContract as b7, type ExecutableQueryContract as b8, type FilterInput as b9, type MutateParallelResult as bA, type MutateTransactionResult as bB, type MutationBody as bC, type MutationDescriptorMap as bD, type MutationFragment as bE, type MutationInputProxy as bF, type MutationInputRef as bG, type MutationIntent as bH, type NumberParam as bI, type OperationKind as bJ, type OperationSpec as bK, type ParallelOpResult as bL, type ParamKind as bM, type ParamSpec as bN, type ParamStructure as bO, type PersistCtx as bP, type PersistOrigin as bQ, type PlannedCommandMethod as bR, type PutOptions as bS, type QueryContractMethodSpec as bT, type QueryEnvelopeResult as bU, type QueryKeyOf as bV, type QueryMethod as bW, type QueryResult as bX, type RangeConditionSpec as bY, type ReadEnvelope as bZ, type ReadOpCtx as b_, type FilterSpec as ba, type FragmentInput as bb, type GsiDefinitionMarker as bc, type GsiOptions as bd, type IdempotencyEffect as be, type InProcessWriteDescriptor as bf, type InputArity as bg, type KeyDefinitionMarker as bh, type KeySegment as bi, type KeySlot as bj, type KeyStructure as bk, type KeyedResult as bl, LIFECYCLE_CONTRACT_MARKER as bm, type LifecycleContract as bn, type LifecycleEffects as bo, type LiteralParam as bp, type ManifestEntity as bq, type ManifestField as br, type ManifestFieldType as bs, type ManifestGsi as bt, type ManifestKey as bu, type ManifestRelation as bv, type ManifestTable as bw, type ModelRef as bx, type MutateMode as by, type MutateOptions as bz, type DeleteInput as c, isContractFromRef as c$, type ReadOperationType as c0, type ReadRouteDescriptor as c1, type ReadRouteOptions as c2, type ReadRouteResult as c3, type RecordedCompose as c4, type RelationBuilder as c5, type RelationSelect as c6, type RelationSpec as c7, type RequiresEffect as c8, type Resolution as c9, buildDeleteInput as cA, buildPutInput as cB, buildUpdateInput as cC, compileFragment as cD, compileMutationPlan as cE, compileSingleFragmentPlan as cF, cond as cG, contractOfMethodSpec as cH, definePlan as cI, entityWrites as cJ, executeBatchGet as cK, executeBatchWrite as cL, executeCommandMethod as cM, executeDelete as cN, executeKeyedBatchGet as cO, executePut as cP, executeQueryMethod as cQ, executeRangeFanout as cR, executeTransaction as cS, executeUpdate as cT, from as cU, getEntityWrites as cV, gsi as cW, isColumn as cX, isCommandModelContract as cY, isCommandPlan as cZ, isContractComposeNode as c_, type RetryInfo as ca, type RetryOperationKind as cb, SPEC_VERSION as cc, type SegmentSpec as cd, type SelectBuilder as ce, type SelectOf as cf, type StartingPosition as cg, type StreamViewType as ch, type StringParam as ci, TransactionContext as cj, type TransactionItemType as ck, type UniqueEffect as cl, type Updatable as cm, type UpdateOptions as cn, type WhenSpec as co, type WriteCtx as cp, type WriteDescriptor as cq, type WriteEnvelope as cr, type WriteInput as cs, type WriteKind as ct, type WriteLifecyclePhase as cu, type WriteMiddleware as cv, type WriteOperationType as cw, type WriteRecorder as cx, type WriteResultProjection as cy, attachModelClass as cz, type BatchWriteExecItem as d, isContractKeyFieldRef as d0, isContractKeyRef as d1, isContractParamRef as d2, isEntityWritesDefinition as d3, isKeySegment as d4, isLifecycleContract as d5, isMutationFragment as d6, isMutationInputRef as d7, isParam as d8, isPlannedCommandMethod as d9, isQueryModelContract as da, isRetryableError as db, isRetryableTransactionCancellation as dc, k as dd, key as de, lifecyclePhaseForIntent as df, mintContractKeyFieldRef as dg, mintContractParamRef as dh, mutation as di, param as dj, publicCommandModel as dk, publicQueryModel as dl, query as dm, resolveLifecycle as dn, wholeKeysSentinel as dp, type BatchExecOptions as e, DDBModel as f, type PrimaryKeyOf as g, type RequestContext as h, type Middleware as i, type ReadRequestKind as j, type CtxModel as k, type ReadParams as l, type ReadRequestCtx as m, type RetryPolicy as n, type ExecutionPlanSpec as o, type SegmentedKey as p, type SelectBuilderSpec as q, type RawCondition as r, type TransactionSpec as s, type Manifest as t, type RetryOverride as u, type ExecutionPlan as v, type ResolvedKey as w, type CdcEmulatorOptions as x, type ChangeHandler as y, type Unsubscribe as z };