graphddb 0.2.4 → 0.3.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 +322 -305
- package/dist/{chunk-GCPEUPAA.js → chunk-6AIAHP3A.js} +1129 -156
- package/dist/{chunk-IQEOJVHI.js → chunk-QEOFIXTN.js} +1194 -60
- package/dist/{chunk-LNESVUTO.js → chunk-QQNP43JL.js} +1 -1
- package/dist/cli.js +2 -2
- package/dist/index.d.ts +275 -73
- package/dist/index.js +131 -7
- package/dist/testing/index.d.ts +1 -1
- package/dist/testing/index.js +2 -2
- package/dist/{types-BEeQ_nSd.d.ts → types-xJRn5qkv.d.ts} +1567 -237
- package/package.json +1 -1
|
@@ -778,213 +778,75 @@ type AtLeastOneKey<T> = {
|
|
|
778
778
|
*/
|
|
779
779
|
type PartialQueryKeyOf<C> = QueryKeyOf<C> extends infer U ? U extends Record<string, unknown> ? AtLeastOneKey<U> : never : never;
|
|
780
780
|
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
* a {@link SelectBuilder} produced by `Model.project(...)`. The builder is
|
|
784
|
-
* model-bound to `T`, so its `.filter(...)` is typed against the full entity.
|
|
785
|
-
*/
|
|
786
|
-
type SelectSpec<T> = SelectableOf<T> | SelectBuilder<T, any>;
|
|
787
|
-
/**
|
|
788
|
-
* Resolves the projection `S` from a top-level select spec (plain object or
|
|
789
|
-
* builder), so the result type is computed identically for both forms.
|
|
790
|
-
*/
|
|
791
|
-
type ProjectionOf<T, Sel> = Sel extends SelectableOf<T> ? Sel : SelectOf<Sel>;
|
|
792
|
-
/**
|
|
793
|
-
* Default model-class constraint used when a `ModelStatic` is referenced
|
|
794
|
-
* without its concrete constructor type (e.g. `ModelStatic<UserModel>`).
|
|
795
|
-
*
|
|
796
|
-
* In that case key/option types fall back to permissive shapes so existing
|
|
797
|
-
* usages keep compiling, while the precise inference is available when the
|
|
798
|
-
* constructor type is supplied (as it is from `DDBModel.asModel()`).
|
|
799
|
-
*/
|
|
800
|
-
type AnyModelClass = abstract new (...args: any[]) => DDBModel;
|
|
801
|
-
/**
|
|
802
|
-
* The accepted PK / GSI key type for `list()` / `explain()`.
|
|
803
|
-
*
|
|
804
|
-
* `list`/`explain` issue a DynamoDB *Query* against a partition, so a **partial**
|
|
805
|
-
* key (partition-key subset of a PK/GSI input) is accepted — the sort-key tail
|
|
806
|
-
* is optional. This matches the runtime key resolver. `query()` stays strict via
|
|
807
|
-
* {@link QueryKey}. Derived from the model class when available, otherwise
|
|
808
|
-
* permissive.
|
|
809
|
-
*/
|
|
810
|
-
type ListKey<C> = [C] extends [AnyModelClass] ? PartialQueryKeyOf<C> : Record<string, unknown>;
|
|
811
|
-
/**
|
|
812
|
-
* The accepted key type for `query()` — only keys guaranteed to return a
|
|
813
|
-
* single item (PK + unique GSIs). Derived from the model class when available.
|
|
814
|
-
*/
|
|
815
|
-
type QueryKey<C> = [C] extends [AnyModelClass] ? UniqueQueryKeyOf<C> : Record<string, unknown>;
|
|
816
|
-
/**
|
|
817
|
-
* The accepted key type for `delete()` (PK only). Derived from the model
|
|
818
|
-
* class when available.
|
|
819
|
-
*/
|
|
820
|
-
type DeleteKey<C> = [C] extends [AnyModelClass] ? PrimaryKeyOf<C> : Record<string, unknown>;
|
|
821
|
-
/**
|
|
822
|
-
* The accepted *explicit* key type for `update()` — the base-table primary key
|
|
823
|
-
* only. DynamoDB `UpdateItem` can target an item solely by its base-table PK
|
|
824
|
-
* (GSI-based updates are impossible), so this is `PrimaryKeyOf<C>`, not
|
|
825
|
-
* `UniqueQueryKeyOf<C>`. Derived from the model class when available.
|
|
826
|
-
*/
|
|
827
|
-
type UpdateExplicitKey<C> = [C] extends [AnyModelClass] ? PrimaryKeyOf<C> : Record<string, unknown>;
|
|
828
|
-
/**
|
|
829
|
-
* Options accepted by `list()` (third argument). The projection is supplied as
|
|
830
|
-
* the separate second `select` argument, symmetric with `query()`.
|
|
831
|
-
*
|
|
832
|
-
* - `filter` is the declarative server-side DynamoDB FilterExpression, typed
|
|
833
|
-
* against the full Entity `T` (may reference unprojected attributes). For
|
|
834
|
-
* arbitrary JS-only post-load filtering, narrow with `filter` then apply
|
|
835
|
-
* `result.items.filter(...)` (already fully typed).
|
|
836
|
-
*
|
|
837
|
-
* @typeParam T - The Entity type.
|
|
838
|
-
*/
|
|
839
|
-
type ListCallOptions<T> = {
|
|
840
|
-
limit?: number;
|
|
841
|
-
after?: string;
|
|
842
|
-
order?: 'ASC' | 'DESC';
|
|
843
|
-
filter?: FilterInput<T>;
|
|
844
|
-
/**
|
|
845
|
-
* When `true`, each returned item is updatable: it carries its resolved
|
|
846
|
-
* base-table key as a hidden, non-enumerable property and is branded
|
|
847
|
-
* `& Updatable`, so it can be passed straight back to `updateItem()`.
|
|
848
|
-
*/
|
|
849
|
-
updatable?: boolean;
|
|
850
|
-
};
|
|
851
|
-
/**
|
|
852
|
-
* Public, type-inferring API surface produced by {@link DDBModel.asModel}.
|
|
853
|
-
*
|
|
854
|
-
* @typeParam T - The Entity type of the model.
|
|
855
|
-
* @typeParam C - The model class constructor type. Supplied automatically by
|
|
856
|
-
* `asModel()`; when omitted, key/option types widen to permissive shapes so
|
|
857
|
-
* that `ModelStatic<T>` keeps compiling for callers that only name the entity.
|
|
858
|
-
*/
|
|
859
|
-
interface ModelStatic<T extends DDBModel, C = unknown> {
|
|
781
|
+
interface PutOptions<T = unknown> {
|
|
782
|
+
condition?: WriteCondition<T>;
|
|
860
783
|
/**
|
|
861
|
-
*
|
|
862
|
-
* the
|
|
863
|
-
*
|
|
864
|
-
* another model's column into this model's `cond` is a compile error.
|
|
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.
|
|
865
787
|
*/
|
|
866
|
-
|
|
788
|
+
retry?: RetryOverride;
|
|
867
789
|
/**
|
|
868
|
-
*
|
|
869
|
-
*
|
|
870
|
-
*
|
|
871
|
-
*
|
|
872
|
-
* @example
|
|
873
|
-
* ```ts
|
|
874
|
-
* Order.list({ userId: 'u' },
|
|
875
|
-
* Order.project({ orderId: true, amount: true })
|
|
876
|
-
* .filter({ status: 'confirmed', amount: { gt: 100 } }),
|
|
877
|
-
* );
|
|
878
|
-
* ```
|
|
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 ⇒ `{}`.
|
|
879
793
|
*/
|
|
880
|
-
|
|
794
|
+
context?: RequestContext;
|
|
795
|
+
}
|
|
796
|
+
interface UpdateOptions<T = unknown> {
|
|
797
|
+
condition?: WriteCondition<T>;
|
|
881
798
|
/**
|
|
882
|
-
*
|
|
883
|
-
*
|
|
884
|
-
*
|
|
885
|
-
* as {@link ModelStatic.project}, plus per-relation pagination
|
|
886
|
-
* (`limit` / `after` / `order`).
|
|
887
|
-
*
|
|
888
|
-
* @example
|
|
889
|
-
* ```ts
|
|
890
|
-
* User.query({ userId: 'u' }, {
|
|
891
|
-
* name: true,
|
|
892
|
-
* orders: Order.relation({ orderId: true, amount: true })
|
|
893
|
-
* .filter({ amount: { gt: 100 } }),
|
|
894
|
-
* });
|
|
895
|
-
* ```
|
|
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.
|
|
896
802
|
*/
|
|
897
|
-
|
|
803
|
+
retry?: RetryOverride;
|
|
898
804
|
/**
|
|
899
|
-
*
|
|
900
|
-
*
|
|
901
|
-
* plain object — `QueryResult<T, ProjectionOf<T, Sel>>`, typed against exactly
|
|
902
|
-
* the `select`ed fields — and its return type `R` becomes the read's return
|
|
903
|
-
* type (subsuming a `queryAs(Ctor, …)` style: `hydrate: (raw) => new Ctor(raw)`).
|
|
904
|
-
*
|
|
905
|
-
* Applied as the **final after-fetch step**, only to a present item: a missing
|
|
906
|
-
* item resolves to `null` and the factory is NOT invoked (hence `R | null`). A
|
|
907
|
-
* throw from the factory propagates verbatim as the read's rejection.
|
|
805
|
+
* How to re-derive a GSI key whose composing fields are changed but **not all**
|
|
806
|
+
* available from `{ ...key, ...changes }` (issue #115).
|
|
908
807
|
*
|
|
909
|
-
*
|
|
910
|
-
*
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
/**
|
|
919
|
-
* Fetch a single item by a unique key, requesting an **updatable** result.
|
|
920
|
-
* The returned item (when non-null) carries its resolved base-table key as a
|
|
921
|
-
* hidden, non-enumerable property and is branded `& Updatable`, so it can be
|
|
922
|
-
* passed straight back to {@link ModelStatic.updateItem} — even for a partial
|
|
923
|
-
* `select` or a GSI-based query. Do not spread / clone / JSON round-trip the
|
|
924
|
-
* result before updating: that drops both the hidden key and the brand.
|
|
925
|
-
*/
|
|
926
|
-
query<Sel extends SelectSpec<T>>(key: QueryKey<C>, select: Sel & StrictSelectSpec<T, Sel>, options: {
|
|
927
|
-
consistentRead?: boolean;
|
|
928
|
-
maxDepth?: number;
|
|
929
|
-
updatable: true;
|
|
930
|
-
}): Promise<(QueryResult<T, ProjectionOf<T, Sel>> & Updatable) | null>;
|
|
931
|
-
/**
|
|
932
|
-
* Fetch a single item by a unique key. The return type contains only the
|
|
933
|
-
* selected fields, derived from `select`.
|
|
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.
|
|
934
817
|
*/
|
|
935
|
-
|
|
936
|
-
consistentRead?: boolean;
|
|
937
|
-
maxDepth?: number;
|
|
938
|
-
updatable?: false;
|
|
939
|
-
}): Promise<QueryResult<T, ProjectionOf<T, Sel>> | null>;
|
|
818
|
+
rederive?: 'read-modify-write';
|
|
940
819
|
/**
|
|
941
|
-
*
|
|
942
|
-
*
|
|
943
|
-
* projection or a `project(...)` builder. `options.filter` is the declarative
|
|
944
|
-
* server-side FilterExpression typed against the full Entity. Returned items
|
|
945
|
-
* contain only the selected fields.
|
|
820
|
+
* Host-injected per-call write context (issue #50 / #139) — exposed to every
|
|
821
|
+
* write hook (W1–W5) as `ctx.context`. NEVER serialized. Absent ⇒ `{}`.
|
|
946
822
|
*/
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
cursor: string | null;
|
|
952
|
-
}>;
|
|
953
|
-
list<Sel extends SelectSpec<T>>(key: ListKey<C>, select: Sel & StrictSelectSpec<T, Sel>, options?: ListCallOptions<T>): Promise<{
|
|
954
|
-
items: QueryResult<T, ProjectionOf<T, Sel>>[];
|
|
955
|
-
cursor: string | null;
|
|
956
|
-
}>;
|
|
957
|
-
explain<Sel extends SelectSpec<T>>(key: ListKey<C>, options?: {
|
|
958
|
-
select?: Sel & StrictSelectSpec<T, Sel>;
|
|
959
|
-
limit?: number;
|
|
960
|
-
after?: string;
|
|
961
|
-
order?: 'ASC' | 'DESC';
|
|
962
|
-
consistentRead?: boolean;
|
|
963
|
-
filter?: FilterInput<T>;
|
|
964
|
-
}): ExecutionPlan;
|
|
965
|
-
putItem(item: EntityInput<T>, options?: WriteOptions<T>): Promise<void>;
|
|
823
|
+
context?: RequestContext;
|
|
824
|
+
}
|
|
825
|
+
interface DeleteOptions<T = unknown> {
|
|
826
|
+
condition?: WriteCondition<T>;
|
|
966
827
|
/**
|
|
967
|
-
*
|
|
968
|
-
*
|
|
969
|
-
*
|
|
970
|
-
*
|
|
971
|
-
* @example `await User.updateItem({ userId: 'alice' }, { status: 'disabled' });`
|
|
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.
|
|
972
831
|
*/
|
|
973
|
-
|
|
832
|
+
retry?: RetryOverride;
|
|
974
833
|
/**
|
|
975
|
-
*
|
|
976
|
-
*
|
|
977
|
-
* hidden base-table key carried by that entity is used directly, so this
|
|
978
|
-
* works even for a partial `select` or a GSI-based read.
|
|
979
|
-
*
|
|
980
|
-
* Pass the entity as-is. A spread / clone / JSON round-trip drops both the
|
|
981
|
-
* brand (rejected here at compile time) and the hidden key (rejected at
|
|
982
|
-
* runtime), preventing an accidental wrong-item update.
|
|
834
|
+
* Host-injected per-call write context (issue #50 / #139) — exposed to every
|
|
835
|
+
* write hook (W1–W5) as `ctx.context`. NEVER serialized. Absent ⇒ `{}`.
|
|
983
836
|
*/
|
|
984
|
-
|
|
985
|
-
deleteItem(key: DeleteKey<C>, options?: WriteOptions<T>): Promise<void>;
|
|
837
|
+
context?: RequestContext;
|
|
986
838
|
}
|
|
987
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
|
+
|
|
988
850
|
interface UpdateInput {
|
|
989
851
|
TableName: string;
|
|
990
852
|
Key: Record<string, unknown>;
|
|
@@ -1139,58 +1001,548 @@ interface Executor {
|
|
|
1139
1001
|
transactWrite(items: TransactWriteExecItem[], options?: BatchExecOptions): Promise<void>;
|
|
1140
1002
|
}
|
|
1141
1003
|
|
|
1142
|
-
|
|
1143
|
-
|
|
1004
|
+
/**
|
|
1005
|
+
* Write middleware / hook public surface (issue #50 write path, #139). The merged
|
|
1006
|
+
* design is `docs/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
|
+
* (see docs/middleware.md). 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 (see docs/middleware.md).
|
|
1020
|
+
*
|
|
1021
|
+
* @see docs/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 (see docs/middleware.md).
|
|
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;
|
|
1144
1068
|
/**
|
|
1145
|
-
*
|
|
1146
|
-
*
|
|
1147
|
-
*
|
|
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).
|
|
1148
1075
|
*/
|
|
1149
|
-
|
|
1076
|
+
readonly transaction?: {
|
|
1077
|
+
readonly id: symbol;
|
|
1078
|
+
};
|
|
1150
1079
|
}
|
|
1151
|
-
|
|
1152
|
-
|
|
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 (see docs/middleware.md).
|
|
1126
|
+
*
|
|
1127
|
+
* @see docs/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/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 docs/middleware.md.
|
|
1171
|
+
*
|
|
1172
|
+
* @see docs/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` ((docs/middleware.md) — 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 docs/middleware.md
|
|
1282
|
+
* "sync / async").
|
|
1283
|
+
*
|
|
1284
|
+
* @see docs/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 docs/middleware.md — 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
|
+
|
|
1327
|
+
/**
|
|
1328
|
+
* A top-level `select` may be either a plain projection (`SelectableOf<T>`) or
|
|
1329
|
+
* a {@link SelectBuilder} produced by `Model.project(...)`. The builder is
|
|
1330
|
+
* model-bound to `T`, so its `.filter(...)` is typed against the full entity.
|
|
1331
|
+
*/
|
|
1332
|
+
type SelectSpec<T> = SelectableOf<T> | SelectBuilder<T, any>;
|
|
1333
|
+
/**
|
|
1334
|
+
* Resolves the projection `S` from a top-level select spec (plain object or
|
|
1335
|
+
* builder), so the result type is computed identically for both forms.
|
|
1336
|
+
*/
|
|
1337
|
+
type ProjectionOf<T, Sel> = Sel extends SelectableOf<T> ? Sel : SelectOf<Sel>;
|
|
1338
|
+
/**
|
|
1339
|
+
* Default model-class constraint used when a `ModelStatic` is referenced
|
|
1340
|
+
* without its concrete constructor type (e.g. `ModelStatic<UserModel>`).
|
|
1341
|
+
*
|
|
1342
|
+
* In that case key/option types fall back to permissive shapes so existing
|
|
1343
|
+
* usages keep compiling, while the precise inference is available when the
|
|
1344
|
+
* constructor type is supplied (as it is from `DDBModel.asModel()`).
|
|
1345
|
+
*/
|
|
1346
|
+
type AnyModelClass = abstract new (...args: any[]) => DDBModel;
|
|
1347
|
+
/**
|
|
1348
|
+
* The accepted PK / GSI key type for `list()` / `explain()`.
|
|
1349
|
+
*
|
|
1350
|
+
* `list`/`explain` issue a DynamoDB *Query* against a partition, so a **partial**
|
|
1351
|
+
* key (partition-key subset of a PK/GSI input) is accepted — the sort-key tail
|
|
1352
|
+
* is optional. This matches the runtime key resolver. `query()` stays strict via
|
|
1353
|
+
* {@link QueryKey}. Derived from the model class when available, otherwise
|
|
1354
|
+
* permissive.
|
|
1355
|
+
*/
|
|
1356
|
+
type ListKey<C> = [C] extends [AnyModelClass] ? PartialQueryKeyOf<C> : Record<string, unknown>;
|
|
1357
|
+
/**
|
|
1358
|
+
* The accepted key type for `query()` — only keys guaranteed to return a
|
|
1359
|
+
* single item (PK + unique GSIs). Derived from the model class when available.
|
|
1360
|
+
*/
|
|
1361
|
+
type QueryKey<C> = [C] extends [AnyModelClass] ? UniqueQueryKeyOf<C> : Record<string, unknown>;
|
|
1362
|
+
/**
|
|
1363
|
+
* The accepted key type for `delete()` (PK only). Derived from the model
|
|
1364
|
+
* class when available.
|
|
1365
|
+
*/
|
|
1366
|
+
type DeleteKey<C> = [C] extends [AnyModelClass] ? PrimaryKeyOf<C> : Record<string, unknown>;
|
|
1367
|
+
/**
|
|
1368
|
+
* The accepted *explicit* key type for `update()` — the base-table primary key
|
|
1369
|
+
* only. DynamoDB `UpdateItem` can target an item solely by its base-table PK
|
|
1370
|
+
* (GSI-based updates are impossible), so this is `PrimaryKeyOf<C>`, not
|
|
1371
|
+
* `UniqueQueryKeyOf<C>`. Derived from the model class when available.
|
|
1372
|
+
*/
|
|
1373
|
+
type UpdateExplicitKey<C> = [C] extends [AnyModelClass] ? PrimaryKeyOf<C> : Record<string, unknown>;
|
|
1374
|
+
/**
|
|
1375
|
+
* Options accepted by `list()` (third argument). The projection is supplied as
|
|
1376
|
+
* the separate second `select` argument, symmetric with `query()`.
|
|
1377
|
+
*
|
|
1378
|
+
* - `filter` is the declarative server-side DynamoDB FilterExpression, typed
|
|
1379
|
+
* against the full Entity `T` (may reference unprojected attributes). For
|
|
1380
|
+
* arbitrary JS-only post-load filtering, narrow with `filter` then apply
|
|
1381
|
+
* `result.items.filter(...)` (already fully typed).
|
|
1382
|
+
*
|
|
1383
|
+
* @typeParam T - The Entity type.
|
|
1384
|
+
*/
|
|
1385
|
+
type ListCallOptions<T> = {
|
|
1386
|
+
limit?: number;
|
|
1387
|
+
after?: string;
|
|
1388
|
+
order?: 'ASC' | 'DESC';
|
|
1389
|
+
filter?: FilterInput<T>;
|
|
1390
|
+
/**
|
|
1391
|
+
* When `true`, each returned item is updatable: it carries its resolved
|
|
1392
|
+
* base-table key as a hidden, non-enumerable property and is branded
|
|
1393
|
+
* `& Updatable`, so it can be passed straight back to `updateItem()`.
|
|
1394
|
+
*/
|
|
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;
|
|
1403
|
+
};
|
|
1404
|
+
/**
|
|
1405
|
+
* Public, type-inferring API surface produced by {@link DDBModel.asModel}.
|
|
1406
|
+
*
|
|
1407
|
+
* @typeParam T - The Entity type of the model.
|
|
1408
|
+
* @typeParam C - The model class constructor type. Supplied automatically by
|
|
1409
|
+
* `asModel()`; when omitted, key/option types widen to permissive shapes so
|
|
1410
|
+
* that `ModelStatic<T>` keeps compiling for callers that only name the entity.
|
|
1411
|
+
*/
|
|
1412
|
+
interface ModelStatic<T extends DDBModel, C = unknown> {
|
|
1413
|
+
/**
|
|
1414
|
+
* Type-safe column references for this model, one per scalar field. Used by
|
|
1415
|
+
* the `cond` raw escape hatch (`cond\`${Model.col.age} > ${18}\``). Each
|
|
1416
|
+
* column carries its declared type and is branded to this model, so passing
|
|
1417
|
+
* another model's column into this model's `cond` is a compile error.
|
|
1418
|
+
*/
|
|
1419
|
+
readonly col: ColumnMap<T>;
|
|
1420
|
+
/**
|
|
1421
|
+
* Begin a model-bound, top-level select builder. The entity type `T` is fixed
|
|
1422
|
+
* by this model and the projection `S` is inferred from the argument.
|
|
1423
|
+
* `.filter(...)` is typed `FilterInput<T>` against the full entity.
|
|
1424
|
+
*
|
|
1425
|
+
* @example
|
|
1426
|
+
* ```ts
|
|
1427
|
+
* Order.list({ userId: 'u' },
|
|
1428
|
+
* Order.project({ orderId: true, amount: true })
|
|
1429
|
+
* .filter({ status: 'confirmed', amount: { gt: 100 } }),
|
|
1430
|
+
* );
|
|
1431
|
+
* ```
|
|
1432
|
+
*/
|
|
1433
|
+
project<const S extends SelectableOf<T>>(select: S): SelectBuilder<T, S>;
|
|
1434
|
+
/**
|
|
1435
|
+
* Begin a model-bound relation select builder for use as a relation field's
|
|
1436
|
+
* value inside a parent `select`. `T` is this (relation target) model's
|
|
1437
|
+
* entity type; `S` is inferred from the argument. Same model-bound inference
|
|
1438
|
+
* as {@link ModelStatic.project}, plus per-relation pagination
|
|
1439
|
+
* (`limit` / `after` / `order`).
|
|
1440
|
+
*
|
|
1441
|
+
* @example
|
|
1442
|
+
* ```ts
|
|
1443
|
+
* User.query({ userId: 'u' }, {
|
|
1444
|
+
* name: true,
|
|
1445
|
+
* orders: Order.relation({ orderId: true, amount: true })
|
|
1446
|
+
* .filter({ amount: { gt: 100 } }),
|
|
1447
|
+
* });
|
|
1448
|
+
* ```
|
|
1449
|
+
*/
|
|
1450
|
+
relation<const S extends SelectableOf<T>>(select: S): RelationBuilder<T, S>;
|
|
1451
|
+
/**
|
|
1452
|
+
* Fetch a single item by a unique key and **hydrate** the plain result onto a
|
|
1453
|
+
* host-side object (issue #53). The `hydrate` factory receives the fully-built
|
|
1454
|
+
* plain object — `QueryResult<T, ProjectionOf<T, Sel>>`, typed against exactly
|
|
1455
|
+
* the `select`ed fields — and its return type `R` becomes the read's return
|
|
1456
|
+
* type (subsuming a `queryAs(Ctor, …)` style: `hydrate: (raw) => new Ctor(raw)`).
|
|
1457
|
+
*
|
|
1458
|
+
* Applied as the **final after-fetch step**, only to a present item: a missing
|
|
1459
|
+
* item resolves to `null` and the factory is NOT invoked (hence `R | null`). A
|
|
1460
|
+
* throw from the factory propagates verbatim as the read's rejection.
|
|
1461
|
+
*
|
|
1462
|
+
* Host-runtime ONLY: the factory is a host-language closure, never serialized
|
|
1463
|
+
* into the SSoT / `operations.json`, so it does not impede the bridge (#48).
|
|
1464
|
+
*/
|
|
1465
|
+
query<Sel extends SelectSpec<T>, R>(key: QueryKey<C>, select: Sel & StrictSelectSpec<T, Sel>, options: {
|
|
1466
|
+
consistentRead?: boolean;
|
|
1467
|
+
maxDepth?: number;
|
|
1468
|
+
updatable?: boolean;
|
|
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;
|
|
1472
|
+
}): Promise<R | null>;
|
|
1473
|
+
/**
|
|
1474
|
+
* Fetch a single item by a unique key, requesting an **updatable** result.
|
|
1475
|
+
* The returned item (when non-null) carries its resolved base-table key as a
|
|
1476
|
+
* hidden, non-enumerable property and is branded `& Updatable`, so it can be
|
|
1477
|
+
* passed straight back to {@link ModelStatic.updateItem} — even for a partial
|
|
1478
|
+
* `select` or a GSI-based query. Do not spread / clone / JSON round-trip the
|
|
1479
|
+
* result before updating: that drops both the hidden key and the brand.
|
|
1480
|
+
*/
|
|
1481
|
+
query<Sel extends SelectSpec<T>>(key: QueryKey<C>, select: Sel & StrictSelectSpec<T, Sel>, options: {
|
|
1482
|
+
consistentRead?: boolean;
|
|
1483
|
+
maxDepth?: number;
|
|
1484
|
+
updatable: true;
|
|
1485
|
+
/** Host-injected per-call read context (issue #50 / #138); see {@link ListCallOptions.context}. */
|
|
1486
|
+
context?: RequestContext;
|
|
1487
|
+
}): Promise<(QueryResult<T, ProjectionOf<T, Sel>> & Updatable) | null>;
|
|
1488
|
+
/**
|
|
1489
|
+
* Fetch a single item by a unique key. The return type contains only the
|
|
1490
|
+
* selected fields, derived from `select`.
|
|
1491
|
+
*/
|
|
1492
|
+
query<Sel extends SelectSpec<T>>(key: QueryKey<C>, select: Sel & StrictSelectSpec<T, Sel>, options?: {
|
|
1493
|
+
consistentRead?: boolean;
|
|
1494
|
+
maxDepth?: number;
|
|
1495
|
+
updatable?: false;
|
|
1496
|
+
/** Host-injected per-call read context (issue #50 / #138); see {@link ListCallOptions.context}. */
|
|
1497
|
+
context?: RequestContext;
|
|
1498
|
+
}): Promise<QueryResult<T, ProjectionOf<T, Sel>> | null>;
|
|
1153
1499
|
/**
|
|
1154
|
-
*
|
|
1155
|
-
*
|
|
1156
|
-
*
|
|
1500
|
+
* List items for a partition, symmetric with {@link ModelStatic.query}:
|
|
1501
|
+
* `list(key, select, options?)`. `select` (the second argument) may be a plain
|
|
1502
|
+
* projection or a `project(...)` builder. `options.filter` is the declarative
|
|
1503
|
+
* server-side FilterExpression typed against the full Entity. Returned items
|
|
1504
|
+
* contain only the selected fields.
|
|
1157
1505
|
*/
|
|
1158
|
-
|
|
1506
|
+
list<Sel extends SelectSpec<T>>(key: ListKey<C>, select: Sel & StrictSelectSpec<T, Sel>, options: ListCallOptions<T> & {
|
|
1507
|
+
updatable: true;
|
|
1508
|
+
}): Promise<{
|
|
1509
|
+
items: (QueryResult<T, ProjectionOf<T, Sel>> & Updatable)[];
|
|
1510
|
+
cursor: string | null;
|
|
1511
|
+
}>;
|
|
1512
|
+
list<Sel extends SelectSpec<T>>(key: ListKey<C>, select: Sel & StrictSelectSpec<T, Sel>, options?: ListCallOptions<T>): Promise<{
|
|
1513
|
+
items: QueryResult<T, ProjectionOf<T, Sel>>[];
|
|
1514
|
+
cursor: string | null;
|
|
1515
|
+
}>;
|
|
1516
|
+
explain<Sel extends SelectSpec<T>>(key: ListKey<C>, options?: {
|
|
1517
|
+
select?: Sel & StrictSelectSpec<T, Sel>;
|
|
1518
|
+
limit?: number;
|
|
1519
|
+
after?: string;
|
|
1520
|
+
order?: 'ASC' | 'DESC';
|
|
1521
|
+
consistentRead?: boolean;
|
|
1522
|
+
filter?: FilterInput<T>;
|
|
1523
|
+
}): ExecutionPlan;
|
|
1524
|
+
putItem(item: EntityInput<T>, options?: WriteOptions<T>): Promise<void>;
|
|
1159
1525
|
/**
|
|
1160
|
-
*
|
|
1161
|
-
*
|
|
1526
|
+
* Update an item identified by its **explicit base-table primary key**
|
|
1527
|
+
* (`PrimaryKeyOf<C>`). This is the canonical form — it requires the key input
|
|
1528
|
+
* up front, so it can never silently target the wrong item.
|
|
1162
1529
|
*
|
|
1163
|
-
*
|
|
1164
|
-
* index silently rot. The error names the index, the changed field, and the
|
|
1165
|
-
* missing field(s). The happy path (all composing fields available) re-derives
|
|
1166
|
-
* in the SAME `UpdateExpression` and is unaffected by this option.
|
|
1167
|
-
* - `'read-modify-write'` — read the current item, merge `{ ...item, ...changes }`,
|
|
1168
|
-
* re-derive every affected GSI key from the merged image, and write back under
|
|
1169
|
-
* an optimistic condition (the item must still exist / be unchanged). This costs
|
|
1170
|
-
* one extra read and is NOT atomic with the original update, but always re-derives
|
|
1171
|
-
* correctly.
|
|
1530
|
+
* @example `await User.updateItem({ userId: 'alice' }, { status: 'disabled' });`
|
|
1172
1531
|
*/
|
|
1173
|
-
|
|
1174
|
-
}
|
|
1175
|
-
interface DeleteOptions<T = unknown> {
|
|
1176
|
-
condition?: WriteCondition<T>;
|
|
1532
|
+
updateItem(key: UpdateExplicitKey<C>, changes: Partial<T>, options?: WriteOptions<T>): Promise<void>;
|
|
1177
1533
|
/**
|
|
1178
|
-
*
|
|
1179
|
-
* {
|
|
1180
|
-
*
|
|
1534
|
+
* Update an item using an **updatable** entity obtained from
|
|
1535
|
+
* `query(..., { updatable: true })` / `list(..., { updatable: true })`. The
|
|
1536
|
+
* hidden base-table key carried by that entity is used directly, so this
|
|
1537
|
+
* works even for a partial `select` or a GSI-based read.
|
|
1538
|
+
*
|
|
1539
|
+
* Pass the entity as-is. A spread / clone / JSON round-trip drops both the
|
|
1540
|
+
* brand (rejected here at compile time) and the hidden key (rejected at
|
|
1541
|
+
* runtime), preventing an accidental wrong-item update.
|
|
1181
1542
|
*/
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
interface PutInput {
|
|
1186
|
-
TableName: string;
|
|
1187
|
-
Item: Record<string, unknown>;
|
|
1188
|
-
ConditionExpression?: string;
|
|
1189
|
-
ExpressionAttributeNames?: Record<string, string>;
|
|
1190
|
-
ExpressionAttributeValues?: Record<string, unknown>;
|
|
1543
|
+
updateItem(entity: Partial<T> & Updatable, changes: Partial<T>, options?: WriteOptions<T>): Promise<void>;
|
|
1544
|
+
deleteItem(key: DeleteKey<C>, options?: WriteOptions<T>): Promise<void>;
|
|
1191
1545
|
}
|
|
1192
|
-
declare function buildPutInput(modelClass: Function, item: Record<string, unknown>, options?: PutOptions): PutInput;
|
|
1193
|
-
declare function executePut(modelClass: Function, item: Record<string, unknown>, options?: PutOptions): Promise<void>;
|
|
1194
1546
|
|
|
1195
1547
|
type TransactWriteItemInput = {
|
|
1196
1548
|
Put: PutInput;
|
|
@@ -1218,9 +1570,32 @@ interface TransactCaptureMeta {
|
|
|
1218
1570
|
};
|
|
1219
1571
|
newItem?: Record<string, unknown>;
|
|
1220
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
|
+
}
|
|
1221
1588
|
declare class TransactionContext {
|
|
1222
1589
|
private readonly items;
|
|
1223
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;
|
|
1224
1599
|
get itemCount(): number;
|
|
1225
1600
|
put(model: ModelStatic<DDBModel>, item: Record<string, unknown>, options?: PutOptions): void;
|
|
1226
1601
|
update(model: ModelStatic<DDBModel>, entity: Record<string, unknown>, changes: Record<string, unknown>, options?: UpdateOptions): void;
|
|
@@ -1242,16 +1617,35 @@ declare class TransactionContext {
|
|
|
1242
1617
|
}): void;
|
|
1243
1618
|
/** @internal */
|
|
1244
1619
|
getTransactItems(): TransactWriteItemInput[];
|
|
1620
|
+
/** @internal — the recorded logical write ops (issue #139 W1), `null` per ConditionCheck slot. */
|
|
1621
|
+
getLogicalOps(): readonly (LogicalWriteOp | null)[];
|
|
1245
1622
|
/** @internal — capture descriptors for the write-capture seam (issue #72). */
|
|
1246
1623
|
getCaptureMeta(): TransactCaptureMeta[];
|
|
1247
1624
|
private assertWithinLimit;
|
|
1248
1625
|
}
|
|
1249
|
-
declare function executeTransaction(fn: (tx: TransactionContext) => void | Promise<void
|
|
1626
|
+
declare function executeTransaction(fn: (tx: TransactionContext) => void | Promise<void>, options?: {
|
|
1627
|
+
context?: RequestContext;
|
|
1628
|
+
}): Promise<void>;
|
|
1250
1629
|
|
|
1251
1630
|
interface BatchGetRequest {
|
|
1252
1631
|
model: ModelStatic<DDBModel>;
|
|
1253
1632
|
keys: Record<string, unknown>[];
|
|
1254
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
|
+
}
|
|
1255
1649
|
interface BatchPutRequest {
|
|
1256
1650
|
type: 'put';
|
|
1257
1651
|
model: ModelStatic<DDBModel>;
|
|
@@ -1270,7 +1664,23 @@ declare class BatchGetResult {
|
|
|
1270
1664
|
constructor(groups: Map<Function, Record<string, unknown>[]>);
|
|
1271
1665
|
get<T extends DDBModel>(model: ModelStatic<T>): Partial<T>[];
|
|
1272
1666
|
}
|
|
1273
|
-
|
|
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>;
|
|
1274
1684
|
declare function executeBatchWrite(requests: BatchWriteRequest[]): Promise<void>;
|
|
1275
1685
|
|
|
1276
1686
|
/**
|
|
@@ -1559,6 +1969,259 @@ interface DeriveEffect {
|
|
|
1559
1969
|
/** The amount to add (for an increment). */
|
|
1560
1970
|
readonly amount: number;
|
|
1561
1971
|
}
|
|
1972
|
+
/**
|
|
1973
|
+
* A model lifecycle event that triggers a maintenance effect, expressed as an
|
|
1974
|
+
* `Entity.event` string — the source entity's class name, a dot, then one of the
|
|
1975
|
+
* lifecycle events. The three events mirror {@link WriteLifecyclePhase}
|
|
1976
|
+
* (`create` / `update` / `remove`) in past-tense event form, so a maintenance
|
|
1977
|
+
* effect is keyed by *what happened* to the source row.
|
|
1978
|
+
*
|
|
1979
|
+
* The string is **branded** ({@link MaintainTrigger}) so a raw `string` is not
|
|
1980
|
+
* accepted where a validated trigger is required; construct one with
|
|
1981
|
+
* {@link maintainTrigger}, which rejects a malformed value.
|
|
1982
|
+
*
|
|
1983
|
+
* @example `'Post.created'`, `'User.updated'`, `'Comment.removed'`
|
|
1984
|
+
*/
|
|
1985
|
+
type MaintainEvent = 'created' | 'updated' | 'removed';
|
|
1986
|
+
/** The brand carried by a validated {@link MaintainTrigger}. */
|
|
1987
|
+
declare const MAINTAIN_TRIGGER_BRAND: unique symbol;
|
|
1988
|
+
/**
|
|
1989
|
+
* A validated `Entity.event` maintenance trigger string (e.g. `'Post.created'`).
|
|
1990
|
+
* Branded so it is told apart from a raw `string`; build one with
|
|
1991
|
+
* {@link maintainTrigger}. The shape is `${EntityName}.${MaintainEvent}` where
|
|
1992
|
+
* `EntityName` is a non-empty identifier-ish token.
|
|
1993
|
+
*/
|
|
1994
|
+
type MaintainTrigger = `${string}.${MaintainEvent}` & {
|
|
1995
|
+
readonly [MAINTAIN_TRIGGER_BRAND]: true;
|
|
1996
|
+
};
|
|
1997
|
+
/**
|
|
1998
|
+
* Construct a validated {@link MaintainTrigger} from an `Entity.event` string.
|
|
1999
|
+
* Rejects a value that is not a non-empty entity name followed by `.created` /
|
|
2000
|
+
* `.updated` / `.removed`, so an invalid trigger is refused at construction time
|
|
2001
|
+
* (the AC's "invalid trigger string is rejected"). The brand is a compile-time
|
|
2002
|
+
* marker only; the returned value is the input string verbatim.
|
|
2003
|
+
*
|
|
2004
|
+
* @throws if `value` is not a well-formed `Entity.event` trigger string.
|
|
2005
|
+
*/
|
|
2006
|
+
declare function maintainTrigger(value: string): MaintainTrigger;
|
|
2007
|
+
/** Runtime guard: is `value` a well-formed {@link MaintainTrigger} string? */
|
|
2008
|
+
declare function isMaintainTrigger(value: unknown): value is MaintainTrigger;
|
|
2009
|
+
/**
|
|
2010
|
+
* A projection transform op (issue #120, 論点3 = 関数形): the function-form
|
|
2011
|
+
* `w.transform(path, op, ...args)` is the **primary** authoring surface (there is
|
|
2012
|
+
* no string DSL). The initial op set is:
|
|
2013
|
+
*
|
|
2014
|
+
* - `identity` — copy the source path through unchanged (no args).
|
|
2015
|
+
* - `preview` — keep a length-bounded preview of the source value; `n` is the
|
|
2016
|
+
* bound (`w.transform('$.body', 'preview', 200)`).
|
|
2017
|
+
*
|
|
2018
|
+
* New ops extend this union (and {@link ProjectionTransform}'s `args` shape) as
|
|
2019
|
+
* the maintenance graph (#124) needs them.
|
|
2020
|
+
*/
|
|
2021
|
+
type ProjectionTransformOp = 'identity' | 'preview';
|
|
2022
|
+
/**
|
|
2023
|
+
* One entry in a maintenance projection map: a `w.transform(path, op, ...args)`
|
|
2024
|
+
* IR node binding a source {@link EffectPath} to a target attribute through a
|
|
2025
|
+
* {@link ProjectionTransformOp}. `args` carries the op's positional arguments
|
|
2026
|
+
* (e.g. `preview`'s length bound); `identity` carries none.
|
|
2027
|
+
*
|
|
2028
|
+
* The function form is the only authoring surface (no string DSL); the node is a
|
|
2029
|
+
* plain branded record so the maintenance graph (#124) and compile injection
|
|
2030
|
+
* (#125) can read it without retaining a closure.
|
|
2031
|
+
*/
|
|
2032
|
+
interface ProjectionTransform {
|
|
2033
|
+
readonly kind: 'transform';
|
|
2034
|
+
/** The path-rooted source value (`$.input.*` / `$.entity.*`). */
|
|
2035
|
+
readonly path: EffectPath;
|
|
2036
|
+
/** The transform op applied to the source value. */
|
|
2037
|
+
readonly op: ProjectionTransformOp;
|
|
2038
|
+
/** The op's positional arguments (e.g. `[200]` for `preview`); empty for `identity`. */
|
|
2039
|
+
readonly args: readonly unknown[];
|
|
2040
|
+
}
|
|
2041
|
+
/**
|
|
2042
|
+
* A maintenance **projection map**: target attribute name → its
|
|
2043
|
+
* {@link ProjectionTransform}. Generalizes the way {@link DeriveEffect} names a
|
|
2044
|
+
* single derived `attribute`, to a whole projected row / collection item.
|
|
2045
|
+
*/
|
|
2046
|
+
type ProjectionMap = Readonly<Record<string, ProjectionTransform>>;
|
|
2047
|
+
/**
|
|
2048
|
+
* Standalone authoring helper for a `preview` {@link ProjectionTransform}: keep a
|
|
2049
|
+
* length-bounded preview of the source `path` (the first `n` characters). This is
|
|
2050
|
+
* the recorder-free counterpart to `w.transform(path, 'preview', n)` — used where
|
|
2051
|
+
* there is no {@link WriteRecorder} `w`, the relation-options decorator arguments
|
|
2052
|
+
* (epic #118 §5.2 / issue #121, e.g. `projection: { textPreview: preview('body',
|
|
2053
|
+
* 120) }`). It produces the **same** IR node as `w.transform` (both route through
|
|
2054
|
+
* {@link makeProjectionTransform}), so the two surfaces never diverge.
|
|
2055
|
+
*
|
|
2056
|
+
* @param path The source value the preview is taken of (a projection source path).
|
|
2057
|
+
* @param n The preview length bound (a positive integer).
|
|
2058
|
+
* @throws if `n` is not a positive integer.
|
|
2059
|
+
*/
|
|
2060
|
+
declare function preview(path: EffectPath, n: number): ProjectionTransform;
|
|
2061
|
+
/**
|
|
2062
|
+
* Standalone authoring helper for an `identity` {@link ProjectionTransform}: copy
|
|
2063
|
+
* the source `path` through unchanged. The recorder-free counterpart to
|
|
2064
|
+
* `w.transform(path, 'identity')`. In relation-options `projection` maps a bare
|
|
2065
|
+
* `string` path is the idiomatic identity shorthand; this helper is provided for
|
|
2066
|
+
* symmetry / when an explicit IR node is wanted. Routes through
|
|
2067
|
+
* {@link makeProjectionTransform} so it is structurally identical to the recorder
|
|
2068
|
+
* form.
|
|
2069
|
+
*
|
|
2070
|
+
* @param path The source value copied through unchanged.
|
|
2071
|
+
*/
|
|
2072
|
+
declare function identity(path: EffectPath): ProjectionTransform;
|
|
2073
|
+
/**
|
|
2074
|
+
* The **path** a maintenance write travels (shared vocabulary, epic #118 A/B
|
|
2075
|
+
* reconciliation — RFC §5.1 / issue #121 are the authority). `mutation` lands the
|
|
2076
|
+
* maintenance write **synchronously**, in the same `TransactWriteItems` as the
|
|
2077
|
+
* source mutation; `stream` defers it to the **asynchronous** Streams path. This
|
|
2078
|
+
* is the path axis, NOT a write-reconciliation axis.
|
|
2079
|
+
*
|
|
2080
|
+
* NOTE: A(#120) previously defined `updateMode` as an *invented* write-reconcile
|
|
2081
|
+
* axis (`'overwrite' | 'ifNewer'`); that axis is **removed for Phase 1** (論点2=b
|
|
2082
|
+
* fixes "1 mutation × 1 target row = 1 effect", so no reconcile is needed) and the
|
|
2083
|
+
* name is reclaimed for the path vocabulary above. A reconcile axis may be
|
|
2084
|
+
* reintroduced later under a *different* name if a future phase needs it.
|
|
2085
|
+
*/
|
|
2086
|
+
type MaintainUpdateMode = 'mutation' | 'stream';
|
|
2087
|
+
/**
|
|
2088
|
+
* The read/write consistency a maintenance effect is realized under (shared
|
|
2089
|
+
* vocabulary, epic #118 A/B reconciliation — RFC §5.1 / issue #121 are the
|
|
2090
|
+
* authority). `transactional` requires the maintenance write to land in the same
|
|
2091
|
+
* transaction as the source write; `eventual` (the default the consumers assume
|
|
2092
|
+
* when omitted) lets it lag. A's earlier `'strong'` value is **retired** in favor
|
|
2093
|
+
* of `'transactional'`.
|
|
2094
|
+
*/
|
|
2095
|
+
type MaintainConsistency = 'transactional' | 'eventual';
|
|
2096
|
+
/**
|
|
2097
|
+
* A **snapshot** maintenance effect (issue #120): on a {@link MaintainTrigger}
|
|
2098
|
+
* the source's {@link project} is mirrored into a single target row resolved by
|
|
2099
|
+
* {@link targetFactory} + {@link keys}. Generalizes {@link DeriveEffect} from a
|
|
2100
|
+
* single scalar `ADD`/`SET` to a projected row. Consumed by the maintenance graph
|
|
2101
|
+
* (#124) and compile injection (#125); #120 defines the type only.
|
|
2102
|
+
*/
|
|
2103
|
+
interface SnapshotEffect {
|
|
2104
|
+
readonly kind: 'snapshot';
|
|
2105
|
+
/** The lifecycle event of the source entity that drives the snapshot. */
|
|
2106
|
+
readonly trigger: MaintainTrigger;
|
|
2107
|
+
/** Resolves the target entity whose row holds the snapshot. */
|
|
2108
|
+
readonly targetFactory: () => new (...args: unknown[]) => unknown;
|
|
2109
|
+
/** The key binding of the target row (path-rooted values). */
|
|
2110
|
+
readonly keys: Readonly<Record<string, EffectPath>>;
|
|
2111
|
+
/** The projection map: target attribute → `w.transform(...)` IR. */
|
|
2112
|
+
readonly project: ProjectionMap;
|
|
2113
|
+
/** The path the write travels — synchronous `mutation` tx or async `stream`. */
|
|
2114
|
+
readonly updateMode?: MaintainUpdateMode;
|
|
2115
|
+
/** The consistency the write lands under (default `eventual`). */
|
|
2116
|
+
readonly consistency?: MaintainConsistency;
|
|
2117
|
+
}
|
|
2118
|
+
/**
|
|
2119
|
+
* Options for the bounded collection a {@link CollectionEffect} maintains: the
|
|
2120
|
+
* target {@link field} that holds the collection, an optional {@link maxItems}
|
|
2121
|
+
* cap (oldest items are evicted past it), and an optional {@link orderBy} source
|
|
2122
|
+
* path the items are ordered by.
|
|
2123
|
+
*/
|
|
2124
|
+
interface CollectionOptions {
|
|
2125
|
+
/** The target attribute that holds the maintained collection. */
|
|
2126
|
+
readonly field: string;
|
|
2127
|
+
/** Cap on the collection size; items past it are evicted (unbounded if omitted). */
|
|
2128
|
+
readonly maxItems?: number;
|
|
2129
|
+
/** A path-rooted source value the collection is ordered by (e.g. `$.entity.createdAt`). */
|
|
2130
|
+
readonly orderBy?: EffectPath;
|
|
2131
|
+
}
|
|
2132
|
+
/**
|
|
2133
|
+
* A **collection** maintenance effect (issue #120): on a {@link MaintainTrigger}
|
|
2134
|
+
* the source's {@link project} is appended as an item into the bounded collection
|
|
2135
|
+
* named by {@link collection} on the target row resolved by {@link targetFactory}
|
|
2136
|
+
* + {@link keys}. Generalizes {@link DeriveEffect} to a bounded list projection.
|
|
2137
|
+
* Consumed by the maintenance graph (#124) and compile injection (#125); #120
|
|
2138
|
+
* defines the type only.
|
|
2139
|
+
*/
|
|
2140
|
+
interface CollectionEffect {
|
|
2141
|
+
readonly kind: 'collection';
|
|
2142
|
+
/** The lifecycle event of the source entity that drives the collection write. */
|
|
2143
|
+
readonly trigger: MaintainTrigger;
|
|
2144
|
+
/** Resolves the target entity whose row holds the collection. */
|
|
2145
|
+
readonly targetFactory: () => new (...args: unknown[]) => unknown;
|
|
2146
|
+
/** The key binding of the target row (path-rooted values). */
|
|
2147
|
+
readonly keys: Readonly<Record<string, EffectPath>>;
|
|
2148
|
+
/** The projection map for each collection item: attribute → `w.transform(...)` IR. */
|
|
2149
|
+
readonly project: ProjectionMap;
|
|
2150
|
+
/** The bounded-collection options (`field` / `maxItems` / `orderBy`). */
|
|
2151
|
+
readonly collection: CollectionOptions;
|
|
2152
|
+
/** The path the write travels — synchronous `mutation` tx or async `stream`. */
|
|
2153
|
+
readonly updateMode?: MaintainUpdateMode;
|
|
2154
|
+
/** The consistency the write lands under (default `eventual`). */
|
|
2155
|
+
readonly consistency?: MaintainConsistency;
|
|
2156
|
+
}
|
|
2157
|
+
/**
|
|
2158
|
+
* The aggregation a {@link CounterEffect} maintains (Epic #118 §5.2 counter /
|
|
2159
|
+
* latest preset; issue #141). The function-form IR `count()` / `max(field)` the
|
|
2160
|
+
* `@aggregate` value helpers build, lifted into the maintenance IR:
|
|
2161
|
+
*
|
|
2162
|
+
* - `count` — the cardinality of the matched source rows: a `created` trigger
|
|
2163
|
+
* adds `+1`, a `removed` trigger adds `-1` (the {@link CounterEffect.delta}).
|
|
2164
|
+
* - `max` — the running maximum of a named source `field`. A synchronous
|
|
2165
|
+
* transactional max cannot be expressed as a concurrency-safe single-item
|
|
2166
|
+
* `UpdateExpression` (a conditional `SET` whose guard fails would roll back the
|
|
2167
|
+
* *source* write that legitimately happened), so Phase 1 records the IR but the
|
|
2168
|
+
* compile injection (#141) rejects it for the synchronous `mutation` path —
|
|
2169
|
+
* `max` is the asynchronous stream path (#130, Phase 2). The op is kept here so
|
|
2170
|
+
* the IR is complete and the reject is precise.
|
|
2171
|
+
*/
|
|
2172
|
+
type CounterAggregate = {
|
|
2173
|
+
readonly op: 'count';
|
|
2174
|
+
} | {
|
|
2175
|
+
readonly op: 'max';
|
|
2176
|
+
readonly field: string;
|
|
2177
|
+
};
|
|
2178
|
+
/**
|
|
2179
|
+
* A **counter** maintenance effect (Epic #118 §5.2 counter preset; issue #141): on
|
|
2180
|
+
* a {@link MaintainTrigger} a scalar aggregate on a single target row resolved by
|
|
2181
|
+
* {@link targetFactory} + {@link keys} is kept in sync with the source entity's
|
|
2182
|
+
* lifecycle. Where {@link SnapshotEffect} mirrors a projection and
|
|
2183
|
+
* {@link CollectionEffect} appends an item, a counter applies an atomic scalar
|
|
2184
|
+
* `ADD` — the maintenance-pipeline generalization of the self-lifecycle
|
|
2185
|
+
* {@link DeriveEffect} (`w.increment`, #85), but driven by a CROSS-entity
|
|
2186
|
+
* `maintainedOn` trigger rather than the written entity's own lifecycle.
|
|
2187
|
+
*
|
|
2188
|
+
* For `value.op === 'count'` the {@link delta} is the signed amount the trigger
|
|
2189
|
+
* applies (`+1` on `created`, `-1` on `removed`); the maintenance graph (#124)
|
|
2190
|
+
* derives it from the trigger event. Two counter effects targeting the SAME row in
|
|
2191
|
+
* one mutation MERGE (the symmetric-ADD collapse #92), unlike snapshot/collection
|
|
2192
|
+
* which reject a same-row collision — an `ADD` is commutative.
|
|
2193
|
+
*
|
|
2194
|
+
* Consumed by the maintenance graph (#124, `em.aggregates` walk) and the compile
|
|
2195
|
+
* injection (#141, `DerivedMaintainWrite` of `kind: 'counter'`).
|
|
2196
|
+
*/
|
|
2197
|
+
interface CounterEffect {
|
|
2198
|
+
readonly kind: 'counter';
|
|
2199
|
+
/** The lifecycle event of the source entity that drives the counter. */
|
|
2200
|
+
readonly trigger: MaintainTrigger;
|
|
2201
|
+
/** Resolves the target entity whose row holds the counter scalar. */
|
|
2202
|
+
readonly targetFactory: () => new (...args: unknown[]) => unknown;
|
|
2203
|
+
/** The key binding of the target row (path-rooted values). */
|
|
2204
|
+
readonly keys: Readonly<Record<string, EffectPath>>;
|
|
2205
|
+
/** The target attribute the aggregate scalar is written to (e.g. `postCount`). */
|
|
2206
|
+
readonly attribute: string;
|
|
2207
|
+
/** The aggregation maintained (`count()` / `max(field)`). */
|
|
2208
|
+
readonly value: CounterAggregate;
|
|
2209
|
+
/**
|
|
2210
|
+
* For `value.op === 'count'`, the signed `ADD` delta this trigger applies
|
|
2211
|
+
* (`+1` created / `-1` removed). Absent for `max` (not realized synchronously).
|
|
2212
|
+
*/
|
|
2213
|
+
readonly delta?: number;
|
|
2214
|
+
/** The path the write travels — synchronous `mutation` tx or async `stream`. */
|
|
2215
|
+
readonly updateMode?: MaintainUpdateMode;
|
|
2216
|
+
/** The consistency the write lands under (default `eventual`). */
|
|
2217
|
+
readonly consistency?: MaintainConsistency;
|
|
2218
|
+
}
|
|
2219
|
+
/**
|
|
2220
|
+
* The union of maintenance effects a {@link SnapshotEffect}, {@link CollectionEffect},
|
|
2221
|
+
* or {@link CounterEffect} can be. The shared IR the maintenance graph (#124) and
|
|
2222
|
+
* compile injection (#125 snapshot/collection, #141 counter) consume.
|
|
2223
|
+
*/
|
|
2224
|
+
type MaintainEffect = SnapshotEffect | CollectionEffect | CounterEffect;
|
|
1562
2225
|
/**
|
|
1563
2226
|
* A domain event (proposal §2 `emits`): an outbox `Put` drained to Streams /
|
|
1564
2227
|
* `src/cdc/`. Derived by #87 — stored opaquely here.
|
|
@@ -1667,6 +2330,21 @@ interface WriteRecorder {
|
|
|
1667
2330
|
deleteEdge(target: () => new (...args: unknown[]) => unknown, relationProperty: string): EdgeEffect;
|
|
1668
2331
|
/** Declare a derived / cascading update (§2 `derive`; derived #85). */
|
|
1669
2332
|
increment(target: () => new (...args: unknown[]) => unknown, keys: Readonly<Record<string, EffectPath>>, attribute: string, amount: number): DeriveEffect;
|
|
2333
|
+
/**
|
|
2334
|
+
* Build a projection transform IR node (issue #120, 関数形): the primary
|
|
2335
|
+
* function-form authoring surface for a maintenance projection map entry. The
|
|
2336
|
+
* initial ops are `identity` (no args — copy the source path through) and
|
|
2337
|
+
* `preview` (one numeric arg — keep a length-bounded preview). There is no
|
|
2338
|
+
* string DSL. The returned {@link ProjectionTransform} is consumed by the
|
|
2339
|
+
* maintenance graph (#124) / compile injection (#125).
|
|
2340
|
+
*
|
|
2341
|
+
* NOTE (issue #120 scope): the escape-hatch recorders that *attach* a
|
|
2342
|
+
* {@link SnapshotEffect} / {@link CollectionEffect} to a lifecycle
|
|
2343
|
+
* (`w.snapshotInto` / `w.captureInto`) and the `LifecycleEffects.maintain`
|
|
2344
|
+
* wiring are **Phase 2** and are intentionally NOT part of this recorder.
|
|
2345
|
+
*/
|
|
2346
|
+
transform(path: EffectPath, op: 'identity'): ProjectionTransform;
|
|
2347
|
+
transform(path: EffectPath, op: 'preview', n: number): ProjectionTransform;
|
|
1670
2348
|
/** Declare a domain event (§2 `emits`; derived #87). */
|
|
1671
2349
|
event(name: string, payload: Readonly<Record<string, EffectPath>>): EmitEffect;
|
|
1672
2350
|
/** Declare an idempotency guard (§2 `idempotency`; derived #87). */
|
|
@@ -1990,6 +2668,163 @@ declare function mutation(name: string, body: MutationBody): CommandPlan;
|
|
|
1990
2668
|
*/
|
|
1991
2669
|
declare const definePlan: typeof mutation;
|
|
1992
2670
|
|
|
2671
|
+
/**
|
|
2672
|
+
* Maintenance graph builder (Epic #118 issue #124 — the RFC B-案 core).
|
|
2673
|
+
*
|
|
2674
|
+
* ## What this module is
|
|
2675
|
+
*
|
|
2676
|
+
* A relation may declare a **maintenance** side via its relation options
|
|
2677
|
+
* (issue #121, the B work): `write.maintainedOn` lists the cross-entity
|
|
2678
|
+
* lifecycle triggers (`"<Entity>.<event>"`, e.g. `"Post.created"`) that should
|
|
2679
|
+
* keep the relation's materialized shape in sync, and `projection` declares how
|
|
2680
|
+
* the source entity is projected into that shape. This module walks every
|
|
2681
|
+
* registered model (`MetadataRegistry.getAll()`), reads those relation-side
|
|
2682
|
+
* declarations, and builds an **index**:
|
|
2683
|
+
*
|
|
2684
|
+
* trigger source entity × lifecycle event (created/updated/removed)
|
|
2685
|
+
* → list of maintenance effects to apply
|
|
2686
|
+
*
|
|
2687
|
+
* That index is exactly the question the compile-injection step (#125) asks of
|
|
2688
|
+
* a mutation: "this mutation `created` a `Post` — what must be maintained?". This
|
|
2689
|
+
* module answers *which* effect fires for *which* trigger, and validates that
|
|
2690
|
+
* every declared effect is realizable (round-trips with the read side); it does
|
|
2691
|
+
* **not** inject anything into the compiler — see "Scope" below.
|
|
2692
|
+
*
|
|
2693
|
+
* ## Input is relation-side declarations only (論点1 = c)
|
|
2694
|
+
*
|
|
2695
|
+
* The only input is each relation's `options.write.maintainedOn` +
|
|
2696
|
+
* `options.projection` (the #121 relation options). The `entityWrites` escape
|
|
2697
|
+
* hatch (`w.snapshotInto` / `w.captureInto` attached to a lifecycle) is **Phase
|
|
2698
|
+
* 2** and is deliberately not read here.
|
|
2699
|
+
*
|
|
2700
|
+
* ## Reuses the A (#120) maintenance IR — no second definition
|
|
2701
|
+
*
|
|
2702
|
+
* The maintenance effect each index entry carries is the **A-defined IR** —
|
|
2703
|
+
* {@link SnapshotEffect} / {@link CollectionEffect} / {@link ProjectionTransform}
|
|
2704
|
+
* from `src/define/entity-writes.ts` (also re-exported from the root barrel). This
|
|
2705
|
+
* module *synthesizes* those nodes from the relation-side declaration (it is the
|
|
2706
|
+
* relation-options → IR lowering); it never re-defines the IR shapes.
|
|
2707
|
+
*
|
|
2708
|
+
* ## Round-trip validation (流用 of `assertRelationRoundTrips`)
|
|
2709
|
+
*
|
|
2710
|
+
* Mirroring `src/relation/edge-write.ts`'s build-time round-trip guard, the graph
|
|
2711
|
+
* is verified the moment it is built, and a mis-declaration is a **loud reject**:
|
|
2712
|
+
*
|
|
2713
|
+
* - **Snapshot/collection target key points at a real row.** The relation's
|
|
2714
|
+
* `keyBinding` target fields must resolve to a real access pattern on the
|
|
2715
|
+
* target entity (the row that holds the snapshot/collection), via the *same*
|
|
2716
|
+
* {@link resolveKey} the read planner uses. A binding that covers no partition
|
|
2717
|
+
* would write/read an unreachable row → reject.
|
|
2718
|
+
* - **Projection source attributes exist on the source payload (論点4 = payload
|
|
2719
|
+
* 同梱のみ).** Every projection source path (`$.entity.<field>` /
|
|
2720
|
+
* `$.input.<field>`) must name an attribute that the *source* entity (the
|
|
2721
|
+
* trigger origin) actually carries. There is no fetch / re-projection: an
|
|
2722
|
+
* attribute that is not in the source payload is rejected, not fetched.
|
|
2723
|
+
* - **Unresolved trigger.** A trigger whose entity segment names no registered
|
|
2724
|
+
* model, or whose event is not `created`/`updated`/`removed`, is rejected.
|
|
2725
|
+
* - **Cycle.** A maintenance dependency cycle (entity A's event maintains a
|
|
2726
|
+
* shape on B while B's event maintains a shape on A, transitively) is detected
|
|
2727
|
+
* and rejected — an unbounded maintenance cascade.
|
|
2728
|
+
*
|
|
2729
|
+
* ## Scope (#124 only — NOT #125)
|
|
2730
|
+
*
|
|
2731
|
+
* This module builds and validates the index, and exposes enough structure for
|
|
2732
|
+
* the compile-injection step (#125/#126) to consume it — including the material
|
|
2733
|
+
* to detect that *two* maintainers target the **same** target row (論点2 = b,
|
|
2734
|
+
* "1 mutation × 1 target row = 1 effect"). It does **not** resolve maintainers
|
|
2735
|
+
* into the mutation compiler, derive `DerivedMaintainWrite`s, or emit any
|
|
2736
|
+
* transaction items; `resolveMaintainers` / `deriveMaintainItems` are #125's.
|
|
2737
|
+
*/
|
|
2738
|
+
|
|
2739
|
+
type ModelClass = new (...args: unknown[]) => unknown;
|
|
2740
|
+
/**
|
|
2741
|
+
* One maintenance effect, keyed in the graph by its {@link trigger}. Synthesized
|
|
2742
|
+
* from a single relation's maintenance declaration (its `write.maintainedOn` +
|
|
2743
|
+
* `projection`).
|
|
2744
|
+
*
|
|
2745
|
+
* The role mapping follows RFC §5.1 (`relation({ from, to, maintainedOn })`):
|
|
2746
|
+
*
|
|
2747
|
+
* - **source** = the relation's `to` (its `targetFactory()`): the entity whose
|
|
2748
|
+
* lifecycle event fires the maintenance and whose payload is projected. The
|
|
2749
|
+
* trigger's `Entity` segment names this source.
|
|
2750
|
+
* - **owner** = the relation's `from` (the model declaring the relation): the row
|
|
2751
|
+
* that *holds* the maintained snapshot / collection — the **destination** of the
|
|
2752
|
+
* maintenance write.
|
|
2753
|
+
*
|
|
2754
|
+
* So a `Post.created` trigger on `ThreadSummary.latestPosts → Post` maintains the
|
|
2755
|
+
* `ThreadSummary` (owner) row from the `Post` (source) payload. It carries the
|
|
2756
|
+
* A-defined {@link MaintainEffect} IR ({@link SnapshotEffect} /
|
|
2757
|
+
* {@link CollectionEffect}) the maintenance lowers to.
|
|
2758
|
+
*/
|
|
2759
|
+
interface MaintainItem {
|
|
2760
|
+
/** The validated `Entity.event` trigger that fires this effect. */
|
|
2761
|
+
readonly trigger: MaintainTrigger;
|
|
2762
|
+
/**
|
|
2763
|
+
* The source entity name (trigger origin — the row whose lifecycle fires this
|
|
2764
|
+
* and whose payload is projected). Equals the relation's target (`to`).
|
|
2765
|
+
*/
|
|
2766
|
+
readonly sourceEntity: string;
|
|
2767
|
+
/** The source model class (the relation's `targetFactory()`). */
|
|
2768
|
+
readonly sourceClass: ModelClass;
|
|
2769
|
+
/**
|
|
2770
|
+
* The entity that *declares* the maintaining relation (the relation owner /
|
|
2771
|
+
* `from`) — the row that holds the maintained snapshot / collection (the
|
|
2772
|
+
* maintenance write **destination**).
|
|
2773
|
+
*/
|
|
2774
|
+
readonly ownerEntity: string;
|
|
2775
|
+
/** The owner model class (the maintenance destination). */
|
|
2776
|
+
readonly ownerClass: ModelClass;
|
|
2777
|
+
/** The relation property on the owner that declares this maintenance. */
|
|
2778
|
+
readonly relationProperty: string;
|
|
2779
|
+
/**
|
|
2780
|
+
* A stable key for the **destination row** a single trigger writes (the owner
|
|
2781
|
+
* row), derived from the relation's bound owner-key fields. Two
|
|
2782
|
+
* {@link MaintainItem}s firing on the same trigger with the same
|
|
2783
|
+
* {@link destinationRowKey} write the same row — the material #125/#126 uses to
|
|
2784
|
+
* enforce 論点2 = b ("1 mutation × 1 target row = 1 effect"; multiple is a
|
|
2785
|
+
* reject). Built deterministically (sorted), so it is a directly comparable
|
|
2786
|
+
* string.
|
|
2787
|
+
*/
|
|
2788
|
+
readonly destinationRowKey: string;
|
|
2789
|
+
/** The A-defined maintenance IR this relation lowers to. */
|
|
2790
|
+
readonly effect: MaintainEffect;
|
|
2791
|
+
}
|
|
2792
|
+
/**
|
|
2793
|
+
* The maintenance graph: the trigger → effects index plus the validation surface.
|
|
2794
|
+
*
|
|
2795
|
+
* `byTrigger` is the core index: a `MaintainTrigger` (`"Post.created"`) maps to
|
|
2796
|
+
* every {@link MaintainItem} that fires on it, in a stable order (owner entity,
|
|
2797
|
+
* then relation property). Empty (no entry) for a trigger nothing maintains on.
|
|
2798
|
+
*/
|
|
2799
|
+
interface MaintenanceGraph {
|
|
2800
|
+
/** Every maintenance item, flattened, in deterministic order. */
|
|
2801
|
+
readonly items: readonly MaintainItem[];
|
|
2802
|
+
/** The core index: trigger → the effects it fires. */
|
|
2803
|
+
readonly byTrigger: ReadonlyMap<MaintainTrigger, readonly MaintainItem[]>;
|
|
2804
|
+
/** Look up the effects fired by a trigger (empty array if none). */
|
|
2805
|
+
effectsFor(trigger: MaintainTrigger): readonly MaintainItem[];
|
|
2806
|
+
/**
|
|
2807
|
+
* Items that, under the SAME trigger, write the SAME target row — grouped by
|
|
2808
|
+
* `"<trigger><targetRowKey>"`. Only groups with **more than one** item are
|
|
2809
|
+
* present, so an empty map means no trigger has a multi-maintainer collision.
|
|
2810
|
+
* This is the detection material for 論点2 = b; #124 surfaces it, #125/#126
|
|
2811
|
+
* decide the reject. (Build-time we do NOT reject here — a model may legitimately
|
|
2812
|
+
* declare overlapping shapes that a later phase reconciles; #124's contract is
|
|
2813
|
+
* to make the collision *detectable*.)
|
|
2814
|
+
*/
|
|
2815
|
+
readonly multiMaintainerTargets: ReadonlyMap<string, readonly MaintainItem[]>;
|
|
2816
|
+
}
|
|
2817
|
+
/**
|
|
2818
|
+
* Build the {@link MaintenanceGraph} from every registered model's relation-side
|
|
2819
|
+
* maintenance declarations (`MetadataRegistry.getAll()`), validating each as it is
|
|
2820
|
+
* collected (round-trip, unresolved trigger) and the whole graph (cycle) before
|
|
2821
|
+
* returning. Throws a loud, actionable error on any invalid declaration.
|
|
2822
|
+
*
|
|
2823
|
+
* @param registry The models to scan. Defaults to `MetadataRegistry.getAll()`;
|
|
2824
|
+
* an explicit map is accepted for tests / scoped builds.
|
|
2825
|
+
*/
|
|
2826
|
+
declare function buildMaintenanceGraph(registry?: ReadonlyMap<Function, EntityMetadata>): MaintenanceGraph;
|
|
2827
|
+
|
|
1993
2828
|
/**
|
|
1994
2829
|
* Serializable static operation specs and manifest (issue #42, Python-bridge
|
|
1995
2830
|
* Phase 0b).
|
|
@@ -2354,6 +3189,99 @@ interface TransactionItemSpec {
|
|
|
2354
3189
|
* entity / edge / counter / ConditionCheck item), so those paths are unchanged.
|
|
2355
3190
|
*/
|
|
2356
3191
|
readonly literalKey?: boolean;
|
|
3192
|
+
/**
|
|
3193
|
+
* A **relation-side maintenance write** (Epic #118, issue #129 — the JSON-SSoT /
|
|
3194
|
+
* Python mirror of the TS in-process `renderMaintainWriteItem`). Present ONLY on an
|
|
3195
|
+
* `Update` item that materializes a maintained access path: a projected
|
|
3196
|
+
* {@link DerivedMaintainWrite} of a just-written source row into a SEPARATE owner
|
|
3197
|
+
* (destination) row, in the SAME atomic transaction as the source write.
|
|
3198
|
+
*
|
|
3199
|
+
* The item's {@link keyCondition} carries the owner row's key templates (bound from
|
|
3200
|
+
* the source payload — `{sourceInputField}`), so the same key derivation / collapse
|
|
3201
|
+
* signature the other `Update` items use applies unchanged. This `maintain` payload
|
|
3202
|
+
* carries the projection transform IR (`identity` / `preview`) the runtimes apply
|
|
3203
|
+
* IDENTICALLY when rendering the `SET … = :v` (snapshot) / `list_append(…)`
|
|
3204
|
+
* (collection) `UpdateExpression`, so the maintained row is byte-consistent across
|
|
3205
|
+
* TS and Python. The `changes` / `add` fields are NOT used when this is present.
|
|
3206
|
+
*/
|
|
3207
|
+
readonly maintain?: MaintainItemSpec;
|
|
3208
|
+
}
|
|
3209
|
+
/**
|
|
3210
|
+
* The transform op applied to one projected maintenance attribute (Epic #118).
|
|
3211
|
+
* Mirrors `ProjectionTransformOp` from `src/define/entity-writes.ts`: `identity`
|
|
3212
|
+
* copies the source value through unchanged; `preview` keeps the first `n`
|
|
3213
|
+
* characters of (the string form of) the source value. The runtimes apply this
|
|
3214
|
+
* IDENTICALLY so the maintained row is byte-consistent.
|
|
3215
|
+
*/
|
|
3216
|
+
type MaintainProjectionOp = 'identity' | 'preview';
|
|
3217
|
+
/**
|
|
3218
|
+
* One projected maintenance attribute: the source value is read from the mutation
|
|
3219
|
+
* input field {@link inputField} (payload 同梱 — the just-written source row image),
|
|
3220
|
+
* then run through {@link op} with {@link args} (e.g. `preview`'s length bound).
|
|
3221
|
+
*/
|
|
3222
|
+
interface MaintainProjectionEntry {
|
|
3223
|
+
/** The transform op applied to the source value (`identity` / `preview`). */
|
|
3224
|
+
readonly op: MaintainProjectionOp;
|
|
3225
|
+
/** The op's positional arguments (`[n]` for `preview`; empty for `identity`). */
|
|
3226
|
+
readonly args: readonly unknown[];
|
|
3227
|
+
/** The mutation-input field the source value is read from (`{inputField}`). */
|
|
3228
|
+
readonly inputField: string;
|
|
3229
|
+
}
|
|
3230
|
+
/**
|
|
3231
|
+
* The bounded-collection options a `collection` maintenance write carries. Phase 1
|
|
3232
|
+
* is **append-only**: `maxItems` / `orderBy` are recorded for the future async trim
|
|
3233
|
+
* (#130) but NOT applied synchronously — a single `UpdateExpression` cannot
|
|
3234
|
+
* read-modify-write a bounded/ordered list.
|
|
3235
|
+
*/
|
|
3236
|
+
interface MaintainCollectionSpec {
|
|
3237
|
+
/** The target attribute that holds the maintained collection. */
|
|
3238
|
+
readonly field: string;
|
|
3239
|
+
/** Cap on the collection size (recorded for #130; not trimmed in Phase 1). */
|
|
3240
|
+
readonly maxItems?: number;
|
|
3241
|
+
/** The mutation-input field the items are ordered by (recorded for #130). */
|
|
3242
|
+
readonly orderBy?: string;
|
|
3243
|
+
}
|
|
3244
|
+
/**
|
|
3245
|
+
* The maintenance payload an `Update` {@link TransactionItemSpec} carries when it
|
|
3246
|
+
* materializes a maintained access path (Epic #118 / #129). A `snapshot` mirrors
|
|
3247
|
+
* the projection onto the owner row via `SET`; a `collection` appends the projection
|
|
3248
|
+
* as an item into the bounded list named by {@link collection} via `list_append`.
|
|
3249
|
+
*/
|
|
3250
|
+
interface MaintainItemSpec {
|
|
3251
|
+
/**
|
|
3252
|
+
* Whether the write mirrors a single row (`snapshot`), appends a collection item
|
|
3253
|
+
* (`collection`), or applies a scalar `ADD` counter (`counter`, Epic #118 / #141).
|
|
3254
|
+
*/
|
|
3255
|
+
readonly kind: 'snapshot' | 'collection' | 'counter';
|
|
3256
|
+
/** The relation property / `@aggregate` field that declared this maintenance (documentary). */
|
|
3257
|
+
readonly relationProperty: string;
|
|
3258
|
+
/** The trigger (`<sourceLogicalName>.<event>`) that fired this maintenance. */
|
|
3259
|
+
readonly trigger: string;
|
|
3260
|
+
/** target attribute → its projection transform (source field + op + args). Empty for `counter`. */
|
|
3261
|
+
readonly projection: Readonly<Record<string, MaintainProjectionEntry>>;
|
|
3262
|
+
/** The bounded-collection options; present only for `kind: 'collection'`. */
|
|
3263
|
+
readonly collection?: MaintainCollectionSpec;
|
|
3264
|
+
/**
|
|
3265
|
+
* The scalar counter `ADD`; present only for `kind: 'counter'` (#141). The runtimes
|
|
3266
|
+
* render `ADD #attr :delta` — an atomic, concurrency-safe increment that MERGES with
|
|
3267
|
+
* a same-row counter ADD (#92), the maintenance-pipeline analogue of the
|
|
3268
|
+
* self-lifecycle derived counter's `add` slot. `delta` is a numeric template string
|
|
3269
|
+
* (the compile-time constant `+1` created / `-1` removed).
|
|
3270
|
+
*/
|
|
3271
|
+
readonly counter?: MaintainCounterSpec;
|
|
3272
|
+
}
|
|
3273
|
+
/**
|
|
3274
|
+
* The scalar counter a `counter` maintenance write applies (Epic #118 / #141): an
|
|
3275
|
+
* atomic `ADD #attribute :delta`. `delta` is a literal numeric template string (the
|
|
3276
|
+
* compile-time constant the trigger fixed — `"1"` created / `"-1"` removed); the
|
|
3277
|
+
* runtimes serialize it as a number. Only `count()` is realized synchronously
|
|
3278
|
+
* (`max(field)` is rejected for the synchronous path — #130 / Phase 2).
|
|
3279
|
+
*/
|
|
3280
|
+
interface MaintainCounterSpec {
|
|
3281
|
+
/** The target attribute the counter increments (e.g. `postCount`). */
|
|
3282
|
+
readonly attribute: string;
|
|
3283
|
+
/** The signed `ADD` delta as a numeric template string (`"1"` / `"-1"`). */
|
|
3284
|
+
readonly delta: string;
|
|
2357
3285
|
}
|
|
2358
3286
|
/** A declarative transaction definition's full execution spec. */
|
|
2359
3287
|
interface TransactionSpec {
|
|
@@ -2783,6 +3711,75 @@ interface DerivedUpdate {
|
|
|
2783
3711
|
/** The signed delta to add (e.g. `+1` create, `-1` remove). */
|
|
2784
3712
|
readonly amount: number;
|
|
2785
3713
|
}
|
|
3714
|
+
/**
|
|
3715
|
+
* One **derived maintenance write** — the projection of a just-written source row
|
|
3716
|
+
* into a *separate* target (owner) row, derived from one maintenance-graph
|
|
3717
|
+
* {@link MaintainItem} (issue #125; the D/#124 → compile lowering). It generalizes
|
|
3718
|
+
* {@link DerivedUpdate}: where a `derive` is one scalar `ADD`, a maintenance write
|
|
3719
|
+
* mirrors a whole projection — a single-row {@link SnapshotEffect} (`kind:
|
|
3720
|
+
* 'snapshot'`) or an item appended into a bounded collection ({@link
|
|
3721
|
+
* CollectionEffect}, `kind: 'collection'`).
|
|
3722
|
+
*
|
|
3723
|
+
* The target row's key is bound from the source payload (the relation's owner-key
|
|
3724
|
+
* fields, filled from `$.entity.<sourceField>` → the fragment's `<sourceField>`
|
|
3725
|
+
* input param), and each projected attribute is a {@link ProjectionTransform} IR
|
|
3726
|
+
* node (A/#120 関数形 — `identity` / `preview`) resolved to the input param it reads.
|
|
3727
|
+
* #125 produces this structure; the runtime realization (the actual `Put` / `Update`
|
|
3728
|
+
* item and its placement in the atomic `TransactWriteItems`) is #127.
|
|
3729
|
+
*/
|
|
3730
|
+
interface DerivedMaintainWrite {
|
|
3731
|
+
/** The maintained (destination / owner) entity whose row holds the snapshot / collection. */
|
|
3732
|
+
readonly entity: EntityRef;
|
|
3733
|
+
/** The relation property on the owner that declared this maintenance (documentary). */
|
|
3734
|
+
readonly relationProperty: string;
|
|
3735
|
+
/** The trigger (`<sourceLogicalName>.<event>`) that fired this maintenance. */
|
|
3736
|
+
readonly trigger: MaintainTrigger;
|
|
3737
|
+
/**
|
|
3738
|
+
* Whether the write mirrors a single row (`snapshot`), appends a collection item
|
|
3739
|
+
* (`collection`), or applies a scalar `ADD` counter (`counter`, #141).
|
|
3740
|
+
*/
|
|
3741
|
+
readonly kind: 'snapshot' | 'collection' | 'counter';
|
|
3742
|
+
/**
|
|
3743
|
+
* The destination row's key binding: target key field → the mutation-input param
|
|
3744
|
+
* that supplies it (resolved from the effect's `$.entity.<sourceField>` paths —
|
|
3745
|
+
* the source row image, payload 同梱). Every owner-key field is bound.
|
|
3746
|
+
*/
|
|
3747
|
+
readonly keyBinding: Readonly<Record<string, string>>;
|
|
3748
|
+
/**
|
|
3749
|
+
* The projection: target attribute → `{ op, args, inputField }`, where `inputField`
|
|
3750
|
+
* is the mutation-input param the source value is read from and `op` / `args` are
|
|
3751
|
+
* the A/#120 {@link ProjectionTransform} realization (`identity` copies through,
|
|
3752
|
+
* `preview` keeps the first `n` chars). Empty for a `counter` (a scalar `ADD`
|
|
3753
|
+
* projects no row attributes).
|
|
3754
|
+
*/
|
|
3755
|
+
readonly projection: Readonly<Record<string, {
|
|
3756
|
+
readonly op: ProjectionTransform['op'];
|
|
3757
|
+
readonly args: readonly unknown[];
|
|
3758
|
+
readonly inputField: string;
|
|
3759
|
+
}>>;
|
|
3760
|
+
/**
|
|
3761
|
+
* For a `collection` write, the bounded-collection options (the target `field`,
|
|
3762
|
+
* the optional `maxItems` cap, and the optional `orderBy` input param); absent for
|
|
3763
|
+
* a `snapshot` / `counter`.
|
|
3764
|
+
*/
|
|
3765
|
+
readonly collection?: {
|
|
3766
|
+
readonly field: string;
|
|
3767
|
+
readonly maxItems?: number;
|
|
3768
|
+
readonly orderBy?: string;
|
|
3769
|
+
};
|
|
3770
|
+
/**
|
|
3771
|
+
* For a `counter` write (#141 — `@aggregate(..., { pattern: 'counter', value:
|
|
3772
|
+
* count() })`), the scalar `ADD`: the target {@link counter.attribute} and the
|
|
3773
|
+
* signed {@link counter.delta} the trigger applies (`+1` created / `-1` removed).
|
|
3774
|
+
* A counter `ADD` is commutative, so two counter writes on the SAME row MERGE (the
|
|
3775
|
+
* #92 symmetric-counter collapse) rather than reject — unlike snapshot/collection.
|
|
3776
|
+
* Absent for a `snapshot` / `collection`.
|
|
3777
|
+
*/
|
|
3778
|
+
readonly counter?: {
|
|
3779
|
+
readonly attribute: string;
|
|
3780
|
+
readonly delta: number;
|
|
3781
|
+
};
|
|
3782
|
+
}
|
|
2786
3783
|
/**
|
|
2787
3784
|
* A **derived uniqueness guard** — the marker-row `Put` (and, on a unique-field
|
|
2788
3785
|
* change, the old-guard `Delete` + new-guard `Put` swap) that enforces an
|
|
@@ -2975,6 +3972,20 @@ interface CompiledFragment {
|
|
|
2975
3972
|
* guard and rolls the WHOLE transaction back — so no effect is double-applied.
|
|
2976
3973
|
*/
|
|
2977
3974
|
readonly idempotencyGuard?: DerivedIdempotencyGuard;
|
|
3975
|
+
/**
|
|
3976
|
+
* The maintenance writes derived from the maintenance graph (issue #125; the
|
|
3977
|
+
* D/#124 → compile lowering): one {@link DerivedMaintainWrite} per relation that
|
|
3978
|
+
* declared `write.maintainedOn` for this fragment's lifecycle event (a projected
|
|
3979
|
+
* snapshot / collection on a SEPARATE target row). Present (non-empty) only when
|
|
3980
|
+
* the fragment's trigger fires at least one maintainer; absent otherwise (no
|
|
3981
|
+
* regression — a fragment that maintains nothing compiles exactly as before).
|
|
3982
|
+
*
|
|
3983
|
+
* SCOPE (#125): each entry is the runtime-neutral structure the runtime (#127)
|
|
3984
|
+
* realizes into the atomic `TransactWriteItems`; #125 derives it and rejects a
|
|
3985
|
+
* same-target collision (論点2 = b). The cross-fragment AGGREGATION and the
|
|
3986
|
+
* one-atomic-tx execution wiring are #127 — not done here.
|
|
3987
|
+
*/
|
|
3988
|
+
readonly maintainWrites?: readonly DerivedMaintainWrite[];
|
|
2978
3989
|
}
|
|
2979
3990
|
/**
|
|
2980
3991
|
* Resolve the {@link LifecycleContract} a fragment adopts for its intent:
|
|
@@ -3015,8 +4026,12 @@ type EntityRefResolver = (ref: MutationEntityRef, consumerIndex: number, consume
|
|
|
3015
4026
|
* @param resolveEntityRef Resolver for a `$.entity[i].field` cross-fragment
|
|
3016
4027
|
* reference (multi-fragment compile), or `null` for a single-fragment compile
|
|
3017
4028
|
* (where such a reference is a hard error).
|
|
4029
|
+
* @param maintenanceGraph The {@link MaintenanceGraph} the #125 maintain injection
|
|
4030
|
+
* queries for this fragment's trigger ({@link resolveMaintainers}). Defaults to a
|
|
4031
|
+
* lazily-built, cached graph over the global registry; an explicit graph is
|
|
4032
|
+
* accepted for scoped tests.
|
|
3018
4033
|
*/
|
|
3019
|
-
declare function compileFragment(fragment: MutationFragment, index?: number, resolveEntityRef?: EntityRefResolver | null): CompiledFragment;
|
|
4034
|
+
declare function compileFragment(fragment: MutationFragment, index?: number, resolveEntityRef?: EntityRefResolver | null, maintenanceGraph?: MaintenanceGraph): CompiledFragment;
|
|
3020
4035
|
/**
|
|
3021
4036
|
* Compile a single-fragment {@link CommandPlan} into its {@link CompiledFragment}.
|
|
3022
4037
|
* The N-fragment atomic merge is **#90** ({@link compileMutationPlan}); this entry
|
|
@@ -3688,6 +4703,23 @@ interface CommandMethodSpec<TKey, TParams, TResult> {
|
|
|
3688
4703
|
* the guard and rolls the WHOLE write back — no effect is double-applied.
|
|
3689
4704
|
*/
|
|
3690
4705
|
readonly idempotencyGuard?: DerivedIdempotencyGuard;
|
|
4706
|
+
/**
|
|
4707
|
+
* The maintenance writes derived from the relation-side `write.maintainedOn`
|
|
4708
|
+
* declarations (issue #125; D/#124 → compile lowering), flattened across every
|
|
4709
|
+
* fragment in declaration order. Present (non-empty) only when a fragment's
|
|
4710
|
+
* lifecycle event (`Post.created` etc.) fires at least one maintainer; absent
|
|
4711
|
+
* otherwise (no regression — a maintainer-free mutation is byte-identical to its
|
|
4712
|
+
* pre-#127 form). Each {@link DerivedMaintainWrite} projects the just-written
|
|
4713
|
+
* source row into a SEPARATE owner (destination) row — a single-row snapshot
|
|
4714
|
+
* (`kind: 'snapshot'`) or an item appended into a bounded collection (`kind:
|
|
4715
|
+
* 'collection'`). Their presence (like {@link conditionChecks}) **promotes** an
|
|
4716
|
+
* otherwise single-op method to a transaction: the runtime (#127) realizes each
|
|
4717
|
+
* into the method's atomic `TransactWriteItems`, so the maintained row moves
|
|
4718
|
+
* atomically with the source write (a failure rolls the WHOLE set back). #127 is
|
|
4719
|
+
* the synchronous (`updateMode: 'mutation'`) path only; the async stream path is
|
|
4720
|
+
* #130 (loud-rejected at compile time, never silently dropped).
|
|
4721
|
+
*/
|
|
4722
|
+
readonly maintainWrites?: readonly DerivedMaintainWrite[];
|
|
3691
4723
|
/** @internal Phantom carrier retaining the formal `CommandMethod` type. */
|
|
3692
4724
|
readonly __signature?: CommandMethod<TKey, TParams, TResult>;
|
|
3693
4725
|
}
|
|
@@ -4288,6 +5320,22 @@ interface ExecutableCommandContract {
|
|
|
4288
5320
|
* Absent on a mutation with no `idempotency` / a #64 hand-written method.
|
|
4289
5321
|
*/
|
|
4290
5322
|
readonly idempotencyGuard?: DerivedIdempotencyGuard;
|
|
5323
|
+
/**
|
|
5324
|
+
* The maintenance writes derived from the relation-side `write.maintainedOn`
|
|
5325
|
+
* declarations (issue #125 → #127; the D/#124 → compile lowering). When
|
|
5326
|
+
* present (non-empty), a **single-key** call composes each maintenance write
|
|
5327
|
+
* — a projected snapshot (`SET`) or a bounded-collection append
|
|
5328
|
+
* (`SET … = list_append(…)`) on a SEPARATE owner row — into the same atomic
|
|
5329
|
+
* `TransactWriteItems` as the source entity write (and every other effect),
|
|
5330
|
+
* so the maintained row moves ATOMICALLY with the source: a failure on any
|
|
5331
|
+
* item rolls the WHOLE transaction back (the snapshot / collection never
|
|
5332
|
+
* partially persists). The owner row is upserted (a bare `UpdateItem`
|
|
5333
|
+
* creates it if absent — the additive maintenance semantics). This is the
|
|
5334
|
+
* synchronous (`updateMode: 'mutation'`) path only; the async stream path is
|
|
5335
|
+
* #130 (loud-rejected at compile time). Absent on a mutation that fires no
|
|
5336
|
+
* maintainer / a #64 hand-written method.
|
|
5337
|
+
*/
|
|
5338
|
+
readonly maintainWrites?: readonly DerivedMaintainWrite[];
|
|
4291
5339
|
/**
|
|
4292
5340
|
* The return projection of a `mutation`-derived method (issue #83). When
|
|
4293
5341
|
* present, a **single-key** call performs the write then issues a
|
|
@@ -4391,6 +5439,13 @@ interface ReadRouteOptions {
|
|
|
4391
5439
|
readonly after?: string;
|
|
4392
5440
|
readonly order?: 'ASC' | 'DESC';
|
|
4393
5441
|
readonly filter?: Record<string, unknown>;
|
|
5442
|
+
/**
|
|
5443
|
+
* Host-injected per-call read context (issue #50 / #138). Threaded into the
|
|
5444
|
+
* route's underlying read (`executeQuery` / `executeList`), so the read hooks
|
|
5445
|
+
* R1–R5 fire for the route with this `ctx.context`. Each route runs
|
|
5446
|
+
* independently, so its hooks see only its own context. Absent ⇒ `{}`.
|
|
5447
|
+
*/
|
|
5448
|
+
readonly context?: RequestContext;
|
|
4394
5449
|
}
|
|
4395
5450
|
/** One read route: a `query` (point/unique) or a `list` (partition) descriptor. */
|
|
4396
5451
|
interface ReadRouteDescriptor {
|
|
@@ -4447,6 +5502,13 @@ interface MutateOptions {
|
|
|
4447
5502
|
* built-in default policy.
|
|
4448
5503
|
*/
|
|
4449
5504
|
readonly retry?: RetryOverride;
|
|
5505
|
+
/**
|
|
5506
|
+
* Host-injected per-call write context (issue #50 / #139) — exposed to every
|
|
5507
|
+
* write hook (W1–W5) as `ctx.context`. In `transaction` mode every composed op
|
|
5508
|
+
* shares it and the persist hooks fire once for the atomic batch; in `parallel`
|
|
5509
|
+
* mode each independent op sees it. NEVER serialized. Absent ⇒ `{}`.
|
|
5510
|
+
*/
|
|
5511
|
+
readonly context?: RequestContext;
|
|
4450
5512
|
}
|
|
4451
5513
|
/** A per-alias result in `parallel` mode: success carries the read-back, else error. */
|
|
4452
5514
|
type ParallelOpResult = {
|
|
@@ -4548,7 +5610,33 @@ declare abstract class DDBModel {
|
|
|
4548
5610
|
* for that call (`false` disables retry).
|
|
4549
5611
|
*/
|
|
4550
5612
|
static setRetryPolicy(policy: RetryPolicy | null): void;
|
|
4551
|
-
|
|
5613
|
+
/**
|
|
5614
|
+
* Register a {@link Middleware} (issue #50; read path #138, write path #139).
|
|
5615
|
+
* One registered object may carry read and/or write hooks.
|
|
5616
|
+
*
|
|
5617
|
+
* - **Read** R1–R5 — request entry (`read.before`), each physical op before send
|
|
5618
|
+
* / after raw fetch (`read.op.before` / `read.op.afterFetch`, INCLUDING every
|
|
5619
|
+
* relation fan-out fetch), the final hydrated result (`read.afterFetch`), and on
|
|
5620
|
+
* error (`read.op.onError` / `read.onError`).
|
|
5621
|
+
* - **Write** W1–W5 — the logical write before effect derivation (`write.before`,
|
|
5622
|
+
* may mutate `ctx.input` / `ctx.kind` incl. a delete→update rewrite), after commit
|
|
5623
|
+
* (`write.after`), the physical persist on the fully composed batch
|
|
5624
|
+
* (`write.persist.before` / `write.persist.after`, fires ONCE per atomic
|
|
5625
|
+
* transaction), and on error (`write.persist.onError` / `write.onError`).
|
|
5626
|
+
*
|
|
5627
|
+
* Hooks may observe, mutate `ctx`, `throw` to cancel, or (on `onError`) recover by
|
|
5628
|
+
* returning a value; the library imposes no semantics. Ordering: `before*` runs
|
|
5629
|
+
* first-registered-first (FIFO); `after*` / `onError` run last-registered-first
|
|
5630
|
+
* (LIFO). Mirrors {@link setRetryPolicy} — a host-runtime global stored on
|
|
5631
|
+
* {@link ClientManager}; never serialized (the bridge #48 is unaffected). Per-call
|
|
5632
|
+
* host context flows in via the read's / write's `{ context }` option.
|
|
5633
|
+
*/
|
|
5634
|
+
static use(mw: Middleware): void;
|
|
5635
|
+
/** Remove every registered middleware (read AND write; teardown / tests). See {@link use}. */
|
|
5636
|
+
static clearMiddleware(): void;
|
|
5637
|
+
static transaction(fn: (tx: TransactionContext) => void | Promise<void>, options?: {
|
|
5638
|
+
context?: RequestContext;
|
|
5639
|
+
}): Promise<void>;
|
|
4552
5640
|
/**
|
|
4553
5641
|
* In-process multi-route READ (issue #101 — the unified envelope). Each alias
|
|
4554
5642
|
* route (`{ query | list: Model, key, select, options? }`) runs **independently
|
|
@@ -4576,7 +5664,19 @@ declare abstract class DDBModel {
|
|
|
4576
5664
|
static mutate<const E extends WriteEnvelope>(envelope: E, options: {
|
|
4577
5665
|
mode: 'parallel';
|
|
4578
5666
|
}): Promise<MutateParallelResult<E>>;
|
|
4579
|
-
|
|
5667
|
+
/**
|
|
5668
|
+
* Multi-key, multi-model batch read. Each {@link BatchGetRequest} contributes its
|
|
5669
|
+
* keys to a chunked `BatchGetItem` per physical table; results are re-grouped by
|
|
5670
|
+
* model on the returned {@link BatchGetResult}.
|
|
5671
|
+
*
|
|
5672
|
+
* Fires the read hooks (issue #142): request-level R1/R4/R5 with kind
|
|
5673
|
+
* `'batchGet'`, and op-level R2/R3/R5 for each underlying physical `BatchGetItem`
|
|
5674
|
+
* op. The optional `{ context }` is exposed to every hook as `ctx.context` and
|
|
5675
|
+
* threaded UNCHANGED to every op (absent ⇒ `{}`), so a global hook (e.g. a
|
|
5676
|
+
* tenant-scope R2 FilterExpression) applies across the whole batch. With no
|
|
5677
|
+
* middleware registered the read path is unchanged (zero-overhead fast path).
|
|
5678
|
+
*/
|
|
5679
|
+
static batchGet(requests: BatchGetRequest[], options?: BatchGetOptions): Promise<BatchGetResult>;
|
|
4580
5680
|
static batchWrite(requests: BatchWriteRequest[]): Promise<void>;
|
|
4581
5681
|
static asModel<C extends new (...args: never[]) => DDBModel>(this: C): ModelStatic<InstanceType<C>, C>;
|
|
4582
5682
|
}
|
|
@@ -4707,6 +5807,236 @@ declare function key<T extends Record<string, unknown>>(builder: (c: {
|
|
|
4707
5807
|
readonly [K in keyof T]-?: Column<T[K], T>;
|
|
4708
5808
|
}) => KeyStructure): KeyDefinitionMarker<T>;
|
|
4709
5809
|
|
|
5810
|
+
type DynamoType = 'S' | 'N' | 'BOOL' | 'B' | 'SS' | 'NS' | 'L' | 'M';
|
|
5811
|
+
interface FieldOptions {
|
|
5812
|
+
format?: 'datetime' | 'date';
|
|
5813
|
+
readonly?: boolean;
|
|
5814
|
+
serialize?: (value: unknown) => unknown;
|
|
5815
|
+
deserialize?: (value: unknown) => unknown;
|
|
5816
|
+
}
|
|
5817
|
+
interface FieldMetadata {
|
|
5818
|
+
propertyName: string;
|
|
5819
|
+
dynamoType: DynamoType;
|
|
5820
|
+
options?: FieldOptions;
|
|
5821
|
+
/**
|
|
5822
|
+
* For a `@literal(...)` field — a `string`-stored field whose value is one of a
|
|
5823
|
+
* known, finite set — the allowed literal values, in declaration order (issue
|
|
5824
|
+
* #75). Recorded purely as model metadata so a Contract param bound into the
|
|
5825
|
+
* field can recover the precise `literal` param-kind from its bind position
|
|
5826
|
+
* (`src/spec/contracts.ts`), serializing as `{ type: 'literal', literals: [...] }`
|
|
5827
|
+
* instead of the imprecise `'string'`. JSON-safe by construction (a non-empty
|
|
5828
|
+
* array of strings). `undefined` for every other field decorator (`@string`,
|
|
5829
|
+
* `@number`, …), so plain fields are byte-for-byte unchanged (backward compat).
|
|
5830
|
+
*/
|
|
5831
|
+
literals?: readonly string[];
|
|
5832
|
+
}
|
|
5833
|
+
|
|
5834
|
+
interface KeyDefinition {
|
|
5835
|
+
/** The canonical structured key (PK/SK segment lists). */
|
|
5836
|
+
segmented: SegmentedKey;
|
|
5837
|
+
inputFieldNames: string[];
|
|
5838
|
+
}
|
|
5839
|
+
interface GsiDefinition {
|
|
5840
|
+
indexName: string;
|
|
5841
|
+
/** The canonical structured key (PK/SK segment lists). */
|
|
5842
|
+
segmented: SegmentedKey;
|
|
5843
|
+
inputFieldNames: string[];
|
|
5844
|
+
unique: boolean;
|
|
5845
|
+
}
|
|
5846
|
+
interface RelationLimitOptions {
|
|
5847
|
+
default: number;
|
|
5848
|
+
max: number;
|
|
5849
|
+
}
|
|
5850
|
+
/**
|
|
5851
|
+
* The named maintenance pattern a relation participates in (Epic #118 §5.1).
|
|
5852
|
+
*
|
|
5853
|
+
* `pattern` is the *preset* selector: omitting it leaves the relation a plain
|
|
5854
|
+
* read-only navigation (the historical `hasMany`/`belongsTo`/`hasOne`
|
|
5855
|
+
* behaviour), exactly backward compatible. When present it names the AWS
|
|
5856
|
+
* design pattern the relation lowers to — the lowering itself (maintenance
|
|
5857
|
+
* graph / compile injection) is out of scope for issue #121 and lands in
|
|
5858
|
+
* #124/#125; here the value is recorded purely as declaration metadata.
|
|
5859
|
+
*
|
|
5860
|
+
* Typed as a `string` union of the known presets but kept open-ended via the
|
|
5861
|
+
* `(string & {})` tail so a future preset can be authored before this union is
|
|
5862
|
+
* widened, without a breaking change to callers.
|
|
5863
|
+
*/
|
|
5864
|
+
type RelationPattern = 'samePartition' | 'edge' | 'counter' | 'embeddedSnapshot' | 'materializedView' | 'sparseView' | 'externalProjection' | (string & {});
|
|
5865
|
+
/**
|
|
5866
|
+
* Consistency boundary a maintained write is declared against (Epic #118 §4.A.7):
|
|
5867
|
+
* `transactional` = same `TransactWriteItems`; `eventual` = asynchronous catch-up.
|
|
5868
|
+
*/
|
|
5869
|
+
type RelationConsistency = 'transactional' | 'eventual';
|
|
5870
|
+
/**
|
|
5871
|
+
* How a maintained write is applied (Epic #118 §4.A.7):
|
|
5872
|
+
* `mutation` = composed into the same mutation; `stream` = driven asynchronously
|
|
5873
|
+
* off a stream / outbox.
|
|
5874
|
+
*/
|
|
5875
|
+
type RelationUpdateMode = 'mutation' | 'stream';
|
|
5876
|
+
/**
|
|
5877
|
+
* Read-strategy hints for a maintained relation (Epic #118 §5.1 `read`).
|
|
5878
|
+
*
|
|
5879
|
+
* All fields optional — present only when a `pattern` declares a read shape
|
|
5880
|
+
* (e.g. an `embeddedSnapshot` keeping the latest N items). A bare relation
|
|
5881
|
+
* carries no `read` block.
|
|
5882
|
+
*/
|
|
5883
|
+
interface RelationReadOptions {
|
|
5884
|
+
/** Cap on materialised items kept inline (e.g. "latest 3 posts"). */
|
|
5885
|
+
maxItems?: number;
|
|
5886
|
+
/** Ordering of the maintained collection. */
|
|
5887
|
+
order?: 'ASC' | 'DESC';
|
|
5888
|
+
}
|
|
5889
|
+
/**
|
|
5890
|
+
* Write-maintenance declaration for a relation (Epic #118 §5.1 `write`).
|
|
5891
|
+
*
|
|
5892
|
+
* `maintainedOn` is the cross-entity trigger list (`"<Entity>.<event>"`, e.g.
|
|
5893
|
+
* `"Post.created"`) that the maintenance graph (#124/#125) will key off. Recorded
|
|
5894
|
+
* here as declaration metadata only.
|
|
5895
|
+
*/
|
|
5896
|
+
interface RelationWriteOptions {
|
|
5897
|
+
/**
|
|
5898
|
+
* Cross-entity triggers, e.g. `['Post.created', 'Post.removed']`. The event
|
|
5899
|
+
* segment is the confirmed shared lifecycle vocabulary (epic #118 A/B
|
|
5900
|
+
* reconciliation): `created` / `updated` / `removed`.
|
|
5901
|
+
*/
|
|
5902
|
+
maintainedOn?: string[];
|
|
5903
|
+
consistency?: RelationConsistency;
|
|
5904
|
+
updateMode?: RelationUpdateMode;
|
|
5905
|
+
}
|
|
5906
|
+
/**
|
|
5907
|
+
* Snapshot / projection capture map for a maintained relation
|
|
5908
|
+
* (Epic #118 §5.1 / §5.2 `projection`).
|
|
5909
|
+
*
|
|
5910
|
+
* Maps each captured field name on the maintaining side to **how** the source
|
|
5911
|
+
* value is projected. The confirmed shared vocabulary (epic #118 A/B
|
|
5912
|
+
* reconciliation) is the **function-form IR** — the same {@link
|
|
5913
|
+
* ProjectionTransform} node A (#120) defines on `src/define/entity-writes.ts`,
|
|
5914
|
+
* re-used here (NOT re-defined) so the two surfaces never re-diverge. Each value
|
|
5915
|
+
* is either:
|
|
5916
|
+
*
|
|
5917
|
+
* - a {@link ProjectionTransform} (built with the standalone authoring helpers,
|
|
5918
|
+
* e.g. `preview('body', 120)` — RFC §5.2 `textPreview: preview('body', 120)`),
|
|
5919
|
+
* or
|
|
5920
|
+
* - a bare `string` — the **identity shorthand**: project that source path
|
|
5921
|
+
* through unchanged (e.g. `{ postId: 'postId' }`).
|
|
5922
|
+
*
|
|
5923
|
+
* `Readonly` so the recorded declaration metadata is not mutated downstream.
|
|
5924
|
+
* Issue #121 scopes this to the declaration shape; the maintenance graph /
|
|
5925
|
+
* compile injection that consumes the IR is #124/#125.
|
|
5926
|
+
*
|
|
5927
|
+
* @example `{ postId: 'postId', textPreview: preview('body', 120) }`
|
|
5928
|
+
*/
|
|
5929
|
+
type RelationProjection = Readonly<Record<string, ProjectionTransform | string>>;
|
|
5930
|
+
interface RelationOptions {
|
|
5931
|
+
limit?: RelationLimitOptions;
|
|
5932
|
+
order?: 'ASC' | 'DESC';
|
|
5933
|
+
/**
|
|
5934
|
+
* Named maintenance preset (Epic #118). Omitted = plain read-only navigation,
|
|
5935
|
+
* fully backward compatible with the historical relation decorators.
|
|
5936
|
+
*/
|
|
5937
|
+
pattern?: RelationPattern;
|
|
5938
|
+
read?: RelationReadOptions;
|
|
5939
|
+
write?: RelationWriteOptions;
|
|
5940
|
+
projection?: RelationProjection;
|
|
5941
|
+
}
|
|
5942
|
+
interface RelationMetadata {
|
|
5943
|
+
type: 'hasMany' | 'hasOne' | 'belongsTo';
|
|
5944
|
+
propertyName: string;
|
|
5945
|
+
targetFactory: () => new (...args: unknown[]) => unknown;
|
|
5946
|
+
keyBinding: Record<string, string>;
|
|
5947
|
+
options?: RelationOptions;
|
|
5948
|
+
}
|
|
5949
|
+
/**
|
|
5950
|
+
* The scalar-aggregate value an `@aggregate` field derives from its source entity
|
|
5951
|
+
* (Epic #118 §5.2 counter / latest preset; issue #122). It is the function-form
|
|
5952
|
+
* IR built by the {@link count} / {@link max} value helpers — the aggregation an
|
|
5953
|
+
* `@aggregate(() => Source, keyBinding, { pattern, value })` field maintains:
|
|
5954
|
+
*
|
|
5955
|
+
* - `count` — the cardinality of source rows matched by the key binding (no
|
|
5956
|
+
* source field; e.g. `postCount!: number` ← `count()`).
|
|
5957
|
+
* - `max` — the maximum value of a named source `field` (e.g. `lastPostAt!:
|
|
5958
|
+
* string` ← `max('createdAt')`).
|
|
5959
|
+
*
|
|
5960
|
+
* Recorded purely as declaration metadata (issue #122 scope = declaration
|
|
5961
|
+
* surface): the maintenance graph (#124) / compile injection (#125) consume it;
|
|
5962
|
+
* the effect itself is out of scope here. Kept open-ended via the discriminated
|
|
5963
|
+
* `op` so a future aggregation (`min`, `sum`, …) extends the union without a
|
|
5964
|
+
* breaking change.
|
|
5965
|
+
*
|
|
5966
|
+
* @example `count()` → `{ op: 'count' }`
|
|
5967
|
+
* @example `max('createdAt')` → `{ op: 'max', field: 'createdAt' }`
|
|
5968
|
+
*/
|
|
5969
|
+
type AggregateValue = {
|
|
5970
|
+
readonly op: 'count';
|
|
5971
|
+
} | {
|
|
5972
|
+
readonly op: 'max';
|
|
5973
|
+
readonly field: string;
|
|
5974
|
+
};
|
|
5975
|
+
/**
|
|
5976
|
+
* Options for an `@aggregate` field (Epic #118 §5.2 counter / latest preset;
|
|
5977
|
+
* issue #122). Mirrors the maintenance vocabulary a relation declares so the two
|
|
5978
|
+
* authoring surfaces stay in lockstep:
|
|
5979
|
+
*
|
|
5980
|
+
* - `pattern` reuses {@link RelationPattern} (e.g. `'counter'`) — the named
|
|
5981
|
+
* maintenance preset, NOT re-defined here.
|
|
5982
|
+
* - `value` is the {@link AggregateValue} the field maintains (`count()` /
|
|
5983
|
+
* `max('createdAt')`).
|
|
5984
|
+
* - `write` reuses {@link RelationWriteOptions} verbatim — `maintainedOn`
|
|
5985
|
+
* triggers (`Post.created` / `Post.removed`, the confirmed shared
|
|
5986
|
+
* `created|updated|removed` event vocabulary) plus `consistency`
|
|
5987
|
+
* (`transactional|eventual`) / `updateMode` (`mutation|stream`). These are NOT
|
|
5988
|
+
* re-defined; they are the same shared types B (#121) added.
|
|
5989
|
+
*/
|
|
5990
|
+
interface AggregateOptions {
|
|
5991
|
+
/** Named maintenance preset (typically `'counter'`); reuses {@link RelationPattern}. */
|
|
5992
|
+
pattern?: RelationPattern;
|
|
5993
|
+
/** The scalar aggregation this field maintains (`count()` / `max('createdAt')`). */
|
|
5994
|
+
value: AggregateValue;
|
|
5995
|
+
/** Cross-entity maintenance triggers / consistency / update-mode (reused from B). */
|
|
5996
|
+
write?: RelationWriteOptions;
|
|
5997
|
+
}
|
|
5998
|
+
/**
|
|
5999
|
+
* Declaration metadata recorded by an `@aggregate` field decorator (issue #122).
|
|
6000
|
+
*
|
|
6001
|
+
* An `@aggregate` is a **scalar** field (`postCount!: number`,
|
|
6002
|
+
* `lastPostAt!: string`) whose value is derived — by aggregating rows of a source
|
|
6003
|
+
* entity resolved by {@link targetFactory} + {@link keyBinding} — rather than
|
|
6004
|
+
* stored directly. It is therefore recorded in its **own** metadata array
|
|
6005
|
+
* (`EntityMetadata.aggregates`), distinct from both {@link FieldMetadata} (a
|
|
6006
|
+
* plain stored attribute) and {@link RelationMetadata} (a navigation yielding
|
|
6007
|
+
* model instances): an aggregate carries a source binding like a relation, but
|
|
6008
|
+
* surfaces a scalar value like a field. The collector path mirrors relations
|
|
6009
|
+
* (decorator pushes, `@model` drains).
|
|
6010
|
+
*
|
|
6011
|
+
* Issue #122 scope is the declaration surface + metadata pass-through only; the
|
|
6012
|
+
* aggregation effect's compile / runtime (#124/#125/#127) is out of scope.
|
|
6013
|
+
*/
|
|
6014
|
+
interface AggregateMetadata {
|
|
6015
|
+
/** The scalar field the aggregate value is written to. */
|
|
6016
|
+
propertyName: string;
|
|
6017
|
+
/** Resolves the source entity whose rows are aggregated. */
|
|
6018
|
+
targetFactory: () => new (...args: unknown[]) => unknown;
|
|
6019
|
+
/** The key binding selecting the source rows (target key field → source field). */
|
|
6020
|
+
keyBinding: Record<string, string>;
|
|
6021
|
+
/** The aggregate preset / value / maintenance-write declaration. */
|
|
6022
|
+
options: AggregateOptions;
|
|
6023
|
+
}
|
|
6024
|
+
interface EmbeddedMetadata {
|
|
6025
|
+
propertyName: string;
|
|
6026
|
+
modelFactory: () => new (...args: unknown[]) => unknown;
|
|
6027
|
+
}
|
|
6028
|
+
interface EntityMetadata {
|
|
6029
|
+
tableName: string;
|
|
6030
|
+
prefix: string;
|
|
6031
|
+
fields: FieldMetadata[];
|
|
6032
|
+
primaryKey: KeyDefinition | null;
|
|
6033
|
+
gsiDefinitions: GsiDefinition[];
|
|
6034
|
+
relations: RelationMetadata[];
|
|
6035
|
+
/** Scalar `@aggregate` fields (Epic #118 §5.2 counter / latest; issue #122). */
|
|
6036
|
+
aggregates: AggregateMetadata[];
|
|
6037
|
+
embeddedFields: EmbeddedMetadata[];
|
|
6038
|
+
}
|
|
6039
|
+
|
|
4710
6040
|
/**
|
|
4711
6041
|
* CDC emulator types (issue #72). These types are **cdc-module-owned**; core
|
|
4712
6042
|
* never references them. The {@link ChangeEvent} shape mirrors DynamoDB Streams
|
|
@@ -4820,4 +6150,4 @@ interface CdcEmulatorOptions {
|
|
|
4820
6150
|
}
|
|
4821
6151
|
type ShardId = string;
|
|
4822
6152
|
|
|
4823
|
-
export { type
|
|
6153
|
+
export { type Param as $, type AggregateOptions as A, type BatchGetExecInput as B, type ChangeEvent as C, type DynamoDBOperation as D, type Executor as E, type FieldOptions as F, type GsiDefinition as G, type ResolvedKey as H, type Item as I, type CdcEmulatorOptions as J, type KeyDefinition as K, type ChangeHandler as L, type ModelStatic as M, type Unsubscribe as N, type FaultSpec as O, type PutInput as P, type ConcurrentRecomputeRef as Q, type ReadExecOptions as R, type SelectableOf as S, type TransactWriteExecItem as T, type UpdateInput as U, type EventLog as V, type WriteExecOptions as W, type ReplayOptions as X, type ShardId as Y, type RelationMetadata as Z, type TransactionItemSpec as _, type ExecutorResult as a, type ContractKeySpec as a$, type ParamDescriptor as a0, type DefinitionMap as a1, type OperationDefinition as a2, type WriteDefinitionOptions as a3, type PartialQueryKeyOf as a4, type StrictSelectSpec as a5, type EntityInput as a6, type UniqueQueryKeyOf as a7, type EntityRef as a8, type ConditionInput as a9, type CollectionEffect as aA, type CollectionOptions as aB, type Column as aC, type ColumnMap as aD, type CommandContractMethodSpec as aE, type CommandInputShape as aF, type CommandMethod as aG, type CommandPlan as aH, type CommandResolutionTarget as aI, type CommandResultKind as aJ, type CommandSelectShape as aK, type CompiledFragment as aL, type ComposeSpec as aM, type CondSlot as aN, type ConditionCheckInput as aO, type Connection as aP, type ContractCallSignature as aQ, type ContractCardinality as aR, type ContractCommandParams as aS, type ContractCommandResult as aT, type ContractComposeNode as aU, type ContractFromRef as aV, type ContractInputArity as aW, type ContractItem as aX, type ContractKeyFieldRef as aY, type ContractKeyInput as aZ, type ContractKeyRef as a_, type QueryModelContract as aa, type QueryMethodSpec as ab, type CommandModelContract as ac, type CommandMethodSpec as ad, type ContractSpec as ae, type QuerySpec as af, type CommandSpec as ag, type ContextSpec as ah, type OperationsDocument as ai, type AnyOperationDefinition as aj, type BridgeBundle as ak, type ConditionSpec as al, type AggregateMetadata as am, type BatchDeleteRequest as an, type BatchGetOptions as ao, type BatchGetRequest as ap, BatchGetResult as aq, type BatchPutRequest as ar, type BatchResult as as, type BatchWriteRequest as at, CONTRACT_RANGE_FANOUT_CONCURRENCY as au, type CdcMode as av, type Change as aw, type ChangeBatch as ax, type ChangeEventName as ay, type ClockMode as az, type WriteResult as b, type MutationInputProxy as b$, type ContractKind as b0, type ContractMethodOp as b1, type ContractParamRef as b2, type ContractQueryParams as b3, type ContractResolution as b4, type CounterAggregate as b5, type CounterEffect as b6, type CtxBase as b7, DEFAULT_MAX_ATTEMPTS as b8, DEFAULT_RETRY_POLICY as b9, type KeyedResult as bA, LIFECYCLE_CONTRACT_MARKER as bB, type LifecycleContract as bC, type LifecycleEffects as bD, type LiteralParam as bE, type MaintainConsistency as bF, type MaintainEffect as bG, type MaintainEvent as bH, type MaintainItem as bI, type MaintainTrigger as bJ, type MaintainUpdateMode as bK, type MaintenanceGraph as bL, type ManifestEntity as bM, type ManifestField as bN, type ManifestFieldType as bO, type ManifestGsi as bP, type ManifestKey as bQ, type ManifestRelation as bR, type ManifestTable as bS, type ModelRef as bT, type MutateMode as bU, type MutateOptions as bV, type MutateParallelResult as bW, type MutateTransactionResult as bX, type MutationBody as bY, type MutationDescriptorMap as bZ, type MutationFragment as b_, type DeleteOptions as ba, type DeriveEffect as bb, type DerivedEdgeWrite as bc, type DerivedUpdate as bd, type DescriptorBinding as be, ENTITY_WRITES_MARKER as bf, type EdgeEffect as bg, type EffectPath as bh, type EmbeddedMetadata as bi, type EmitEffect as bj, type EntityWritesDefinition as bk, type EntityWritesShape as bl, type ExecutableCommandContract as bm, type ExecutableQueryContract as bn, type FilterInput as bo, type FilterSpec as bp, type FragmentInput as bq, type GsiDefinitionMarker as br, type GsiOptions as bs, type IdempotencyEffect as bt, type InProcessWriteDescriptor as bu, type InputArity as bv, type KeyDefinitionMarker as bw, type KeySegment as bx, type KeySlot as by, type KeyStructure as bz, type DeleteInput as c, type WriteKind as c$, type MutationInputRef as c0, type MutationIntent as c1, type NumberParam as c2, type OperationKind as c3, type OperationSpec as c4, type ParallelOpResult as c5, type ParamKind as c6, type ParamSpec as c7, type ParamStructure as c8, type PersistCtx as c9, type RelationSelect as cA, type RelationSpec as cB, type RelationUpdateMode as cC, type RelationWriteOptions as cD, type RequiresEffect as cE, type Resolution as cF, type RetryInfo as cG, type RetryOperationKind as cH, SPEC_VERSION as cI, type SegmentSpec as cJ, type SegmentedKey as cK, type SelectBuilder as cL, type SelectOf as cM, type SnapshotEffect as cN, type StartingPosition as cO, type StreamViewType as cP, type StringParam as cQ, TransactionContext as cR, type TransactionItemType as cS, type UniqueEffect as cT, type Updatable as cU, type UpdateOptions as cV, type WhenSpec as cW, type WriteCtx as cX, type WriteDescriptor as cY, type WriteEnvelope as cZ, type WriteInput as c_, type PersistOrigin as ca, type PlannedCommandMethod as cb, type ProjectionMap as cc, type ProjectionTransform as cd, type ProjectionTransformOp as ce, type PutOptions as cf, type QueryContractMethodSpec as cg, type QueryEnvelopeResult as ch, type QueryKeyOf as ci, type QueryMethod as cj, type QueryResult as ck, type RangeConditionSpec as cl, type ReadEnvelope as cm, type ReadOpCtx as cn, type ReadOpKind as co, type ReadOperationType as cp, type ReadRouteDescriptor as cq, type ReadRouteOptions as cr, type ReadRouteResult as cs, type RecordedCompose as ct, type RelationBuilder as cu, type RelationConsistency as cv, type RelationLimitOptions as cw, type RelationPattern as cx, type RelationProjection as cy, type RelationReadOptions as cz, type BatchWriteExecItem as d, resolveLifecycle as d$, type WriteLifecyclePhase as d0, type WriteMiddleware as d1, type WriteOperationType as d2, type WriteRecorder as d3, type WriteResultProjection as d4, attachModelClass as d5, buildDeleteInput as d6, buildMaintenanceGraph as d7, buildPutInput as d8, buildUpdateInput 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, isParam as dK, isPlannedCommandMethod 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, param as dW, preview as dX, publicCommandModel as dY, publicQueryModel as dZ, query as d_, compileFragment as da, compileMutationPlan as db, compileSingleFragmentPlan 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 BatchExecOptions as e, wholeKeysSentinel as e0, 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 EntityMetadata as p, type DynamoType as q, type RelationOptions as r, type AggregateValue as s, type SelectBuilderSpec as t, type RawCondition as u, type TransactionSpec as v, type Manifest as w, type RetryOverride as x, type ExecutionPlan as y, type FieldMetadata as z };
|