graphddb 0.2.5 → 0.3.1

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.
@@ -1003,7 +1003,7 @@ interface Executor {
1003
1003
 
1004
1004
  /**
1005
1005
  * Write middleware / hook public surface (issue #50 write path, #139). The merged
1006
- * design is `docs/proposals/read-middleware.md`; this module implements the
1006
+ * design is `docs/middleware.md`; this module implements the
1007
1007
  * **write** hook points W1–W5 and their context objects, layered on the SAME
1008
1008
  * registry / runtime machinery the read hooks (#138) introduced.
1009
1009
  *
@@ -1014,18 +1014,18 @@ interface Executor {
1014
1014
  * the TS↔Python bridge (#48) is unaffected. It is **unrestricted by intent**: a
1015
1015
  * hook may observe, mutate `ctx`, `throw` to cancel, or (on `onError`) recover by
1016
1016
  * returning a value. The library does NOT validate hook logic or impose semantics
1017
- * (proposal appendix A). The internal CDC / capture mechanism (`src/capture/`,
1017
+ * (see docs/middleware.md). The internal CDC / capture mechanism (`src/capture/`,
1018
1018
  * `src/cdc/`) is a SEPARATE surface — write hooks are not a substitute for it nor
1019
- * it for them (proposal appendix C).
1019
+ * it for them (see docs/middleware.md).
1020
1020
  *
1021
- * @see docs/proposals/read-middleware.md (hook points W1–W5)
1021
+ * @see docs/middleware.md (hook points W1–W5)
1022
1022
  */
1023
1023
 
1024
1024
  /**
1025
1025
  * The logical write kinds a {@link WriteCtx} may carry (W1 / W2 / W5). A W1 hook
1026
1026
  * may REWRITE this in place — the canonical case is a `delete` → `update`
1027
1027
  * (soft-delete-by-rewrite); derivation then runs on the rewritten op, so the
1028
- * stored effect is the update, not the delete (proposal W1 + examples).
1028
+ * stored effect is the update, not the delete (see docs/middleware.md).
1029
1029
  */
1030
1030
  type WriteKind = 'put' | 'update' | 'delete';
1031
1031
  /**
@@ -1122,9 +1122,9 @@ interface PersistCtx {
1122
1122
  * The write half of a registered {@link import('./types.js').Middleware}. Any
1123
1123
  * subset of the write hook points may be set; an unset hook is skipped. All hooks
1124
1124
  * are async-first — the runtime always `await`s, so sync and async hooks compose
1125
- * uniformly (proposal "sync / async").
1125
+ * uniformly (see docs/middleware.md).
1126
1126
  *
1127
- * @see docs/proposals/read-middleware.md (hook points W1–W5)
1127
+ * @see docs/middleware.md (hook points W1–W5)
1128
1128
  */
1129
1129
  interface WriteMiddleware {
1130
1130
  /** W1 — logical write, before effect derivation. May mutate `ctx.input` / `ctx.kind`; `throw` cancels (in a tx, aborts all). */
@@ -1157,7 +1157,7 @@ interface WriteMiddleware {
1157
1157
 
1158
1158
  /**
1159
1159
  * Read middleware / hook public surface (issue #50, implemented for the read
1160
- * path in #138). The merged design is `docs/proposals/read-middleware.md`; this
1160
+ * path in #138). The merged design is `docs/middleware.md`; this
1161
1161
  * module implements the **read** hook points R1–R5 and their context objects.
1162
1162
  *
1163
1163
  * A {@link Middleware} is a host-only, runtime-registered object (registered via
@@ -1167,9 +1167,9 @@ interface WriteMiddleware {
1167
1167
  * `operations.json`, so the TS↔Python bridge (#48) is unaffected. Hooks are
1168
1168
  * **unrestricted by intent**: a hook may observe, mutate `ctx`, `throw` to
1169
1169
  * cancel, or return a replacement value. The library does NOT validate hook
1170
- * logic or impose semantics — see the proposal appendix A.
1170
+ * logic or impose semantics — see docs/middleware.md.
1171
1171
  *
1172
- * @see docs/proposals/read-middleware.md
1172
+ * @see docs/middleware.md
1173
1173
  */
1174
1174
 
1175
1175
  /** A raw DynamoDB item (the shape R3 sees, per-op). */
@@ -1196,7 +1196,7 @@ interface CtxBase {
1196
1196
  /**
1197
1197
  * Scratch space shared across this op's phases (R2 → R3 → R5 for one op, or
1198
1198
  * R1 → R4 → R5 for the request). A fresh object per request context object;
1199
- * each fan-out op gets its own `state` (see proposal appendix D — siblings run
1199
+ * each fan-out op gets its own `state` ((docs/middleware.md) — siblings run
1200
1200
  * concurrently, so a shared accumulator should live on a host object via
1201
1201
  * `context`, updated commutatively).
1202
1202
  */
@@ -1278,10 +1278,10 @@ interface ReadOpCtx extends CtxBase {
1278
1278
  /**
1279
1279
  * A registered read middleware object. Any subset of the read hook points may
1280
1280
  * be set; an unset hook is simply skipped. All hooks may be synchronous or
1281
- * return a `Promise` — the runtime always `await`s (async-first, see proposal
1281
+ * return a `Promise` — the runtime always `await`s (async-first, see docs/middleware.md
1282
1282
  * "sync / async").
1283
1283
  *
1284
- * @see docs/proposals/read-middleware.md (the write hooks W1–W5 are a later phase)
1284
+ * @see docs/middleware.md (the write hooks W1–W5 are a later phase)
1285
1285
  */
1286
1286
  interface Middleware {
1287
1287
  /** Optional name (for host-side debugging / ordering introspection). */
@@ -1301,7 +1301,7 @@ interface Middleware {
1301
1301
  * an `Item[]`: those become this op's items and the read pipeline continues
1302
1302
  * (the first hook that returns a value wins and short-circuits the chain).
1303
1303
  * Return `void` / `undefined` to decline — if no hook recovers, the original
1304
- * error rethrows. (See proposal R5 + appendix A — hooks are unrestricted.)
1304
+ * error rethrows. (see docs/middleware.md — hooks are unrestricted.)
1305
1305
  */
1306
1306
  onError?(ctx: ReadOpCtx, err: unknown): void | Item[] | Promise<void | Item[]>;
1307
1307
  };
@@ -1343,7 +1343,7 @@ type ProjectionOf<T, Sel> = Sel extends SelectableOf<T> ? Sel : SelectOf<Sel>;
1343
1343
  * usages keep compiling, while the precise inference is available when the
1344
1344
  * constructor type is supplied (as it is from `DDBModel.asModel()`).
1345
1345
  */
1346
- type AnyModelClass = abstract new (...args: any[]) => DDBModel;
1346
+ type AnyModelClass$1 = abstract new (...args: any[]) => DDBModel;
1347
1347
  /**
1348
1348
  * The accepted PK / GSI key type for `list()` / `explain()`.
1349
1349
  *
@@ -1353,24 +1353,24 @@ type AnyModelClass = abstract new (...args: any[]) => DDBModel;
1353
1353
  * {@link QueryKey}. Derived from the model class when available, otherwise
1354
1354
  * permissive.
1355
1355
  */
1356
- type ListKey<C> = [C] extends [AnyModelClass] ? PartialQueryKeyOf<C> : Record<string, unknown>;
1356
+ type ListKey<C> = [C] extends [AnyModelClass$1] ? PartialQueryKeyOf<C> : Record<string, unknown>;
1357
1357
  /**
1358
1358
  * The accepted key type for `query()` — only keys guaranteed to return a
1359
1359
  * single item (PK + unique GSIs). Derived from the model class when available.
1360
1360
  */
1361
- type QueryKey<C> = [C] extends [AnyModelClass] ? UniqueQueryKeyOf<C> : Record<string, unknown>;
1361
+ type QueryKey<C> = [C] extends [AnyModelClass$1] ? UniqueQueryKeyOf<C> : Record<string, unknown>;
1362
1362
  /**
1363
1363
  * The accepted key type for `delete()` (PK only). Derived from the model
1364
1364
  * class when available.
1365
1365
  */
1366
- type DeleteKey<C> = [C] extends [AnyModelClass] ? PrimaryKeyOf<C> : Record<string, unknown>;
1366
+ type DeleteKey<C> = [C] extends [AnyModelClass$1] ? PrimaryKeyOf<C> : Record<string, unknown>;
1367
1367
  /**
1368
1368
  * The accepted *explicit* key type for `update()` — the base-table primary key
1369
1369
  * only. DynamoDB `UpdateItem` can target an item solely by its base-table PK
1370
1370
  * (GSI-based updates are impossible), so this is `PrimaryKeyOf<C>`, not
1371
1371
  * `UniqueQueryKeyOf<C>`. Derived from the model class when available.
1372
1372
  */
1373
- type UpdateExplicitKey<C> = [C] extends [AnyModelClass] ? PrimaryKeyOf<C> : Record<string, unknown>;
1373
+ type UpdateExplicitKey<C> = [C] extends [AnyModelClass$1] ? PrimaryKeyOf<C> : Record<string, unknown>;
1374
1374
  /**
1375
1375
  * Options accepted by `list()` (third argument). The projection is supplied as
1376
1376
  * the separate second `select` argument, symmetric with `query()`.
@@ -1969,6 +1969,342 @@ interface DeriveEffect {
1969
1969
  /** The amount to add (for an increment). */
1970
1970
  readonly amount: number;
1971
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
+ /** The sort direction a maintained collection orders by (mirrors the read-side `read.order`). */
2119
+ type CollectionOrderDirection = 'ASC' | 'DESC';
2120
+ /**
2121
+ * Options for the bounded collection a {@link CollectionEffect} maintains: the
2122
+ * target {@link field} that holds the collection, an optional {@link maxItems}
2123
+ * cap (oldest items are evicted past it), and an optional {@link orderBy} source
2124
+ * path the items are ordered by (with an {@link orderDir} direction).
2125
+ */
2126
+ interface CollectionOptions {
2127
+ /** The target attribute that holds the maintained collection. */
2128
+ readonly field: string;
2129
+ /** Cap on the collection size; items past it are evicted (unbounded if omitted). */
2130
+ readonly maxItems?: number;
2131
+ /**
2132
+ * A path-rooted source value the collection is ordered by (e.g. `$.entity.createdAt`).
2133
+ * Wired from the relation's `read.order` (#130 是正2): the field is the source
2134
+ * entity's sort-key leaf (the field the live `hasMany` read orders by via
2135
+ * `ScanIndexForward`), so the materialized collection sorts the SAME way as the
2136
+ * live query. The async drain ({@link import('../cdc/maintenance-drain.js')})
2137
+ * applies the sort + trim; a single synchronous `UpdateExpression` cannot.
2138
+ */
2139
+ readonly orderBy?: EffectPath;
2140
+ /**
2141
+ * The direction {@link orderBy} sorts by — `'DESC'` (newest first, the default a
2142
+ * bare `order: 'DESC'` declares) or `'ASC'`. Present only when {@link orderBy} is.
2143
+ */
2144
+ readonly orderDir?: CollectionOrderDirection;
2145
+ }
2146
+ /**
2147
+ * A **collection** maintenance effect (issue #120): on a {@link MaintainTrigger}
2148
+ * the source's {@link project} is appended as an item into the bounded collection
2149
+ * named by {@link collection} on the target row resolved by {@link targetFactory}
2150
+ * + {@link keys}. Generalizes {@link DeriveEffect} to a bounded list projection.
2151
+ * Consumed by the maintenance graph (#124) and compile injection (#125); #120
2152
+ * defines the type only.
2153
+ */
2154
+ interface CollectionEffect {
2155
+ readonly kind: 'collection';
2156
+ /** The lifecycle event of the source entity that drives the collection write. */
2157
+ readonly trigger: MaintainTrigger;
2158
+ /** Resolves the target entity whose row holds the collection. */
2159
+ readonly targetFactory: () => new (...args: unknown[]) => unknown;
2160
+ /** The key binding of the target row (path-rooted values). */
2161
+ readonly keys: Readonly<Record<string, EffectPath>>;
2162
+ /** The projection map for each collection item: attribute → `w.transform(...)` IR. */
2163
+ readonly project: ProjectionMap;
2164
+ /** The bounded-collection options (`field` / `maxItems` / `orderBy`). */
2165
+ readonly collection: CollectionOptions;
2166
+ /** The path the write travels — synchronous `mutation` tx or async `stream`. */
2167
+ readonly updateMode?: MaintainUpdateMode;
2168
+ /** The consistency the write lands under (default `eventual`). */
2169
+ readonly consistency?: MaintainConsistency;
2170
+ }
2171
+ /**
2172
+ * The aggregation a {@link CounterEffect} maintains (Epic #118 §5.2 counter /
2173
+ * latest preset; issue #141). The function-form IR `count()` / `max(field)` the
2174
+ * `@aggregate` value helpers build, lifted into the maintenance IR:
2175
+ *
2176
+ * - `count` — the cardinality of the matched source rows: a `created` trigger
2177
+ * adds `+1`, a `removed` trigger adds `-1` (the {@link CounterEffect.delta}).
2178
+ * - `max` — the running maximum of a named source `field`. A synchronous
2179
+ * transactional max cannot be expressed as a concurrency-safe single-item
2180
+ * `UpdateExpression` (a conditional `SET` whose guard fails would roll back the
2181
+ * *source* write that legitimately happened), so Phase 1 records the IR but the
2182
+ * compile injection (#141) rejects it for the synchronous `mutation` path —
2183
+ * `max` is the asynchronous stream path (#130, Phase 2). The op is kept here so
2184
+ * the IR is complete and the reject is precise.
2185
+ */
2186
+ type CounterAggregate = {
2187
+ readonly op: 'count';
2188
+ } | {
2189
+ readonly op: 'max';
2190
+ readonly field: string;
2191
+ };
2192
+ /**
2193
+ * A **counter** maintenance effect (Epic #118 §5.2 counter preset; issue #141): on
2194
+ * a {@link MaintainTrigger} a scalar aggregate on a single target row resolved by
2195
+ * {@link targetFactory} + {@link keys} is kept in sync with the source entity's
2196
+ * lifecycle. Where {@link SnapshotEffect} mirrors a projection and
2197
+ * {@link CollectionEffect} appends an item, a counter applies an atomic scalar
2198
+ * `ADD` — the maintenance-pipeline generalization of the self-lifecycle
2199
+ * {@link DeriveEffect} (`w.increment`, #85), but driven by a CROSS-entity
2200
+ * `maintainedOn` trigger rather than the written entity's own lifecycle.
2201
+ *
2202
+ * For `value.op === 'count'` the {@link delta} is the signed amount the trigger
2203
+ * applies (`+1` on `created`, `-1` on `removed`); the maintenance graph (#124)
2204
+ * derives it from the trigger event. Two counter effects targeting the SAME row in
2205
+ * one mutation MERGE (the symmetric-ADD collapse #92), unlike snapshot/collection
2206
+ * which reject a same-row collision — an `ADD` is commutative.
2207
+ *
2208
+ * Consumed by the maintenance graph (#124, `em.aggregates` walk) and the compile
2209
+ * injection (#141, `DerivedMaintainWrite` of `kind: 'counter'`).
2210
+ */
2211
+ interface CounterEffect {
2212
+ readonly kind: 'counter';
2213
+ /** The lifecycle event of the source entity that drives the counter. */
2214
+ readonly trigger: MaintainTrigger;
2215
+ /** Resolves the target entity whose row holds the counter scalar. */
2216
+ readonly targetFactory: () => new (...args: unknown[]) => unknown;
2217
+ /** The key binding of the target row (path-rooted values). */
2218
+ readonly keys: Readonly<Record<string, EffectPath>>;
2219
+ /** The target attribute the aggregate scalar is written to (e.g. `postCount`). */
2220
+ readonly attribute: string;
2221
+ /** The aggregation maintained (`count()` / `max(field)`). */
2222
+ readonly value: CounterAggregate;
2223
+ /**
2224
+ * For `value.op === 'count'`, the signed `ADD` delta this trigger applies
2225
+ * (`+1` created / `-1` removed). Absent for `max` (not realized synchronously).
2226
+ */
2227
+ readonly delta?: number;
2228
+ /** The path the write travels — synchronous `mutation` tx or async `stream`. */
2229
+ readonly updateMode?: MaintainUpdateMode;
2230
+ /** The consistency the write lands under (default `eventual`). */
2231
+ readonly consistency?: MaintainConsistency;
2232
+ }
2233
+ /**
2234
+ * The comparison op a {@link MembershipPredicate} applies to the source value
2235
+ * (Epic #118 pattern 6 / sparse view, issue #133). The host-side drain evaluates
2236
+ * the predicate against the source payload to decide PUT (true) vs DELETE (false):
2237
+ *
2238
+ * - `truthy` / `falsy` — the source value is/ isn't truthy (no `value`);
2239
+ * - `exists` / `notExists` — the source attribute is present / absent (no `value`);
2240
+ * - `eq` / `ne` — the source value equals / does not equal {@link MembershipPredicate.value}.
2241
+ */
2242
+ type MembershipPredicateOp = 'truthy' | 'falsy' | 'exists' | 'notExists' | 'eq' | 'ne';
2243
+ /**
2244
+ * A membership predicate over the source payload (Epic #118 pattern 6 / sparse view,
2245
+ * issue #133). When the predicate holds, the view item is PUT (projected); when it
2246
+ * flips false the view item is DELETED. This is the "述語が flip したら view item を
2247
+ * put/delete" maintenance — distinct from a GSI sparse index (an explicit view row,
2248
+ * conditionally present). The `path` is a payload-rooted source value (`$.entity.*` /
2249
+ * `$.input.*`); `value` is the comparand for `eq` / `ne` (absent for the unary ops).
2250
+ */
2251
+ interface MembershipPredicate {
2252
+ /** The payload-rooted source value the predicate tests (`$.entity.<field>`). */
2253
+ readonly path: EffectPath;
2254
+ /** The comparison op applied to the source value. */
2255
+ readonly op: MembershipPredicateOp;
2256
+ /** The comparand for `eq` / `ne`; absent for the unary ops. */
2257
+ readonly value?: unknown;
2258
+ }
2259
+ /**
2260
+ * A **membership** maintenance effect (Epic #118 pattern 6 / sparse view, issue #133):
2261
+ * on a {@link MaintainTrigger} a {@link predicate} is evaluated over the source
2262
+ * payload, and the view row resolved by {@link targetFactory} + {@link keys} is PUT
2263
+ * (its {@link project}ion) when the predicate holds OR DELETED when it does not. A
2264
+ * source `removed` event always deletes the view row (the source no longer exists).
2265
+ *
2266
+ * ## Why this is a NEW effect (not a snapshot/collection)
2267
+ *
2268
+ * A {@link SnapshotEffect} only ever `SET`s (it cannot remove a row when a predicate
2269
+ * flips false) and a {@link CollectionEffect} only appends/splices a list field —
2270
+ * neither expresses "the WHOLE view row appears/disappears as a predicate flips". So
2271
+ * sparse view is a distinct effect carrying a {@link predicate} and a put-or-delete
2272
+ * realization.
2273
+ *
2274
+ * ## Scope (issue #133): the asynchronous (`stream`) path
2275
+ *
2276
+ * The put-vs-delete branch is decided by evaluating the predicate against the source
2277
+ * image, which the host-side CDC drain ({@link import('../cdc/maintenance-drain.js')})
2278
+ * does naturally (it already branches snapshot SET vs collection read-modify-write). A
2279
+ * SYNCHRONOUS membership write would need the atomic transaction to choose Put-vs-Delete
2280
+ * from a runtime predicate (DynamoDB cannot branch an item kind on a value), which is a
2281
+ * larger runtime change deferred as a follow-up; the compile injection rejects a
2282
+ * `mutation`-path membership effect so it is never silently mis-realized.
2283
+ */
2284
+ interface MembershipEffect {
2285
+ readonly kind: 'membership';
2286
+ /** The lifecycle event of the source entity that drives the membership write. */
2287
+ readonly trigger: MaintainTrigger;
2288
+ /** Resolves the target (view) entity whose row appears / disappears. */
2289
+ readonly targetFactory: () => new (...args: unknown[]) => unknown;
2290
+ /** The key binding of the view row (path-rooted values). */
2291
+ readonly keys: Readonly<Record<string, EffectPath>>;
2292
+ /** The projection PUT onto the view row when the predicate holds. */
2293
+ readonly project: ProjectionMap;
2294
+ /** The membership predicate (PUT when it holds, DELETE when it flips false). */
2295
+ readonly predicate: MembershipPredicate;
2296
+ /** The path the write travels — `stream` only for issue #133 (see header). */
2297
+ readonly updateMode?: MaintainUpdateMode;
2298
+ /** The consistency the write lands under (default `eventual`). */
2299
+ readonly consistency?: MaintainConsistency;
2300
+ }
2301
+ /**
2302
+ * The union of maintenance effects a {@link SnapshotEffect}, {@link CollectionEffect},
2303
+ * {@link CounterEffect}, or {@link MembershipEffect} can be. The shared IR the
2304
+ * maintenance graph (#124) and compile injection (#125 snapshot/collection, #141
2305
+ * counter, #133 membership) consume.
2306
+ */
2307
+ type MaintainEffect = SnapshotEffect | CollectionEffect | CounterEffect | MembershipEffect;
1972
2308
  /**
1973
2309
  * A domain event (proposal §2 `emits`): an outbox `Put` drained to Streams /
1974
2310
  * `src/cdc/`. Derived by #87 — stored opaquely here.
@@ -2077,6 +2413,21 @@ interface WriteRecorder {
2077
2413
  deleteEdge(target: () => new (...args: unknown[]) => unknown, relationProperty: string): EdgeEffect;
2078
2414
  /** Declare a derived / cascading update (§2 `derive`; derived #85). */
2079
2415
  increment(target: () => new (...args: unknown[]) => unknown, keys: Readonly<Record<string, EffectPath>>, attribute: string, amount: number): DeriveEffect;
2416
+ /**
2417
+ * Build a projection transform IR node (issue #120, 関数形): the primary
2418
+ * function-form authoring surface for a maintenance projection map entry. The
2419
+ * initial ops are `identity` (no args — copy the source path through) and
2420
+ * `preview` (one numeric arg — keep a length-bounded preview). There is no
2421
+ * string DSL. The returned {@link ProjectionTransform} is consumed by the
2422
+ * maintenance graph (#124) / compile injection (#125).
2423
+ *
2424
+ * NOTE (issue #120 scope): the escape-hatch recorders that *attach* a
2425
+ * {@link SnapshotEffect} / {@link CollectionEffect} to a lifecycle
2426
+ * (`w.snapshotInto` / `w.captureInto`) and the `LifecycleEffects.maintain`
2427
+ * wiring are **Phase 2** and are intentionally NOT part of this recorder.
2428
+ */
2429
+ transform(path: EffectPath, op: 'identity'): ProjectionTransform;
2430
+ transform(path: EffectPath, op: 'preview', n: number): ProjectionTransform;
2080
2431
  /** Declare a domain event (§2 `emits`; derived #87). */
2081
2432
  event(name: string, payload: Readonly<Record<string, EffectPath>>): EmitEffect;
2082
2433
  /** Declare an idempotency guard (§2 `idempotency`; derived #87). */
@@ -2400,6 +2751,605 @@ declare function mutation(name: string, body: MutationBody): CommandPlan;
2400
2751
  */
2401
2752
  declare const definePlan: typeof mutation;
2402
2753
 
2754
+ /**
2755
+ * `defineView` / `defineProjection` — the maintained **view** and **external
2756
+ * projection** definition DSL (Epic #118 Phase 2, issue #132 child M).
2757
+ *
2758
+ * ## Where this sits in the `define*` family
2759
+ *
2760
+ * This module extends the record-of-definitions pattern of
2761
+ * `src/define/define.ts` (`defineQuery`/`defineList`/… grouped by
2762
+ * `defineQueries`/`defineCommands`) with two new families that declare a single
2763
+ * **view** node → IR:
2764
+ *
2765
+ * - {@link defineView} (`materializedView`) — declares an INDEPENDENT view row
2766
+ * maintained from the lifecycle events of one or more **source** entities. The
2767
+ * view row is its OWN modelled row (a `DDBModel` with its own `keys`), NOT a
2768
+ * field of any source entity — the "特定エンティティのフィールドでない単体行" of
2769
+ * RFC §5.3. RFC §5.3:
2770
+ * ```ts
2771
+ * defineView(UserThreadListModel, {
2772
+ * pattern: 'materializedView',
2773
+ * source: [{ entity: () => PostModel, maintainedOn: ['Post.created'], … }, …],
2774
+ * keys: { userId: '$.entity.userId' },
2775
+ * fields: { … }, // per-source projection of the view row
2776
+ * write: { updateMode: 'stream' },
2777
+ * })
2778
+ * ```
2779
+ * - {@link defineProjection} (`externalProjection`) — declares a stream + idempotency
2780
+ * projection of a source entity into an EXTERNAL sink (BigQuery / S3 / OpenSearch
2781
+ * in production; the CDC emulator in dev/test). RFC §5.3:
2782
+ * ```ts
2783
+ * defineProjection('TestCaseResultBQ', {
2784
+ * pattern: 'externalProjection',
2785
+ * source: () => TestCaseResultModel,
2786
+ * via: 'stream',
2787
+ * idempotencyKey: 'resultId',
2788
+ * })
2789
+ * ```
2790
+ *
2791
+ * `defineViews({...})` / `defineProjections({...})` take a record of these
2792
+ * definition nodes and return the same record (typed), forming the IR the
2793
+ * maintenance graph (#124, extended here as the third maintenance source after
2794
+ * relations and aggregates) and the projection sink consume — exactly the
2795
+ * `defineQueries`/`defineCommands`样式.
2796
+ *
2797
+ * ## Why a view is its own definition (not a relation `@hasMany`)
2798
+ *
2799
+ * A relation/aggregate maintainer (#124) hangs off a MODELLED entity row — its
2800
+ * destination is a `DDBModel` class resolved by the relation owner, and its
2801
+ * single source is the relation's `to`. A **materialized view** is the
2802
+ * complementary case the RFC §5.3 calls out: an independent row fed by SEVERAL
2803
+ * foreign sources (none of which is the view itself). So it cannot be a relation
2804
+ * decorator on a source; it is its own top-level declaration whose destination is
2805
+ * the dedicated **view model** row. That view model is a normal `DDBModel` (so the
2806
+ * maintenance graph / drain / rebuild resolve its key with the SAME machinery they
2807
+ * use for a relation owner — no parallel key system), but it is the owner of NO
2808
+ * relation: the sources point INTO it, declared here rather than on the sources.
2809
+ *
2810
+ * ## Scope boundary (payload-同梱; multi-source as per-source slices)
2811
+ *
2812
+ * Each `source` projects ONLY from its own payload (the same payload-同梱 rule
2813
+ * the relation maintenance graph enforces — RFC §4.A.6 `requiredContext` /
2814
+ * write-time-fetch is a deferred follow-up). A multi-source view is therefore
2815
+ * maintained as a UNION of per-source slices on one view row: `Post.created`
2816
+ * updates the Post-projected `fields`, `Thread.read` updates the Thread-projected
2817
+ * `fields`, each converging the shared view row through the K (#130) stream →
2818
+ * outbox → drain path. A field whose value must combine attributes from more than
2819
+ * one source in a single write (a cross-source join at maintenance time) is the
2820
+ * `requiredContext` case and is out of scope — see the module-level note and the
2821
+ * issue report.
2822
+ */
2823
+
2824
+ type AnyModelClass = abstract new (...args: any[]) => DDBModel;
2825
+ /** A source-entity factory: a model class or a `ModelStatic` wrapper. */
2826
+ type SourceFactory = () => AnyModelClass | ModelStatic<DDBModel>;
2827
+ /** A bare-string identity shorthand or an explicit `w.transform(...)` IR node. */
2828
+ type ProjectionEntry = ProjectionTransform | string;
2829
+ /**
2830
+ * Build a {@link MembershipPredicate} for a sparse view (Epic #118 pattern 6 / #133).
2831
+ * The view row is PUT when the predicate holds and DELETED when it flips false. The
2832
+ * `path` is normalized to a payload-rooted source value (a bare field name becomes
2833
+ * `$.entity.<field>`).
2834
+ *
2835
+ * Named `whenMember` (not `when`) to avoid colliding with the transaction-condition
2836
+ * `when` helper (`src/define/transaction.ts`).
2837
+ *
2838
+ * @example `whenMember('status', 'eq', 'active')` — the view row exists only while
2839
+ * `status === 'active'`.
2840
+ * @example `whenMember('archived', 'falsy')` — the view row exists only while
2841
+ * `archived` is falsy.
2842
+ */
2843
+ declare function whenMember(field: string, op: MembershipPredicateOp, value?: unknown): MembershipPredicate;
2844
+ /**
2845
+ * One source contributing to a maintained view (issue #132). The source entity's
2846
+ * lifecycle events ({@link maintainedOn}) drive the maintenance; its payload is
2847
+ * projected into the view row's {@link fields} (payload-同梱). When the source
2848
+ * maintains an ordered/bounded slice (a collection-shaped view) it declares
2849
+ * {@link collection}; otherwise it maintains a single snapshot slice.
2850
+ */
2851
+ interface ViewSource {
2852
+ /** Resolves the source entity whose lifecycle drives this slice of the view. */
2853
+ readonly entity: SourceFactory;
2854
+ /**
2855
+ * The cross-entity triggers this source fires on (`'<Entity>.<event>'`, the
2856
+ * shared `created`/`updated`/`removed` vocabulary). The entity segment must be
2857
+ * the source entity's logical name (the maintenance graph validates it).
2858
+ */
2859
+ readonly maintainedOn: readonly string[];
2860
+ /**
2861
+ * The view-row key binding for THIS source: view-row key field → a path-rooted
2862
+ * source value (`$.entity.<field>` / `$.input.<field>`). Resolves which view
2863
+ * row this source event maintains. When omitted, the view-level {@link
2864
+ * ViewDefinitionInput.keys} is used (the common case — every source keys the
2865
+ * view row the same way).
2866
+ */
2867
+ readonly keys?: Readonly<Record<string, EffectPath>>;
2868
+ /**
2869
+ * The projection of THIS source's payload into the view row (target attribute →
2870
+ * identity shorthand string / `w.transform(...)`). When omitted, the view-level
2871
+ * {@link ViewDefinitionInput.fields} is used.
2872
+ */
2873
+ readonly fields?: Readonly<Record<string, ProjectionEntry>>;
2874
+ /**
2875
+ * Declares this source maintains a bounded, ordered COLLECTION slice of the view
2876
+ * row (e.g. "latest N posts") rather than a single snapshot. The `field` names
2877
+ * the view-row attribute holding the list; `maxItems` / `order` shape it.
2878
+ */
2879
+ readonly collection?: {
2880
+ readonly field: string;
2881
+ readonly maxItems?: number;
2882
+ readonly order?: 'ASC' | 'DESC';
2883
+ /** The source path the collection orders by + trims on (e.g. `$.entity.createdAt`). */
2884
+ readonly orderBy?: EffectPath;
2885
+ };
2886
+ /**
2887
+ * Sparse-view predicate (Epic #118 pattern 6 / #133): when present, this source
2888
+ * maintains a MEMBERSHIP slice — the view row is PUT (projected) while the predicate
2889
+ * holds and DELETED when it flips false (and always deleted on a source `removed`).
2890
+ * Build it with {@link whenMember} (e.g. `whenMember('status', 'eq', 'active')`). Mutually
2891
+ * exclusive with {@link collection} (a row either appears/disappears or holds a list).
2892
+ */
2893
+ readonly when?: MembershipPredicate;
2894
+ }
2895
+ /** The author-facing input to {@link defineView}. */
2896
+ interface ViewDefinitionInput {
2897
+ /** The maintenance preset — `'materializedView'` (snapshot/collection) or `'sparseView'` (membership). */
2898
+ readonly pattern?: 'materializedView' | 'sparseView';
2899
+ /** One or more sources whose lifecycle events maintain the view row. */
2900
+ readonly source: ViewSource | readonly ViewSource[];
2901
+ /**
2902
+ * The default view-row key binding (view-row key field → path-rooted source
2903
+ * value). A source may override it with its own `keys`. At least one of the
2904
+ * view-level `keys` or every source's `keys` must be present.
2905
+ */
2906
+ readonly keys?: Readonly<Record<string, EffectPath>>;
2907
+ /** The default per-source projection of the view row (source may override). */
2908
+ readonly fields?: Readonly<Record<string, ProjectionEntry>>;
2909
+ /** Maintenance-write declaration shared by the sources (`updateMode` / `consistency`). */
2910
+ readonly write?: {
2911
+ readonly updateMode?: MaintainUpdateMode;
2912
+ readonly consistency?: MaintainConsistency;
2913
+ };
2914
+ }
2915
+ /** Marks an object as a {@link ViewDefinition} IR node. */
2916
+ declare const VIEW_DEFINITION_MARKER: unique symbol;
2917
+ /**
2918
+ * The global registry of declared {@link ViewDefinition}s (issue #132). A
2919
+ * `defineView(...)` registers itself here so the compile-side global maintenance
2920
+ * graph (and the linter) discovers view maintainers WITHOUT the caller threading
2921
+ * the definitions through — the same way `@model` auto-registers an entity in
2922
+ * `MetadataRegistry`. A {@link generation} counter (bumped on every register /
2923
+ * clear) lets a cache keyed off it invalidate when a view is added, so a
2924
+ * newly-declared view is never silently dropped from the compile path.
2925
+ */
2926
+ declare class ViewRegistry {
2927
+ private static readonly views;
2928
+ private static gen;
2929
+ /** Register (or replace, by view class) a declared view. Bumps the generation. */
2930
+ static register(def: ViewDefinition): void;
2931
+ /** Every registered view, in declaration order. */
2932
+ static getAll(): readonly ViewDefinition[];
2933
+ /** The monotonic generation counter (changes whenever the set of views changes). */
2934
+ static get generation(): number;
2935
+ /** Forget every registered view (test reset). Bumps the generation. */
2936
+ static clear(): void;
2937
+ }
2938
+ /**
2939
+ * The lowered per-source maintenance slice of a view — one entry per (source ×
2940
+ * trigger), carrying the resolved source class, the validated triggers, the view
2941
+ * row key binding, and the projection. Consumed by the maintenance graph (#124)
2942
+ * which synthesizes the A-defined `SnapshotEffect` / `CollectionEffect` from it.
2943
+ */
2944
+ interface ViewSourceSlice {
2945
+ /** The resolved source model class (the lifecycle origin / projected payload). */
2946
+ readonly sourceClass: AnyModelClass;
2947
+ /** The validated triggers this slice fires on. */
2948
+ readonly maintainedOn: readonly MaintainTrigger[];
2949
+ /** The view-row key binding (view-row key field → path-rooted source value). */
2950
+ readonly keys: Readonly<Record<string, EffectPath>>;
2951
+ /** The projection map (target attribute → `w.transform(...)` IR). */
2952
+ readonly project: ProjectionMap;
2953
+ /** Bounded/ordered collection options when this slice maintains a list. */
2954
+ readonly collection?: {
2955
+ readonly field: string;
2956
+ readonly maxItems?: number;
2957
+ readonly orderBy?: EffectPath;
2958
+ readonly orderDir?: 'ASC' | 'DESC';
2959
+ };
2960
+ /**
2961
+ * Sparse-view membership predicate (Epic #118 pattern 6 / #133) when this slice
2962
+ * maintains a put/delete membership row. The maintenance graph synthesizes a
2963
+ * `MembershipEffect` (not a snapshot) from it.
2964
+ */
2965
+ readonly predicate?: MembershipPredicate;
2966
+ }
2967
+ /**
2968
+ * The IR for one `defineView` node — an independent materialized-view row
2969
+ * maintained from one or more source entities. The `name` is the view's logical
2970
+ * identity; `viewClass` is the dedicated view model the row lives on (the
2971
+ * maintenance destination); `slices` are the lowered per-source maintenance
2972
+ * contributions the maintenance graph turns into effects.
2973
+ */
2974
+ interface ViewDefinition {
2975
+ readonly [VIEW_DEFINITION_MARKER]: true;
2976
+ /** The view's logical name (e.g. `'UserThreadList'`). */
2977
+ readonly name: string;
2978
+ /** The dedicated view model class the maintained row lives on (the destination). */
2979
+ readonly viewClass: AnyModelClass;
2980
+ /** The maintenance preset (`'materializedView'` or `'sparseView'`). */
2981
+ readonly pattern: 'materializedView' | 'sparseView';
2982
+ /** The lowered per-source maintenance slices. */
2983
+ readonly slices: readonly ViewSourceSlice[];
2984
+ /** The path the maintenance writes travel (`mutation` / `stream`). */
2985
+ readonly updateMode?: MaintainUpdateMode;
2986
+ /** The consistency the maintenance writes land under. */
2987
+ readonly consistency?: MaintainConsistency;
2988
+ }
2989
+ /** Runtime guard: is `value` a {@link ViewDefinition}? */
2990
+ declare function isViewDefinition(value: unknown): value is ViewDefinition;
2991
+ /**
2992
+ * Define a single maintained **view** (`materializedView`, issue #132): an
2993
+ * independent view row (on the dedicated `view` model) maintained from one or more
2994
+ * source entities' lifecycle events, projecting each source's payload (payload-同梱)
2995
+ * into the view row. The returned {@link ViewDefinition} is the IR the maintenance
2996
+ * graph (#124) consumes as a third maintenance source (after relations /
2997
+ * aggregates), so a view row converges through the SAME stream → outbox → drain
2998
+ * path (K, #130).
2999
+ *
3000
+ * @param view The dedicated view model class the maintained row lives on (a normal
3001
+ * `DDBModel` with its own `keys`). Its logical name is the view's `name`.
3002
+ * @param input The view's sources / key binding / projection / write declaration.
3003
+ *
3004
+ * @example
3005
+ * ```ts
3006
+ * const UserThreadList = defineView(UserThreadListModel, {
3007
+ * pattern: 'materializedView',
3008
+ * keys: { userId: '$.entity.userId' },
3009
+ * source: [
3010
+ * { entity: () => PostModel, maintainedOn: ['Post.created'],
3011
+ * fields: { lastPostId: 'postId', lastPostAt: 'createdAt' } },
3012
+ * { entity: () => ThreadModel, maintainedOn: ['Thread.updated'],
3013
+ * fields: { threadTitle: 'title' } },
3014
+ * ],
3015
+ * write: { updateMode: 'stream' },
3016
+ * });
3017
+ * ```
3018
+ */
3019
+ declare function defineView(view: AnyModelClass | ModelStatic<DDBModel>, input: ViewDefinitionInput): ViewDefinition;
3020
+ /** A record of view-name → {@link ViewDefinition}. */
3021
+ type ViewDefinitionMap = Record<string, ViewDefinition>;
3022
+ /**
3023
+ * Group a record of {@link defineView} nodes into the view IR consumed by the
3024
+ * maintenance graph (#124). Validates that every entry is a `defineView` node —
3025
+ * the `defineQueries`/`defineCommands` 様式.
3026
+ */
3027
+ declare function defineViews<const D extends ViewDefinitionMap>(definitions: D): D;
3028
+ /** The author-facing input to {@link defineVersioned}. */
3029
+ interface VersionedDefinitionInput {
3030
+ /** The maintenance preset — `'versioned'`. */
3031
+ readonly pattern?: 'versioned';
3032
+ /** Resolves the source entity whose lifecycle events maintain both rows. */
3033
+ readonly source: SourceFactory;
3034
+ /**
3035
+ * The triggers that maintain a new version (`'<Entity>.<event>'`). Typically
3036
+ * `['Doc.created', 'Doc.updated']` — every persisted revision advances the latest
3037
+ * pointer and appends a history row.
3038
+ */
3039
+ readonly maintainedOn: readonly string[];
3040
+ /**
3041
+ * The LATEST-pointer row's key binding (latest-model key field → path-rooted source
3042
+ * value). It must NOT include the version discriminator — the latest row is
3043
+ * OVERWRITTEN on each revision (one row per source identity, e.g. `{ docId:
3044
+ * '$.entity.docId' }`).
3045
+ */
3046
+ readonly latestKeys: Readonly<Record<string, EffectPath>>;
3047
+ /**
3048
+ * The HISTORY row's key binding (history-model key field → path-rooted source value).
3049
+ * It MUST include the version discriminator so each revision is a NEW row (append-only
3050
+ * history, e.g. `{ docId: '$.entity.docId', version: '$.entity.version' }`).
3051
+ */
3052
+ readonly historyKeys: Readonly<Record<string, EffectPath>>;
3053
+ /** The projection of the source payload into BOTH rows (target attribute → source). */
3054
+ readonly fields: Readonly<Record<string, ProjectionEntry>>;
3055
+ /** Maintenance-write declaration shared by both rows (`updateMode` / `consistency`). */
3056
+ readonly write?: {
3057
+ readonly updateMode?: MaintainUpdateMode;
3058
+ readonly consistency?: MaintainConsistency;
3059
+ };
3060
+ }
3061
+ /**
3062
+ * The pair of {@link ViewDefinition}s a {@link defineVersioned} lowers to — the
3063
+ * latest-pointer snapshot row and the append-only history snapshot row.
3064
+ */
3065
+ interface VersionedDefinition {
3066
+ /** The LATEST-pointer row (a snapshot, overwritten on each revision). */
3067
+ readonly latest: ViewDefinition;
3068
+ /** The append-only HISTORY row (one snapshot row per version). */
3069
+ readonly history: ViewDefinition;
3070
+ }
3071
+ /**
3072
+ * Define a **versioned / latest-pointer** maintained access path (Epic #118 pattern 8,
3073
+ * issue #133): from one source revision, maintain a LATEST-pointer row (overwritten in
3074
+ * place) AND append a HISTORY row (one per version) in a single composed write.
3075
+ *
3076
+ * ## Lowering = composition of two snapshot effects (no new effect)
3077
+ *
3078
+ * Per the design hint "既存の snapshot(latest)+ 別行追記(history)の合成で表現できるか",
3079
+ * `versioned` is **pure composition** of the existing {@link SnapshotEffect}:
3080
+ *
3081
+ * - the LATEST row is a snapshot keyed by the source identity WITHOUT the version
3082
+ * (`latestKeys`) → re-projecting the same key OVERWRITES the pointer in place;
3083
+ * - the HISTORY row is a snapshot keyed by the source identity PLUS the version
3084
+ * (`historyKeys`) → each revision resolves a NEW row, so history is append-only.
3085
+ *
3086
+ * Both are lowered as {@link defineView} (`materializedView`) nodes — the maintenance
3087
+ * graph synthesizes a `SnapshotEffect` per source trigger, and because the two rows
3088
+ * resolve to DIFFERENT destination rows (distinct models / keys) the same-row collision
3089
+ * guard is satisfied and BOTH fire from the one source mutation: the latest pointer and
3090
+ * the history row land together (in one atomic tx for `updateMode: 'mutation'`, or
3091
+ * converge through the stream → drain path for `'stream'`). There is NO new maintenance
3092
+ * primitive — the existing snapshot pipeline (sync #127 / stream #130 / rebuild #131)
3093
+ * carries both.
3094
+ *
3095
+ * @param latest The dedicated LATEST-pointer view model (one row per source identity).
3096
+ * @param history The dedicated HISTORY view model (one row per version).
3097
+ * @param input The source / triggers / key bindings / projection / write declaration.
3098
+ *
3099
+ * @example
3100
+ * ```ts
3101
+ * const DocVersioned = defineVersioned(DocLatestModel, DocVersionModel, {
3102
+ * pattern: 'versioned',
3103
+ * source: () => DocModel,
3104
+ * maintainedOn: ['Doc.created', 'Doc.updated'],
3105
+ * latestKeys: { docId: '$.entity.docId' },
3106
+ * historyKeys: { docId: '$.entity.docId', version: '$.entity.version' },
3107
+ * fields: { docId: 'docId', version: 'version', title: 'title' },
3108
+ * write: { updateMode: 'mutation' },
3109
+ * });
3110
+ * ```
3111
+ */
3112
+ declare function defineVersioned(latest: AnyModelClass | ModelStatic<DDBModel>, history: AnyModelClass | ModelStatic<DDBModel>, input: VersionedDefinitionInput): VersionedDefinition;
3113
+ /** Marks an object as a {@link ProjectionDefinition} IR node. */
3114
+ declare const PROJECTION_DEFINITION_MARKER: unique symbol;
3115
+ /** The transport a projection travels — only `stream` (async) in this phase. */
3116
+ type ProjectionVia = 'stream';
3117
+ /** The author-facing input to {@link defineProjection}. */
3118
+ interface ProjectionDefinitionInput {
3119
+ /** The maintenance preset — `'externalProjection'`. */
3120
+ readonly pattern?: 'externalProjection';
3121
+ /** Resolves the source entity whose lifecycle events are projected to the sink. */
3122
+ readonly source: SourceFactory;
3123
+ /** The transport (`'stream'` — async CDC; the only supported value this phase). */
3124
+ readonly via?: ProjectionVia;
3125
+ /**
3126
+ * The source attribute whose value is the **idempotency key** for at-least-once
3127
+ * de-duplication at the sink (e.g. `'resultId'`). A redelivered source event
3128
+ * with the same key is folded to one sink upsert.
3129
+ */
3130
+ readonly idempotencyKey: string;
3131
+ /**
3132
+ * The lifecycle events projected. Defaults to every event of the source
3133
+ * (`created` / `updated` / `removed`) when omitted.
3134
+ */
3135
+ readonly on?: readonly ('created' | 'updated' | 'removed')[];
3136
+ /**
3137
+ * Optional projection of the source payload into the sink record (target
3138
+ * attribute → identity shorthand / transform). When omitted the whole source
3139
+ * `newImage` is projected.
3140
+ */
3141
+ readonly fields?: Readonly<Record<string, ProjectionEntry>>;
3142
+ }
3143
+ /**
3144
+ * The IR for one `defineProjection` node — a stream + idempotency projection of a
3145
+ * source entity into an external sink. Consumed by the projection sink runtime
3146
+ * ({@link import('../cdc/projection-sink.js')}), which subscribes to
3147
+ * the CDC stream and upserts the projected record into the sink, folding
3148
+ * at-least-once delivery by {@link idempotencyKey}.
3149
+ */
3150
+ interface ProjectionDefinition {
3151
+ readonly [PROJECTION_DEFINITION_MARKER]: true;
3152
+ /** The projection's logical name (e.g. `'TestCaseResultBQ'`). */
3153
+ readonly name: string;
3154
+ /** The maintenance preset (always `'externalProjection'`). */
3155
+ readonly pattern: 'externalProjection';
3156
+ /** The resolved source model class whose events are projected. */
3157
+ readonly sourceClass: AnyModelClass;
3158
+ /** The transport (`'stream'`). */
3159
+ readonly via: ProjectionVia;
3160
+ /** The source attribute used as the idempotency key at the sink. */
3161
+ readonly idempotencyKey: string;
3162
+ /** The lifecycle events projected (defaults to all three). */
3163
+ readonly on: readonly ('created' | 'updated' | 'removed')[];
3164
+ /** The optional projection map (target attribute → transform IR); empty = whole image. */
3165
+ readonly project: ProjectionMap;
3166
+ }
3167
+ /** Runtime guard: is `value` a {@link ProjectionDefinition}? */
3168
+ declare function isProjectionDefinition(value: unknown): value is ProjectionDefinition;
3169
+ /**
3170
+ * Define a single **external projection** (`externalProjection`, issue #132): a
3171
+ * stream + idempotency projection of a source entity into an external sink
3172
+ * (BigQuery / S3 / OpenSearch in production; the CDC emulator + an in-process sink
3173
+ * in dev/test). At-least-once delivery is folded to one sink upsert per genuine
3174
+ * source event by the {@link ProjectionDefinitionInput.idempotencyKey}.
3175
+ *
3176
+ * @example
3177
+ * ```ts
3178
+ * const TestCaseResultBQ = defineProjection('TestCaseResultBQ', {
3179
+ * pattern: 'externalProjection',
3180
+ * source: () => TestCaseResultModel,
3181
+ * via: 'stream',
3182
+ * idempotencyKey: 'resultId',
3183
+ * });
3184
+ * ```
3185
+ */
3186
+ declare function defineProjection(name: string, input: ProjectionDefinitionInput): ProjectionDefinition;
3187
+ /** A record of projection-name → {@link ProjectionDefinition}. */
3188
+ type ProjectionDefinitionMap = Record<string, ProjectionDefinition>;
3189
+ /**
3190
+ * Group a record of {@link defineProjection} nodes into the projection IR consumed
3191
+ * by the projection sink runtime. Validates every entry is a `defineProjection`
3192
+ * node — the `defineQueries`/`defineCommands` 様式.
3193
+ */
3194
+ declare function defineProjections<const D extends ProjectionDefinitionMap>(definitions: D): D;
3195
+
3196
+ /**
3197
+ * Maintenance graph builder (Epic #118 issue #124 — the RFC B-案 core).
3198
+ *
3199
+ * ## What this module is
3200
+ *
3201
+ * A relation may declare a **maintenance** side via its relation options
3202
+ * (issue #121, the B work): `write.maintainedOn` lists the cross-entity
3203
+ * lifecycle triggers (`"<Entity>.<event>"`, e.g. `"Post.created"`) that should
3204
+ * keep the relation's materialized shape in sync, and `projection` declares how
3205
+ * the source entity is projected into that shape. This module walks every
3206
+ * registered model (`MetadataRegistry.getAll()`), reads those relation-side
3207
+ * declarations, and builds an **index**:
3208
+ *
3209
+ * trigger source entity × lifecycle event (created/updated/removed)
3210
+ * → list of maintenance effects to apply
3211
+ *
3212
+ * That index is exactly the question the compile-injection step (#125) asks of
3213
+ * a mutation: "this mutation `created` a `Post` — what must be maintained?". This
3214
+ * module answers *which* effect fires for *which* trigger, and validates that
3215
+ * every declared effect is realizable (round-trips with the read side); it does
3216
+ * **not** inject anything into the compiler — see "Scope" below.
3217
+ *
3218
+ * ## Input is relation-side declarations only (論点1 = c)
3219
+ *
3220
+ * The only input is each relation's `options.write.maintainedOn` +
3221
+ * `options.projection` (the #121 relation options). The `entityWrites` escape
3222
+ * hatch (`w.snapshotInto` / `w.captureInto` attached to a lifecycle) is **Phase
3223
+ * 2** and is deliberately not read here.
3224
+ *
3225
+ * ## Reuses the A (#120) maintenance IR — no second definition
3226
+ *
3227
+ * The maintenance effect each index entry carries is the **A-defined IR** —
3228
+ * {@link SnapshotEffect} / {@link CollectionEffect} / {@link ProjectionTransform}
3229
+ * from `src/define/entity-writes.ts` (also re-exported from the root barrel). This
3230
+ * module *synthesizes* those nodes from the relation-side declaration (it is the
3231
+ * relation-options → IR lowering); it never re-defines the IR shapes.
3232
+ *
3233
+ * ## Round-trip validation (流用 of `assertRelationRoundTrips`)
3234
+ *
3235
+ * Mirroring `src/relation/edge-write.ts`'s build-time round-trip guard, the graph
3236
+ * is verified the moment it is built, and a mis-declaration is a **loud reject**:
3237
+ *
3238
+ * - **Snapshot/collection target key points at a real row.** The relation's
3239
+ * `keyBinding` target fields must resolve to a real access pattern on the
3240
+ * target entity (the row that holds the snapshot/collection), via the *same*
3241
+ * {@link resolveKey} the read planner uses. A binding that covers no partition
3242
+ * would write/read an unreachable row → reject.
3243
+ * - **Projection source attributes exist on the source payload (論点4 = payload
3244
+ * 同梱のみ).** Every projection source path (`$.entity.<field>` /
3245
+ * `$.input.<field>`) must name an attribute that the *source* entity (the
3246
+ * trigger origin) actually carries. There is no fetch / re-projection: an
3247
+ * attribute that is not in the source payload is rejected, not fetched.
3248
+ * - **Unresolved trigger.** A trigger whose entity segment names no registered
3249
+ * model, or whose event is not `created`/`updated`/`removed`, is rejected.
3250
+ * - **Cycle.** A maintenance dependency cycle (entity A's event maintains a
3251
+ * shape on B while B's event maintains a shape on A, transitively) is detected
3252
+ * and rejected — an unbounded maintenance cascade.
3253
+ *
3254
+ * ## Scope (#124 only — NOT #125)
3255
+ *
3256
+ * This module builds and validates the index, and exposes enough structure for
3257
+ * the compile-injection step (#125/#126) to consume it — including the material
3258
+ * to detect that *two* maintainers target the **same** target row (論点2 = b,
3259
+ * "1 mutation × 1 target row = 1 effect"). It does **not** resolve maintainers
3260
+ * into the mutation compiler, derive `DerivedMaintainWrite`s, or emit any
3261
+ * transaction items; `resolveMaintainers` / `deriveMaintainItems` are #125's.
3262
+ */
3263
+
3264
+ type ModelClass = new (...args: unknown[]) => unknown;
3265
+ /**
3266
+ * One maintenance effect, keyed in the graph by its {@link trigger}. Synthesized
3267
+ * from a single relation's maintenance declaration (its `write.maintainedOn` +
3268
+ * `projection`).
3269
+ *
3270
+ * The role mapping follows RFC §5.1 (`relation({ from, to, maintainedOn })`):
3271
+ *
3272
+ * - **source** = the relation's `to` (its `targetFactory()`): the entity whose
3273
+ * lifecycle event fires the maintenance and whose payload is projected. The
3274
+ * trigger's `Entity` segment names this source.
3275
+ * - **owner** = the relation's `from` (the model declaring the relation): the row
3276
+ * that *holds* the maintained snapshot / collection — the **destination** of the
3277
+ * maintenance write.
3278
+ *
3279
+ * So a `Post.created` trigger on `ThreadSummary.latestPosts → Post` maintains the
3280
+ * `ThreadSummary` (owner) row from the `Post` (source) payload. It carries the
3281
+ * A-defined {@link MaintainEffect} IR ({@link SnapshotEffect} /
3282
+ * {@link CollectionEffect}) the maintenance lowers to.
3283
+ */
3284
+ interface MaintainItem {
3285
+ /** The validated `Entity.event` trigger that fires this effect. */
3286
+ readonly trigger: MaintainTrigger;
3287
+ /**
3288
+ * The source entity name (trigger origin — the row whose lifecycle fires this
3289
+ * and whose payload is projected). Equals the relation's target (`to`).
3290
+ */
3291
+ readonly sourceEntity: string;
3292
+ /** The source model class (the relation's `targetFactory()`). */
3293
+ readonly sourceClass: ModelClass;
3294
+ /**
3295
+ * The entity that *declares* the maintaining relation (the relation owner /
3296
+ * `from`) — the row that holds the maintained snapshot / collection (the
3297
+ * maintenance write **destination**).
3298
+ */
3299
+ readonly ownerEntity: string;
3300
+ /** The owner model class (the maintenance destination). */
3301
+ readonly ownerClass: ModelClass;
3302
+ /** The relation property on the owner that declares this maintenance. */
3303
+ readonly relationProperty: string;
3304
+ /**
3305
+ * A stable key for the **destination row** a single trigger writes (the owner
3306
+ * row), derived from the relation's bound owner-key fields. Two
3307
+ * {@link MaintainItem}s firing on the same trigger with the same
3308
+ * {@link destinationRowKey} write the same row — the material #125/#126 uses to
3309
+ * enforce 論点2 = b ("1 mutation × 1 target row = 1 effect"; multiple is a
3310
+ * reject). Built deterministically (sorted), so it is a directly comparable
3311
+ * string.
3312
+ */
3313
+ readonly destinationRowKey: string;
3314
+ /** The A-defined maintenance IR this relation lowers to. */
3315
+ readonly effect: MaintainEffect;
3316
+ }
3317
+ /**
3318
+ * The maintenance graph: the trigger → effects index plus the validation surface.
3319
+ *
3320
+ * `byTrigger` is the core index: a `MaintainTrigger` (`"Post.created"`) maps to
3321
+ * every {@link MaintainItem} that fires on it, in a stable order (owner entity,
3322
+ * then relation property). Empty (no entry) for a trigger nothing maintains on.
3323
+ */
3324
+ interface MaintenanceGraph {
3325
+ /** Every maintenance item, flattened, in deterministic order. */
3326
+ readonly items: readonly MaintainItem[];
3327
+ /** The core index: trigger → the effects it fires. */
3328
+ readonly byTrigger: ReadonlyMap<MaintainTrigger, readonly MaintainItem[]>;
3329
+ /** Look up the effects fired by a trigger (empty array if none). */
3330
+ effectsFor(trigger: MaintainTrigger): readonly MaintainItem[];
3331
+ /**
3332
+ * Items that, under the SAME trigger, write the SAME target row — grouped by
3333
+ * `"<trigger><targetRowKey>"`. Only groups with **more than one** item are
3334
+ * present, so an empty map means no trigger has a multi-maintainer collision.
3335
+ * This is the detection material for 論点2 = b; #124 surfaces it, #125/#126
3336
+ * decide the reject. (Build-time we do NOT reject here — a model may legitimately
3337
+ * declare overlapping shapes that a later phase reconciles; #124's contract is
3338
+ * to make the collision *detectable*.)
3339
+ */
3340
+ readonly multiMaintainerTargets: ReadonlyMap<string, readonly MaintainItem[]>;
3341
+ }
3342
+ /**
3343
+ * Build the {@link MaintenanceGraph} from every registered model's relation-side
3344
+ * maintenance declarations (`MetadataRegistry.getAll()`), validating each as it is
3345
+ * collected (round-trip, unresolved trigger) and the whole graph (cycle) before
3346
+ * returning. Throws a loud, actionable error on any invalid declaration.
3347
+ *
3348
+ * @param registry The models to scan. Defaults to `MetadataRegistry.getAll()`;
3349
+ * an explicit map is accepted for tests / scoped builds.
3350
+ */
3351
+ declare function buildMaintenanceGraph(registry?: ReadonlyMap<Function, EntityMetadata>, views?: readonly ViewDefinition[]): MaintenanceGraph;
3352
+
2403
3353
  /**
2404
3354
  * Serializable static operation specs and manifest (issue #42, Python-bridge
2405
3355
  * Phase 0b).
@@ -2764,6 +3714,101 @@ interface TransactionItemSpec {
2764
3714
  * entity / edge / counter / ConditionCheck item), so those paths are unchanged.
2765
3715
  */
2766
3716
  readonly literalKey?: boolean;
3717
+ /**
3718
+ * A **relation-side maintenance write** (Epic #118, issue #129 — the JSON-SSoT /
3719
+ * Python mirror of the TS in-process `renderMaintainWriteItem`). Present ONLY on an
3720
+ * `Update` item that materializes a maintained access path: a projected
3721
+ * {@link DerivedMaintainWrite} of a just-written source row into a SEPARATE owner
3722
+ * (destination) row, in the SAME atomic transaction as the source write.
3723
+ *
3724
+ * The item's {@link keyCondition} carries the owner row's key templates (bound from
3725
+ * the source payload — `{sourceInputField}`), so the same key derivation / collapse
3726
+ * signature the other `Update` items use applies unchanged. This `maintain` payload
3727
+ * carries the projection transform IR (`identity` / `preview`) the runtimes apply
3728
+ * IDENTICALLY when rendering the `SET … = :v` (snapshot) / `list_append(…)`
3729
+ * (collection) `UpdateExpression`, so the maintained row is byte-consistent across
3730
+ * TS and Python. The `changes` / `add` fields are NOT used when this is present.
3731
+ */
3732
+ readonly maintain?: MaintainItemSpec;
3733
+ }
3734
+ /**
3735
+ * The transform op applied to one projected maintenance attribute (Epic #118).
3736
+ * Mirrors `ProjectionTransformOp` from `src/define/entity-writes.ts`: `identity`
3737
+ * copies the source value through unchanged; `preview` keeps the first `n`
3738
+ * characters of (the string form of) the source value. The runtimes apply this
3739
+ * IDENTICALLY so the maintained row is byte-consistent.
3740
+ */
3741
+ type MaintainProjectionOp = 'identity' | 'preview';
3742
+ /**
3743
+ * One projected maintenance attribute: the source value is read from the mutation
3744
+ * input field {@link inputField} (payload 同梱 — the just-written source row image),
3745
+ * then run through {@link op} with {@link args} (e.g. `preview`'s length bound).
3746
+ */
3747
+ interface MaintainProjectionEntry {
3748
+ /** The transform op applied to the source value (`identity` / `preview`). */
3749
+ readonly op: MaintainProjectionOp;
3750
+ /** The op's positional arguments (`[n]` for `preview`; empty for `identity`). */
3751
+ readonly args: readonly unknown[];
3752
+ /** The mutation-input field the source value is read from (`{inputField}`). */
3753
+ readonly inputField: string;
3754
+ }
3755
+ /**
3756
+ * The bounded-collection options a `collection` maintenance write carries. Phase 1
3757
+ * is **append-only**: `maxItems` / `orderBy` are recorded for the future async trim
3758
+ * (#130) but NOT applied synchronously — a single `UpdateExpression` cannot
3759
+ * read-modify-write a bounded/ordered list.
3760
+ */
3761
+ interface MaintainCollectionSpec {
3762
+ /** The target attribute that holds the maintained collection. */
3763
+ readonly field: string;
3764
+ /** Cap on the collection size (recorded for #130; not trimmed in Phase 1). */
3765
+ readonly maxItems?: number;
3766
+ /** The mutation-input field the items are ordered by (recorded for #130). */
3767
+ readonly orderBy?: string;
3768
+ /** The direction `orderBy` sorts by, from the relation's `read.order` (recorded for #130). */
3769
+ readonly orderDir?: 'ASC' | 'DESC';
3770
+ }
3771
+ /**
3772
+ * The maintenance payload an `Update` {@link TransactionItemSpec} carries when it
3773
+ * materializes a maintained access path (Epic #118 / #129). A `snapshot` mirrors
3774
+ * the projection onto the owner row via `SET`; a `collection` appends the projection
3775
+ * as an item into the bounded list named by {@link collection} via `list_append`.
3776
+ */
3777
+ interface MaintainItemSpec {
3778
+ /**
3779
+ * Whether the write mirrors a single row (`snapshot`), appends a collection item
3780
+ * (`collection`), or applies a scalar `ADD` counter (`counter`, Epic #118 / #141).
3781
+ */
3782
+ readonly kind: 'snapshot' | 'collection' | 'counter';
3783
+ /** The relation property / `@aggregate` field that declared this maintenance (documentary). */
3784
+ readonly relationProperty: string;
3785
+ /** The trigger (`<sourceLogicalName>.<event>`) that fired this maintenance. */
3786
+ readonly trigger: string;
3787
+ /** target attribute → its projection transform (source field + op + args). Empty for `counter`. */
3788
+ readonly projection: Readonly<Record<string, MaintainProjectionEntry>>;
3789
+ /** The bounded-collection options; present only for `kind: 'collection'`. */
3790
+ readonly collection?: MaintainCollectionSpec;
3791
+ /**
3792
+ * The scalar counter `ADD`; present only for `kind: 'counter'` (#141). The runtimes
3793
+ * render `ADD #attr :delta` — an atomic, concurrency-safe increment that MERGES with
3794
+ * a same-row counter ADD (#92), the maintenance-pipeline analogue of the
3795
+ * self-lifecycle derived counter's `add` slot. `delta` is a numeric template string
3796
+ * (the compile-time constant `+1` created / `-1` removed).
3797
+ */
3798
+ readonly counter?: MaintainCounterSpec;
3799
+ }
3800
+ /**
3801
+ * The scalar counter a `counter` maintenance write applies (Epic #118 / #141): an
3802
+ * atomic `ADD #attribute :delta`. `delta` is a literal numeric template string (the
3803
+ * compile-time constant the trigger fixed — `"1"` created / `"-1"` removed); the
3804
+ * runtimes serialize it as a number. Only `count()` is realized synchronously
3805
+ * (`max(field)` is rejected for the synchronous path — #130 / Phase 2).
3806
+ */
3807
+ interface MaintainCounterSpec {
3808
+ /** The target attribute the counter increments (e.g. `postCount`). */
3809
+ readonly attribute: string;
3810
+ /** The signed `ADD` delta as a numeric template string (`"1"` / `"-1"`). */
3811
+ readonly delta: string;
2767
3812
  }
2768
3813
  /** A declarative transaction definition's full execution spec. */
2769
3814
  interface TransactionSpec {
@@ -3058,48 +4103,6 @@ interface BridgeBundle {
3058
4103
  readonly operations: OperationsDocument;
3059
4104
  }
3060
4105
 
3061
- /**
3062
- * Single-fragment mutation → command-op compiler (issue #83, Epic #80; proposal
3063
- * `docs/mutation-command-derivation.md` §3).
3064
- *
3065
- * A {@link CommandPlan} (a `mutation(...)` result) is the internal IR: a **list**
3066
- * of write fragments to compose atomically. This module compiles the
3067
- * **single-fragment** case end-to-end into the **same** {@link ContractMethodOp}
3068
- * the #58 contract recorder produces, so the existing serializer
3069
- * (`opToDefinition` → `buildCommandSpec`, `src/spec/contracts.ts` /
3070
- * `src/spec/operations.ts`) and the existing runtimes consume it with no new
3071
- * write substrate. The IR is already a list, so the **N-fragment atomic merge is
3072
- * #90** — this module rejects N>1 with a clear pointer rather than half-merging.
3073
- *
3074
- * ## Intent → base op (proposal §3, "single fragment compile")
3075
- *
3076
- * | intent | base op |
3077
- * |----------|-------------------------------------------|
3078
- * | `create` | `PutItem` (`attribute_not_exists(PK)`) |
3079
- * | `update` | `UpdateItem` (key + changed fields) |
3080
- * | `remove` | `DeleteItem` (key) |
3081
- *
3082
- * ## `use:` resolution (the resolved fork — proposal §3, pinned spec correction)
3083
- *
3084
- * The fragment's **intent** selects the lifecycle; `use:` is **optional**:
3085
- *
3086
- * - `use:` present → adopt that {@link EntityWritesDefinition} (the **whole**
3087
- * `writes` set; never a single lifecycle).
3088
- * - `use:` omitted → default to the **target model's own** `entityWrites` save
3089
- * contract ({@link getEntityWrites}).
3090
- * - the resolved save contract (custom or default) has **no** entry for the
3091
- * intent's lifecycle, **or the target declares no `writes` at all** → the
3092
- * **trivial base op** for the intent (create → `Put` `attribute_not_exists(PK)`,
3093
- * update → `Update`, remove → `Delete`) against the plain model. So
3094
- * `{ create: () => PostModel, key, input }` works on a bare model.
3095
- *
3096
- * Either way #83 emits the **same** base op — the resolved
3097
- * {@link LifecycleContract} only matters as the **per-fragment hook** the
3098
- * effect-derivation issues (#84–#87) read: the resolved lifecycle's
3099
- * {@link LifecycleContract.effects} are exposed on {@link CompiledFragment} and
3100
- * consumed by NONE of #83 (the empty hook).
3101
- */
3102
-
3103
4106
  /**
3104
4107
  * A **referential-integrity assertion** derived from a `requires`
3105
4108
  * ({@link RequiresEffect}) effect (issue #84; proposal §2/§3). It is the
@@ -3193,6 +4196,122 @@ interface DerivedUpdate {
3193
4196
  /** The signed delta to add (e.g. `+1` create, `-1` remove). */
3194
4197
  readonly amount: number;
3195
4198
  }
4199
+ /**
4200
+ * One **derived maintenance write** — the projection of a just-written source row
4201
+ * into a *separate* target (owner) row, derived from one maintenance-graph
4202
+ * {@link MaintainItem} (issue #125; the D/#124 → compile lowering). It generalizes
4203
+ * {@link DerivedUpdate}: where a `derive` is one scalar `ADD`, a maintenance write
4204
+ * mirrors a whole projection — a single-row {@link SnapshotEffect} (`kind:
4205
+ * 'snapshot'`) or an item appended into a bounded collection ({@link
4206
+ * CollectionEffect}, `kind: 'collection'`).
4207
+ *
4208
+ * The target row's key is bound from the source payload (the relation's owner-key
4209
+ * fields, filled from `$.entity.<sourceField>` → the fragment's `<sourceField>`
4210
+ * input param), and each projected attribute is a {@link ProjectionTransform} IR
4211
+ * node (A/#120 関数形 — `identity` / `preview`) resolved to the input param it reads.
4212
+ * #125 produces this structure; the runtime realization (the actual `Put` / `Update`
4213
+ * item and its placement in the atomic `TransactWriteItems`) is #127.
4214
+ */
4215
+ interface DerivedMaintainWrite {
4216
+ /** The maintained (destination / owner) entity whose row holds the snapshot / collection. */
4217
+ readonly entity: EntityRef;
4218
+ /** The relation property on the owner that declared this maintenance (documentary). */
4219
+ readonly relationProperty: string;
4220
+ /** The trigger (`<sourceLogicalName>.<event>`) that fired this maintenance. */
4221
+ readonly trigger: MaintainTrigger;
4222
+ /**
4223
+ * The maintenance delivery mode (Epic #118 §5.1 `updateMode`):
4224
+ *
4225
+ * - `'mutation'` (synchronous, the default) — the write composes into the SAME
4226
+ * atomic `TransactWriteItems` as the source write (#127); a snapshot `SET`,
4227
+ * a bounded-collection `list_append` (Phase 1 append-only), or a counter `ADD`.
4228
+ * - `'stream'` (asynchronous, #130) — the write is NOT placed in the source tx.
4229
+ * Instead a **maintenance-outbox** row is emitted atomically with the source
4230
+ * write ({@link DerivedMaintainOutbox}) and a CDC drain
4231
+ * ({@link import('../cdc/maintenance-drain.js')}) applies the owner-row write
4232
+ * asynchronously (at-least-once, per-shard ordered). The async path realizes
4233
+ * the operations a single synchronous `UpdateExpression` cannot —
4234
+ * collection `maxItems` trim / `removed`-driven splice and a running `max`.
4235
+ *
4236
+ * A `stream` write is NOT composed into the synchronous maintain items; the
4237
+ * compiler diverts it to a {@link DerivedMaintainOutbox} instead (see
4238
+ * {@link splitMaintainByMode}). The field is carried here so a single resolved
4239
+ * structure describes both modes (the outbox derivation reads it back).
4240
+ */
4241
+ readonly updateMode: 'mutation' | 'stream';
4242
+ /**
4243
+ * Whether the write mirrors a single row (`snapshot`), appends a collection item
4244
+ * (`collection`), applies a scalar `ADD` counter (`counter`, #141), or puts/deletes a
4245
+ * predicate-gated view row (`membership`, #133 sparse view — stream-only).
4246
+ */
4247
+ readonly kind: 'snapshot' | 'collection' | 'counter' | 'membership';
4248
+ /**
4249
+ * The destination row's key binding: target key field → the mutation-input param
4250
+ * that supplies it (resolved from the effect's `$.entity.<sourceField>` paths —
4251
+ * the source row image, payload 同梱). Every owner-key field is bound.
4252
+ */
4253
+ readonly keyBinding: Readonly<Record<string, string>>;
4254
+ /**
4255
+ * The projection: target attribute → `{ op, args, inputField }`, where `inputField`
4256
+ * is the mutation-input param the source value is read from and `op` / `args` are
4257
+ * the A/#120 {@link ProjectionTransform} realization (`identity` copies through,
4258
+ * `preview` keeps the first `n` chars). Empty for a `counter` (a scalar `ADD`
4259
+ * projects no row attributes).
4260
+ */
4261
+ readonly projection: Readonly<Record<string, {
4262
+ readonly op: ProjectionTransform['op'];
4263
+ readonly args: readonly unknown[];
4264
+ readonly inputField: string;
4265
+ }>>;
4266
+ /**
4267
+ * For a `collection` write, the bounded-collection options (the target `field`,
4268
+ * the optional `maxItems` cap, and the optional `orderBy` input param); absent for
4269
+ * a `snapshot` / `counter`.
4270
+ */
4271
+ readonly collection?: {
4272
+ readonly field: string;
4273
+ readonly maxItems?: number;
4274
+ readonly orderBy?: string;
4275
+ readonly orderDir?: 'ASC' | 'DESC';
4276
+ };
4277
+ /**
4278
+ * For a `counter` write, the scalar aggregate applied to the owner attribute:
4279
+ *
4280
+ * - `op: 'count'` (#141) — a synchronous-capable atomic `ADD #attr :delta` (the
4281
+ * signed `delta` is `+1` created / `-1` removed). Commutative, so two count
4282
+ * writes on the SAME row MERGE (the #92 symmetric-counter collapse).
4283
+ * - `op: 'max'` (#130 — `value: max(field)`) — a running maximum of the source
4284
+ * `field`. This needs a conditional `SET` (`#a = :v` guarded by
4285
+ * `attribute_not_exists(#a) OR #a < :v`); a failed guard would roll the SOURCE
4286
+ * write back if done synchronously, so a `max` counter is **stream-only** —
4287
+ * applied by the CDC drain ({@link import('../cdc/maintenance-drain.js')}).
4288
+ *
4289
+ * Absent for a `snapshot` / `collection`.
4290
+ */
4291
+ readonly counter?: {
4292
+ readonly attribute: string;
4293
+ readonly op: 'count';
4294
+ readonly delta: number;
4295
+ } | {
4296
+ readonly attribute: string;
4297
+ readonly op: 'max';
4298
+ readonly field: string;
4299
+ };
4300
+ /**
4301
+ * For a `membership` write (#133 sparse view), the predicate that gates the view row:
4302
+ * PUT (its projection) when it holds, DELETE when it flips false. `inputField` is the
4303
+ * mutation-input param the predicate reads the source value from (payload 同梱);
4304
+ * `op` / `value` are the {@link import('../define/entity-writes.js').MembershipPredicate}
4305
+ * realization. Membership is **stream-only** (the put-vs-delete branch is decided by
4306
+ * the host-side drain), so this is only ever carried on a `stream` write. Absent for
4307
+ * a `snapshot` / `collection` / `counter`.
4308
+ */
4309
+ readonly membership?: {
4310
+ readonly op: MembershipPredicateOp;
4311
+ readonly inputField: string;
4312
+ readonly value?: unknown;
4313
+ };
4314
+ }
3196
4315
  /**
3197
4316
  * A **derived uniqueness guard** — the marker-row `Put` (and, on a unique-field
3198
4317
  * change, the old-guard `Delete` + new-guard `Put` swap) that enforces an
@@ -3304,6 +4423,43 @@ interface DerivedIdempotencyGuard {
3304
4423
  /** The guard marker-row `Put` (one item; `attribute_not_exists`, `literalKey`). */
3305
4424
  readonly items: readonly TransactionItemSpec[];
3306
4425
  }
4426
+ /**
4427
+ * A **derived maintenance-outbox event** (issue #130) — the transactional-outbox
4428
+ * `Put` ({@link TransactionItemSpec}) that records a `updateMode: 'stream'`
4429
+ * maintainer's intent ATOMICALLY with the source write, so the `src/cdc/` drain can
4430
+ * apply the owner-row write asynchronously (at-least-once, per-shard ordered).
4431
+ *
4432
+ * ## What the row carries (dynamic values only; the drain re-derives the projection)
4433
+ *
4434
+ * The row's `PK`/`SK` are the deterministic `OUTBOX#MAINT#…` marker (keyed by owner
4435
+ * entity + relation property + the SOURCE row's primary-key input fields). Its
4436
+ * stored item carries:
4437
+ *
4438
+ * - the routing metadata literals — {@link MAINT_OUTBOX_OWNER_ATTR} (owner entity
4439
+ * name), {@link MAINT_OUTBOX_RELATION_ATTR} (the relation property), and
4440
+ * {@link MAINT_OUTBOX_TRIGGER_ATTR} (the `<source>.<event>` trigger) — so the drain
4441
+ * can look the resolved maintainer up in its own maintenance graph;
4442
+ * - one `{inputField}` token per source value the drain needs (every projection
4443
+ * `inputField`, the `orderBy` field, the `max` field, and every owner-key field),
4444
+ * rendered from the shared method params exactly as the #87 outbox payload — so the
4445
+ * row's `newImage` carries the just-written SOURCE row values the drain reprojects.
4446
+ *
4447
+ * The projection transform IR (`identity` / `preview`) is NOT stored in the row (it
4448
+ * would need braces the template renderer reserves for `{token}`s); the drain
4449
+ * recovers it from its maintenance graph by `(owner, relationProperty, trigger)`.
4450
+ * Because the row is a normal table item, real DynamoDB Streams captures it and a
4451
+ * production consumer drains it identically — the emulator is dev/test only.
4452
+ */
4453
+ interface DerivedMaintainOutbox {
4454
+ /** The maintained (owner) entity whose row the drain will write (documentary). */
4455
+ readonly ownerEntity: string;
4456
+ /** The relation property / `@aggregate` field that declared this maintenance. */
4457
+ readonly relationProperty: string;
4458
+ /** The trigger (`<sourceLogicalName>.<event>`) that fired this maintenance. */
4459
+ readonly trigger: MaintainTrigger;
4460
+ /** The maintenance-outbox marker-row `Put` (one item; `literalKey`, `MARKER_ROW_ENTITY`). */
4461
+ readonly items: readonly TransactionItemSpec[];
4462
+ }
3307
4463
  /**
3308
4464
  * The result of compiling one fragment: the {@link ContractMethodOp} the existing
3309
4465
  * serializer / runtime consume, the resolved contract Key fields (the model's
@@ -3385,6 +4541,38 @@ interface CompiledFragment {
3385
4541
  * guard and rolls the WHOLE transaction back — so no effect is double-applied.
3386
4542
  */
3387
4543
  readonly idempotencyGuard?: DerivedIdempotencyGuard;
4544
+ /**
4545
+ * The maintenance writes derived from the maintenance graph (issue #125; the
4546
+ * D/#124 → compile lowering): one {@link DerivedMaintainWrite} per relation that
4547
+ * declared `write.maintainedOn` for this fragment's lifecycle event (a projected
4548
+ * snapshot / collection on a SEPARATE target row). Present (non-empty) only when
4549
+ * the fragment's trigger fires at least one maintainer; absent otherwise (no
4550
+ * regression — a fragment that maintains nothing compiles exactly as before).
4551
+ *
4552
+ * SCOPE (#125): each entry is the runtime-neutral structure the runtime (#127)
4553
+ * realizes into the atomic `TransactWriteItems`; #125 derives it and rejects a
4554
+ * same-target collision (論点2 = b). The cross-fragment AGGREGATION and the
4555
+ * one-atomic-tx execution wiring are #127 — not done here.
4556
+ *
4557
+ * SCOPE (#130): this carries ONLY the synchronous (`updateMode: 'mutation'`)
4558
+ * maintainers. A `updateMode: 'stream'` maintainer is diverted to
4559
+ * {@link maintainOutbox} instead (it is applied asynchronously by the CDC drain),
4560
+ * so it never appears here.
4561
+ */
4562
+ readonly maintainWrites?: readonly DerivedMaintainWrite[];
4563
+ /**
4564
+ * The maintenance-outbox events for this fragment's `updateMode: 'stream'`
4565
+ * maintainers (issue #130): one {@link DerivedMaintainOutbox} per stream maintainer
4566
+ * the fragment's trigger fires. Each is a transactional-outbox `Put` composed into
4567
+ * the source write's atomic `TransactWriteItems` — recording the maintenance intent
4568
+ * ATOMICALLY with the source row — which the `src/cdc/` drain
4569
+ * ({@link import('../cdc/maintenance-drain.js')}) selects (`PK begins_with
4570
+ * 'OUTBOX#MAINT#'`) and applies asynchronously (at-least-once, per-shard ordered).
4571
+ * Present (non-empty) only when the fragment fires at least one stream maintainer;
4572
+ * absent otherwise (no regression — a fragment with no stream maintainer compiles
4573
+ * exactly as before).
4574
+ */
4575
+ readonly maintainOutbox?: readonly DerivedMaintainOutbox[];
3388
4576
  }
3389
4577
  /**
3390
4578
  * Resolve the {@link LifecycleContract} a fragment adopts for its intent:
@@ -3425,8 +4613,12 @@ type EntityRefResolver = (ref: MutationEntityRef, consumerIndex: number, consume
3425
4613
  * @param resolveEntityRef Resolver for a `$.entity[i].field` cross-fragment
3426
4614
  * reference (multi-fragment compile), or `null` for a single-fragment compile
3427
4615
  * (where such a reference is a hard error).
4616
+ * @param maintenanceGraph The {@link MaintenanceGraph} the #125 maintain injection
4617
+ * queries for this fragment's trigger ({@link resolveMaintainers}). Defaults to a
4618
+ * lazily-built, cached graph over the global registry; an explicit graph is
4619
+ * accepted for scoped tests.
3428
4620
  */
3429
- declare function compileFragment(fragment: MutationFragment, index?: number, resolveEntityRef?: EntityRefResolver | null): CompiledFragment;
4621
+ declare function compileFragment(fragment: MutationFragment, index?: number, resolveEntityRef?: EntityRefResolver | null, maintenanceGraph?: MaintenanceGraph): CompiledFragment;
3430
4622
  /**
3431
4623
  * Compile a single-fragment {@link CommandPlan} into its {@link CompiledFragment}.
3432
4624
  * The N-fragment atomic merge is **#90** ({@link compileMutationPlan}); this entry
@@ -4098,6 +5290,35 @@ interface CommandMethodSpec<TKey, TParams, TResult> {
4098
5290
  * the guard and rolls the WHOLE write back — no effect is double-applied.
4099
5291
  */
4100
5292
  readonly idempotencyGuard?: DerivedIdempotencyGuard;
5293
+ /**
5294
+ * The maintenance writes derived from the relation-side `write.maintainedOn`
5295
+ * declarations (issue #125; D/#124 → compile lowering), flattened across every
5296
+ * fragment in declaration order. Present (non-empty) only when a fragment's
5297
+ * lifecycle event (`Post.created` etc.) fires at least one maintainer; absent
5298
+ * otherwise (no regression — a maintainer-free mutation is byte-identical to its
5299
+ * pre-#127 form). Each {@link DerivedMaintainWrite} projects the just-written
5300
+ * source row into a SEPARATE owner (destination) row — a single-row snapshot
5301
+ * (`kind: 'snapshot'`) or an item appended into a bounded collection (`kind:
5302
+ * 'collection'`). Their presence (like {@link conditionChecks}) **promotes** an
5303
+ * otherwise single-op method to a transaction: the runtime (#127) realizes each
5304
+ * into the method's atomic `TransactWriteItems`, so the maintained row moves
5305
+ * atomically with the source write (a failure rolls the WHOLE set back). #127 is
5306
+ * the synchronous (`updateMode: 'mutation'`) path only; a `updateMode: 'stream'`
5307
+ * maintainer is carried in {@link maintainOutbox} instead (it is applied
5308
+ * asynchronously by the CDC drain), never in this synchronous slot.
5309
+ */
5310
+ readonly maintainWrites?: readonly DerivedMaintainWrite[];
5311
+ /**
5312
+ * The maintenance-outbox events for `updateMode: 'stream'` maintainers (issue
5313
+ * #130): one {@link DerivedMaintainOutbox} per stream maintainer fired across the
5314
+ * method's fragments. Each is a transactional-outbox `Put` the runtime composes
5315
+ * into the method's atomic `TransactWriteItems` — recording the maintenance intent
5316
+ * ATOMICALLY with the source write — which the `src/cdc/` drain
5317
+ * ({@link import('../cdc/maintenance-drain.js')}) applies asynchronously
5318
+ * (at-least-once, per-shard ordered). Present (non-empty) only when the method fires
5319
+ * at least one stream maintainer; absent otherwise (no regression).
5320
+ */
5321
+ readonly maintainOutbox?: readonly DerivedMaintainOutbox[];
4101
5322
  /** @internal Phantom carrier retaining the formal `CommandMethod` type. */
4102
5323
  readonly __signature?: CommandMethod<TKey, TParams, TResult>;
4103
5324
  }
@@ -4698,6 +5919,34 @@ interface ExecutableCommandContract {
4698
5919
  * Absent on a mutation with no `idempotency` / a #64 hand-written method.
4699
5920
  */
4700
5921
  readonly idempotencyGuard?: DerivedIdempotencyGuard;
5922
+ /**
5923
+ * The maintenance writes derived from the relation-side `write.maintainedOn`
5924
+ * declarations (issue #125 → #127; the D/#124 → compile lowering). When
5925
+ * present (non-empty), a **single-key** call composes each maintenance write
5926
+ * — a projected snapshot (`SET`) or a bounded-collection append
5927
+ * (`SET … = list_append(…)`) on a SEPARATE owner row — into the same atomic
5928
+ * `TransactWriteItems` as the source entity write (and every other effect),
5929
+ * so the maintained row moves ATOMICALLY with the source: a failure on any
5930
+ * item rolls the WHOLE transaction back (the snapshot / collection never
5931
+ * partially persists). The owner row is upserted (a bare `UpdateItem`
5932
+ * creates it if absent — the additive maintenance semantics). This is the
5933
+ * synchronous (`updateMode: 'mutation'`) path only; the async stream path is
5934
+ * #130 (loud-rejected at compile time). Absent on a mutation that fires no
5935
+ * maintainer / a #64 hand-written method.
5936
+ */
5937
+ readonly maintainWrites?: readonly DerivedMaintainWrite[];
5938
+ /**
5939
+ * The maintenance-outbox events for `updateMode: 'stream'` maintainers (issue
5940
+ * #130). Each is a transactional-outbox `Put` (the same `literalKey` marker
5941
+ * mechanism as #87's outbox) composed into the method's atomic
5942
+ * `TransactWriteItems` — recording the maintenance intent ATOMICALLY with the
5943
+ * source write. The `src/cdc/` drain selects these (`PK begins_with
5944
+ * 'OUTBOX#MAINT#'`) and applies the owner-row write asynchronously (so a
5945
+ * snapshot SET / collection append+trim / counter ADD / running-max conditional
5946
+ * SET happens off the write path, at-least-once, per-shard ordered). Absent on a
5947
+ * mutation with no stream maintainer.
5948
+ */
5949
+ readonly maintainOutbox?: readonly DerivedMaintainOutbox[];
4701
5950
  /**
4702
5951
  * The return projection of a `mutation`-derived method (issue #83). When
4703
5952
  * present, a **single-key** call performs the write then issues a
@@ -5169,6 +6418,236 @@ declare function key<T extends Record<string, unknown>>(builder: (c: {
5169
6418
  readonly [K in keyof T]-?: Column<T[K], T>;
5170
6419
  }) => KeyStructure): KeyDefinitionMarker<T>;
5171
6420
 
6421
+ type DynamoType = 'S' | 'N' | 'BOOL' | 'B' | 'SS' | 'NS' | 'L' | 'M';
6422
+ interface FieldOptions {
6423
+ format?: 'datetime' | 'date';
6424
+ readonly?: boolean;
6425
+ serialize?: (value: unknown) => unknown;
6426
+ deserialize?: (value: unknown) => unknown;
6427
+ }
6428
+ interface FieldMetadata {
6429
+ propertyName: string;
6430
+ dynamoType: DynamoType;
6431
+ options?: FieldOptions;
6432
+ /**
6433
+ * For a `@literal(...)` field — a `string`-stored field whose value is one of a
6434
+ * known, finite set — the allowed literal values, in declaration order (issue
6435
+ * #75). Recorded purely as model metadata so a Contract param bound into the
6436
+ * field can recover the precise `literal` param-kind from its bind position
6437
+ * (`src/spec/contracts.ts`), serializing as `{ type: 'literal', literals: [...] }`
6438
+ * instead of the imprecise `'string'`. JSON-safe by construction (a non-empty
6439
+ * array of strings). `undefined` for every other field decorator (`@string`,
6440
+ * `@number`, …), so plain fields are byte-for-byte unchanged (backward compat).
6441
+ */
6442
+ literals?: readonly string[];
6443
+ }
6444
+
6445
+ interface KeyDefinition {
6446
+ /** The canonical structured key (PK/SK segment lists). */
6447
+ segmented: SegmentedKey;
6448
+ inputFieldNames: string[];
6449
+ }
6450
+ interface GsiDefinition {
6451
+ indexName: string;
6452
+ /** The canonical structured key (PK/SK segment lists). */
6453
+ segmented: SegmentedKey;
6454
+ inputFieldNames: string[];
6455
+ unique: boolean;
6456
+ }
6457
+ interface RelationLimitOptions {
6458
+ default: number;
6459
+ max: number;
6460
+ }
6461
+ /**
6462
+ * The named maintenance pattern a relation participates in (Epic #118 §5.1).
6463
+ *
6464
+ * `pattern` is the *preset* selector: omitting it leaves the relation a plain
6465
+ * read-only navigation (the historical `hasMany`/`belongsTo`/`hasOne`
6466
+ * behaviour), exactly backward compatible. When present it names the AWS
6467
+ * design pattern the relation lowers to — the lowering itself (maintenance
6468
+ * graph / compile injection) is out of scope for issue #121 and lands in
6469
+ * #124/#125; here the value is recorded purely as declaration metadata.
6470
+ *
6471
+ * Typed as a `string` union of the known presets but kept open-ended via the
6472
+ * `(string & {})` tail so a future preset can be authored before this union is
6473
+ * widened, without a breaking change to callers.
6474
+ */
6475
+ type RelationPattern = 'samePartition' | 'edge' | 'dualEdge' | 'counter' | 'embeddedSnapshot' | 'materializedView' | 'sparseView' | 'versioned' | 'externalProjection' | (string & {});
6476
+ /**
6477
+ * Consistency boundary a maintained write is declared against (Epic #118 §4.A.7):
6478
+ * `transactional` = same `TransactWriteItems`; `eventual` = asynchronous catch-up.
6479
+ */
6480
+ type RelationConsistency = 'transactional' | 'eventual';
6481
+ /**
6482
+ * How a maintained write is applied (Epic #118 §4.A.7):
6483
+ * `mutation` = composed into the same mutation; `stream` = driven asynchronously
6484
+ * off a stream / outbox.
6485
+ */
6486
+ type RelationUpdateMode = 'mutation' | 'stream';
6487
+ /**
6488
+ * Read-strategy hints for a maintained relation (Epic #118 §5.1 `read`).
6489
+ *
6490
+ * All fields optional — present only when a `pattern` declares a read shape
6491
+ * (e.g. an `embeddedSnapshot` keeping the latest N items). A bare relation
6492
+ * carries no `read` block.
6493
+ */
6494
+ interface RelationReadOptions {
6495
+ /** Cap on materialised items kept inline (e.g. "latest 3 posts"). */
6496
+ maxItems?: number;
6497
+ /** Ordering of the maintained collection. */
6498
+ order?: 'ASC' | 'DESC';
6499
+ }
6500
+ /**
6501
+ * Write-maintenance declaration for a relation (Epic #118 §5.1 `write`).
6502
+ *
6503
+ * `maintainedOn` is the cross-entity trigger list (`"<Entity>.<event>"`, e.g.
6504
+ * `"Post.created"`) that the maintenance graph (#124/#125) will key off. Recorded
6505
+ * here as declaration metadata only.
6506
+ */
6507
+ interface RelationWriteOptions {
6508
+ /**
6509
+ * Cross-entity triggers, e.g. `['Post.created', 'Post.removed']`. The event
6510
+ * segment is the confirmed shared lifecycle vocabulary (epic #118 A/B
6511
+ * reconciliation): `created` / `updated` / `removed`.
6512
+ */
6513
+ maintainedOn?: string[];
6514
+ consistency?: RelationConsistency;
6515
+ updateMode?: RelationUpdateMode;
6516
+ }
6517
+ /**
6518
+ * Snapshot / projection capture map for a maintained relation
6519
+ * (Epic #118 §5.1 / §5.2 `projection`).
6520
+ *
6521
+ * Maps each captured field name on the maintaining side to **how** the source
6522
+ * value is projected. The confirmed shared vocabulary (epic #118 A/B
6523
+ * reconciliation) is the **function-form IR** — the same {@link
6524
+ * ProjectionTransform} node A (#120) defines on `src/define/entity-writes.ts`,
6525
+ * re-used here (NOT re-defined) so the two surfaces never re-diverge. Each value
6526
+ * is either:
6527
+ *
6528
+ * - a {@link ProjectionTransform} (built with the standalone authoring helpers,
6529
+ * e.g. `preview('body', 120)` — RFC §5.2 `textPreview: preview('body', 120)`),
6530
+ * or
6531
+ * - a bare `string` — the **identity shorthand**: project that source path
6532
+ * through unchanged (e.g. `{ postId: 'postId' }`).
6533
+ *
6534
+ * `Readonly` so the recorded declaration metadata is not mutated downstream.
6535
+ * Issue #121 scopes this to the declaration shape; the maintenance graph /
6536
+ * compile injection that consumes the IR is #124/#125.
6537
+ *
6538
+ * @example `{ postId: 'postId', textPreview: preview('body', 120) }`
6539
+ */
6540
+ type RelationProjection = Readonly<Record<string, ProjectionTransform | string>>;
6541
+ interface RelationOptions {
6542
+ limit?: RelationLimitOptions;
6543
+ order?: 'ASC' | 'DESC';
6544
+ /**
6545
+ * Named maintenance preset (Epic #118). Omitted = plain read-only navigation,
6546
+ * fully backward compatible with the historical relation decorators.
6547
+ */
6548
+ pattern?: RelationPattern;
6549
+ read?: RelationReadOptions;
6550
+ write?: RelationWriteOptions;
6551
+ projection?: RelationProjection;
6552
+ }
6553
+ interface RelationMetadata {
6554
+ type: 'hasMany' | 'hasOne' | 'belongsTo';
6555
+ propertyName: string;
6556
+ targetFactory: () => new (...args: unknown[]) => unknown;
6557
+ keyBinding: Record<string, string>;
6558
+ options?: RelationOptions;
6559
+ }
6560
+ /**
6561
+ * The scalar-aggregate value an `@aggregate` field derives from its source entity
6562
+ * (Epic #118 §5.2 counter / latest preset; issue #122). It is the function-form
6563
+ * IR built by the {@link count} / {@link max} value helpers — the aggregation an
6564
+ * `@aggregate(() => Source, keyBinding, { pattern, value })` field maintains:
6565
+ *
6566
+ * - `count` — the cardinality of source rows matched by the key binding (no
6567
+ * source field; e.g. `postCount!: number` ← `count()`).
6568
+ * - `max` — the maximum value of a named source `field` (e.g. `lastPostAt!:
6569
+ * string` ← `max('createdAt')`).
6570
+ *
6571
+ * Recorded purely as declaration metadata (issue #122 scope = declaration
6572
+ * surface): the maintenance graph (#124) / compile injection (#125) consume it;
6573
+ * the effect itself is out of scope here. Kept open-ended via the discriminated
6574
+ * `op` so a future aggregation (`min`, `sum`, …) extends the union without a
6575
+ * breaking change.
6576
+ *
6577
+ * @example `count()` → `{ op: 'count' }`
6578
+ * @example `max('createdAt')` → `{ op: 'max', field: 'createdAt' }`
6579
+ */
6580
+ type AggregateValue = {
6581
+ readonly op: 'count';
6582
+ } | {
6583
+ readonly op: 'max';
6584
+ readonly field: string;
6585
+ };
6586
+ /**
6587
+ * Options for an `@aggregate` field (Epic #118 §5.2 counter / latest preset;
6588
+ * issue #122). Mirrors the maintenance vocabulary a relation declares so the two
6589
+ * authoring surfaces stay in lockstep:
6590
+ *
6591
+ * - `pattern` reuses {@link RelationPattern} (e.g. `'counter'`) — the named
6592
+ * maintenance preset, NOT re-defined here.
6593
+ * - `value` is the {@link AggregateValue} the field maintains (`count()` /
6594
+ * `max('createdAt')`).
6595
+ * - `write` reuses {@link RelationWriteOptions} verbatim — `maintainedOn`
6596
+ * triggers (`Post.created` / `Post.removed`, the confirmed shared
6597
+ * `created|updated|removed` event vocabulary) plus `consistency`
6598
+ * (`transactional|eventual`) / `updateMode` (`mutation|stream`). These are NOT
6599
+ * re-defined; they are the same shared types B (#121) added.
6600
+ */
6601
+ interface AggregateOptions {
6602
+ /** Named maintenance preset (typically `'counter'`); reuses {@link RelationPattern}. */
6603
+ pattern?: RelationPattern;
6604
+ /** The scalar aggregation this field maintains (`count()` / `max('createdAt')`). */
6605
+ value: AggregateValue;
6606
+ /** Cross-entity maintenance triggers / consistency / update-mode (reused from B). */
6607
+ write?: RelationWriteOptions;
6608
+ }
6609
+ /**
6610
+ * Declaration metadata recorded by an `@aggregate` field decorator (issue #122).
6611
+ *
6612
+ * An `@aggregate` is a **scalar** field (`postCount!: number`,
6613
+ * `lastPostAt!: string`) whose value is derived — by aggregating rows of a source
6614
+ * entity resolved by {@link targetFactory} + {@link keyBinding} — rather than
6615
+ * stored directly. It is therefore recorded in its **own** metadata array
6616
+ * (`EntityMetadata.aggregates`), distinct from both {@link FieldMetadata} (a
6617
+ * plain stored attribute) and {@link RelationMetadata} (a navigation yielding
6618
+ * model instances): an aggregate carries a source binding like a relation, but
6619
+ * surfaces a scalar value like a field. The collector path mirrors relations
6620
+ * (decorator pushes, `@model` drains).
6621
+ *
6622
+ * Issue #122 scope is the declaration surface + metadata pass-through only; the
6623
+ * aggregation effect's compile / runtime (#124/#125/#127) is out of scope.
6624
+ */
6625
+ interface AggregateMetadata {
6626
+ /** The scalar field the aggregate value is written to. */
6627
+ propertyName: string;
6628
+ /** Resolves the source entity whose rows are aggregated. */
6629
+ targetFactory: () => new (...args: unknown[]) => unknown;
6630
+ /** The key binding selecting the source rows (target key field → source field). */
6631
+ keyBinding: Record<string, string>;
6632
+ /** The aggregate preset / value / maintenance-write declaration. */
6633
+ options: AggregateOptions;
6634
+ }
6635
+ interface EmbeddedMetadata {
6636
+ propertyName: string;
6637
+ modelFactory: () => new (...args: unknown[]) => unknown;
6638
+ }
6639
+ interface EntityMetadata {
6640
+ tableName: string;
6641
+ prefix: string;
6642
+ fields: FieldMetadata[];
6643
+ primaryKey: KeyDefinition | null;
6644
+ gsiDefinitions: GsiDefinition[];
6645
+ relations: RelationMetadata[];
6646
+ /** Scalar `@aggregate` fields (Epic #118 §5.2 counter / latest; issue #122). */
6647
+ aggregates: AggregateMetadata[];
6648
+ embeddedFields: EmbeddedMetadata[];
6649
+ }
6650
+
5172
6651
  /**
5173
6652
  * CDC emulator types (issue #72). These types are **cdc-module-owned**; core
5174
6653
  * never references them. The {@link ChangeEvent} shape mirrors DynamoDB Streams
@@ -5282,4 +6761,4 @@ interface CdcEmulatorOptions {
5282
6761
  }
5283
6762
  type ShardId = string;
5284
6763
 
5285
- export { type EntityRef as $, type ConcurrentRecomputeRef as A, type BatchGetExecInput as B, type ChangeEvent as C, type DynamoDBOperation as D, type Executor as E, type FaultSpec as F, type EventLog as G, type ReplayOptions as H, type Item as I, type ShardId as J, type TransactionItemSpec as K, type Param as L, type ModelStatic as M, type ParamDescriptor as N, type DefinitionMap as O, type PutInput as P, type OperationDefinition as Q, type ReadExecOptions as R, type SelectableOf as S, type TransactWriteExecItem as T, type UpdateInput as U, type WriteDefinitionOptions as V, type WriteExecOptions as W, type PartialQueryKeyOf as X, type StrictSelectSpec as Y, type EntityInput as Z, type UniqueQueryKeyOf as _, type ExecutorResult as a, type DerivedUpdate as a$, type ConditionInput as a0, type QueryModelContract as a1, type QueryMethodSpec as a2, type CommandModelContract as a3, type CommandMethodSpec as a4, type ContractSpec as a5, type QuerySpec as a6, type CommandSpec as a7, type ContextSpec as a8, type OperationsDocument as a9, type ComposeSpec as aA, type CondSlot as aB, type ConditionCheckInput as aC, type Connection as aD, type ContractCallSignature as aE, type ContractCardinality as aF, type ContractCommandParams as aG, type ContractCommandResult as aH, type ContractComposeNode as aI, type ContractFromRef as aJ, type ContractInputArity as aK, type ContractItem as aL, type ContractKeyFieldRef as aM, type ContractKeyInput as aN, type ContractKeyRef as aO, type ContractKeySpec as aP, type ContractKind as aQ, type ContractMethodOp as aR, type ContractParamRef as aS, type ContractQueryParams as aT, type ContractResolution as aU, type CtxBase as aV, DEFAULT_MAX_ATTEMPTS as aW, DEFAULT_RETRY_POLICY as aX, type DeleteOptions as aY, type DeriveEffect as aZ, type DerivedEdgeWrite as a_, type AnyOperationDefinition as aa, type BridgeBundle as ab, type ConditionSpec as ac, type BatchDeleteRequest as ad, type BatchGetOptions as ae, type BatchGetRequest as af, BatchGetResult as ag, type BatchPutRequest as ah, type BatchResult as ai, type BatchWriteRequest as aj, CONTRACT_RANGE_FANOUT_CONCURRENCY as ak, type CdcMode as al, type Change as am, type ChangeBatch as an, type ChangeEventName as ao, type ClockMode as ap, type Column as aq, type ColumnMap as ar, type CommandContractMethodSpec as as, type CommandInputShape as at, type CommandMethod as au, type CommandPlan as av, type CommandResolutionTarget as aw, type CommandResultKind as ax, type CommandSelectShape as ay, type CompiledFragment as az, type WriteResult as b, type ReadOpKind as b$, type DescriptorBinding as b0, ENTITY_WRITES_MARKER as b1, type EdgeEffect as b2, type EffectPath as b3, type EmitEffect as b4, type EntityWritesDefinition as b5, type EntityWritesShape as b6, type ExecutableCommandContract as b7, type ExecutableQueryContract as b8, type FilterInput as b9, type MutateParallelResult as bA, type MutateTransactionResult as bB, type MutationBody as bC, type MutationDescriptorMap as bD, type MutationFragment as bE, type MutationInputProxy as bF, type MutationInputRef as bG, type MutationIntent as bH, type NumberParam as bI, type OperationKind as bJ, type OperationSpec as bK, type ParallelOpResult as bL, type ParamKind as bM, type ParamSpec as bN, type ParamStructure as bO, type PersistCtx as bP, type PersistOrigin as bQ, type PlannedCommandMethod as bR, type PutOptions as bS, type QueryContractMethodSpec as bT, type QueryEnvelopeResult as bU, type QueryKeyOf as bV, type QueryMethod as bW, type QueryResult as bX, type RangeConditionSpec as bY, type ReadEnvelope as bZ, type ReadOpCtx as b_, type FilterSpec as ba, type FragmentInput as bb, type GsiDefinitionMarker as bc, type GsiOptions as bd, type IdempotencyEffect as be, type InProcessWriteDescriptor as bf, type InputArity as bg, type KeyDefinitionMarker as bh, type KeySegment as bi, type KeySlot as bj, type KeyStructure as bk, type KeyedResult as bl, LIFECYCLE_CONTRACT_MARKER as bm, type LifecycleContract as bn, type LifecycleEffects as bo, type LiteralParam as bp, type ManifestEntity as bq, type ManifestField as br, type ManifestFieldType as bs, type ManifestGsi as bt, type ManifestKey as bu, type ManifestRelation as bv, type ManifestTable as bw, type ModelRef as bx, type MutateMode as by, type MutateOptions as bz, type DeleteInput as c, isContractFromRef as c$, type ReadOperationType as c0, type ReadRouteDescriptor as c1, type ReadRouteOptions as c2, type ReadRouteResult as c3, type RecordedCompose as c4, type RelationBuilder as c5, type RelationSelect as c6, type RelationSpec as c7, type RequiresEffect as c8, type Resolution as c9, buildDeleteInput as cA, buildPutInput as cB, buildUpdateInput as cC, compileFragment as cD, compileMutationPlan as cE, compileSingleFragmentPlan as cF, cond as cG, contractOfMethodSpec as cH, definePlan as cI, entityWrites as cJ, executeBatchGet as cK, executeBatchWrite as cL, executeCommandMethod as cM, executeDelete as cN, executeKeyedBatchGet as cO, executePut as cP, executeQueryMethod as cQ, executeRangeFanout as cR, executeTransaction as cS, executeUpdate as cT, from as cU, getEntityWrites as cV, gsi as cW, isColumn as cX, isCommandModelContract as cY, isCommandPlan as cZ, isContractComposeNode as c_, type RetryInfo as ca, type RetryOperationKind as cb, SPEC_VERSION as cc, type SegmentSpec as cd, type SelectBuilder as ce, type SelectOf as cf, type StartingPosition as cg, type StreamViewType as ch, type StringParam as ci, TransactionContext as cj, type TransactionItemType as ck, type UniqueEffect as cl, type Updatable as cm, type UpdateOptions as cn, type WhenSpec as co, type WriteCtx as cp, type WriteDescriptor as cq, type WriteEnvelope as cr, type WriteInput as cs, type WriteKind as ct, type WriteLifecyclePhase as cu, type WriteMiddleware as cv, type WriteOperationType as cw, type WriteRecorder as cx, type WriteResultProjection as cy, attachModelClass as cz, type BatchWriteExecItem as d, isContractKeyFieldRef as d0, isContractKeyRef as d1, isContractParamRef as d2, isEntityWritesDefinition as d3, isKeySegment as d4, isLifecycleContract as d5, isMutationFragment as d6, isMutationInputRef as d7, isParam as d8, isPlannedCommandMethod as d9, isQueryModelContract as da, isRetryableError as db, isRetryableTransactionCancellation as dc, k as dd, key as de, lifecyclePhaseForIntent as df, mintContractKeyFieldRef as dg, mintContractParamRef as dh, mutation as di, param as dj, publicCommandModel as dk, publicQueryModel as dl, query as dm, resolveLifecycle as dn, wholeKeysSentinel as dp, type BatchExecOptions as e, DDBModel as f, type PrimaryKeyOf as g, type RequestContext as h, type Middleware as i, type ReadRequestKind as j, type CtxModel as k, type ReadParams as l, type ReadRequestCtx as m, type RetryPolicy as n, type ExecutionPlanSpec as o, type SegmentedKey as p, type SelectBuilderSpec as q, type RawCondition as r, type TransactionSpec as s, type Manifest as t, type RetryOverride as u, type ExecutionPlan as v, type ResolvedKey as w, type CdcEmulatorOptions as x, type ChangeHandler as y, type Unsubscribe as z };
6764
+ export { type RelationMetadata 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 ViewDefinition as Z, type ProjectionDefinition as _, type ExecutorResult as a, type ContractKeyFieldRef as a$, type TransactionItemSpec as a0, type MaintainEffect as a1, type Param as a2, type ParamDescriptor as a3, type DefinitionMap as a4, type OperationDefinition as a5, type WriteDefinitionOptions as a6, type PartialQueryKeyOf as a7, type StrictSelectSpec as a8, type EntityInput as a9, type ChangeBatch as aA, type ChangeEventName as aB, type ClockMode as aC, type CollectionEffect as aD, type CollectionOptions as aE, type Column as aF, type ColumnMap as aG, type CommandContractMethodSpec as aH, type CommandInputShape as aI, type CommandMethod as aJ, type CommandPlan as aK, type CommandResolutionTarget as aL, type CommandResultKind as aM, type CommandSelectShape as aN, type CompiledFragment as aO, type ComposeSpec as aP, type CondSlot as aQ, type ConditionCheckInput as aR, type Connection as aS, type ContractCallSignature as aT, type ContractCardinality as aU, type ContractCommandParams as aV, type ContractCommandResult as aW, type ContractComposeNode as aX, type ContractFromRef as aY, type ContractInputArity as aZ, type ContractItem as a_, type UniqueQueryKeyOf as aa, type EntityRef as ab, type ConditionInput as ac, type QueryModelContract as ad, type QueryMethodSpec as ae, type CommandModelContract as af, type CommandMethodSpec as ag, type ContractSpec as ah, type QuerySpec as ai, type CommandSpec as aj, type ContextSpec as ak, type OperationsDocument as al, type AnyOperationDefinition as am, type BridgeBundle as an, type ConditionSpec as ao, type AggregateMetadata as ap, type BatchDeleteRequest as aq, type BatchGetOptions as ar, type BatchGetRequest as as, BatchGetResult as at, type BatchPutRequest as au, type BatchResult as av, type BatchWriteRequest as aw, CONTRACT_RANGE_FANOUT_CONCURRENCY as ax, type CdcMode as ay, type Change as az, type WriteResult as b, type MutateParallelResult as b$, type ContractKeyInput as b0, type ContractKeyRef as b1, type ContractKeySpec as b2, type ContractKind as b3, type ContractMethodOp as b4, type ContractParamRef as b5, type ContractQueryParams as b6, type ContractResolution as b7, type CounterAggregate as b8, type CounterEffect as b9, type KeySegment as bA, type KeySlot as bB, type KeyStructure as bC, type KeyedResult as bD, LIFECYCLE_CONTRACT_MARKER as bE, type LifecycleContract as bF, type LifecycleEffects as bG, type LiteralParam as bH, type MaintainConsistency as bI, type MaintainEvent as bJ, type MaintainItem as bK, type MaintainTrigger as bL, type MaintainUpdateMode as bM, type MaintenanceGraph as bN, type ManifestEntity as bO, type ManifestField as bP, type ManifestFieldType as bQ, type ManifestGsi as bR, type ManifestKey as bS, type ManifestRelation as bT, type ManifestTable as bU, type MembershipEffect as bV, type MembershipPredicate as bW, type MembershipPredicateOp as bX, type ModelRef as bY, type MutateMode as bZ, type MutateOptions as b_, type CtxBase as ba, DEFAULT_MAX_ATTEMPTS as bb, DEFAULT_RETRY_POLICY as bc, type DeleteOptions as bd, type DeriveEffect as be, type DerivedEdgeWrite as bf, type DerivedUpdate as bg, type DescriptorBinding as bh, ENTITY_WRITES_MARKER as bi, type EdgeEffect as bj, type EffectPath as bk, type EmbeddedMetadata as bl, type EmitEffect as bm, type EntityWritesDefinition as bn, type EntityWritesShape as bo, type ExecutableCommandContract as bp, type ExecutableQueryContract as bq, type FilterInput as br, type FilterSpec as bs, type FragmentInput as bt, type GsiDefinitionMarker as bu, type GsiOptions as bv, type IdempotencyEffect as bw, type InProcessWriteDescriptor as bx, type InputArity as by, type KeyDefinitionMarker as bz, type DeleteInput as c, type UniqueEffect as c$, type MutateTransactionResult as c0, type MutationBody as c1, type MutationDescriptorMap as c2, type MutationFragment as c3, type MutationInputProxy as c4, type MutationInputRef as c5, type MutationIntent as c6, type NumberParam as c7, type OperationKind as c8, type OperationSpec as c9, type ReadRouteResult as cA, type RecordedCompose as cB, type RelationBuilder as cC, type RelationConsistency as cD, type RelationLimitOptions as cE, type RelationPattern as cF, type RelationProjection as cG, type RelationReadOptions as cH, type RelationSelect as cI, type RelationSpec as cJ, type RelationUpdateMode as cK, type RelationWriteOptions as cL, type RequiresEffect as cM, type Resolution as cN, type RetryInfo as cO, type RetryOperationKind as cP, SPEC_VERSION as cQ, type SegmentSpec as cR, type SegmentedKey as cS, type SelectBuilder as cT, type SelectOf as cU, type SnapshotEffect as cV, type StartingPosition as cW, type StreamViewType as cX, type StringParam as cY, TransactionContext as cZ, type TransactionItemType as c_, type ParallelOpResult as ca, type ParamKind as cb, type ParamSpec as cc, type ParamStructure as cd, type PersistCtx as ce, type PersistOrigin as cf, type PlannedCommandMethod as cg, type ProjectionDefinitionInput as ch, type ProjectionDefinitionMap as ci, type ProjectionMap as cj, type ProjectionTransform as ck, type ProjectionTransformOp as cl, type ProjectionVia as cm, type PutOptions as cn, type QueryContractMethodSpec as co, type QueryEnvelopeResult as cp, type QueryKeyOf as cq, type QueryMethod as cr, type QueryResult as cs, type RangeConditionSpec as ct, type ReadEnvelope as cu, type ReadOpCtx as cv, type ReadOpKind as cw, type ReadOperationType as cx, type ReadRouteDescriptor as cy, type ReadRouteOptions as cz, type BatchWriteExecItem as d, isMaintainTrigger as d$, type Updatable as d0, type UpdateOptions as d1, type VersionedDefinition as d2, type VersionedDefinitionInput as d3, type ViewDefinitionInput as d4, type ViewDefinitionMap as d5, ViewRegistry as d6, type ViewSource as d7, type ViewSourceSlice as d8, type WhenSpec as d9, defineViews as dA, entityWrites as dB, executeBatchGet as dC, executeBatchWrite as dD, executeCommandMethod as dE, executeDelete as dF, executeKeyedBatchGet as dG, executePut as dH, executeQueryMethod as dI, executeRangeFanout as dJ, executeTransaction as dK, executeUpdate as dL, from as dM, getEntityWrites as dN, gsi as dO, identity as dP, isColumn as dQ, isCommandModelContract as dR, isCommandPlan as dS, isContractComposeNode as dT, isContractFromRef as dU, isContractKeyFieldRef as dV, isContractKeyRef as dW, isContractParamRef as dX, isEntityWritesDefinition as dY, isKeySegment as dZ, isLifecycleContract as d_, type WriteCtx as da, type WriteDescriptor as db, type WriteEnvelope as dc, type WriteInput as dd, type WriteKind as de, type WriteLifecyclePhase as df, type WriteMiddleware as dg, type WriteOperationType as dh, type WriteRecorder as di, type WriteResultProjection as dj, attachModelClass as dk, buildDeleteInput as dl, buildMaintenanceGraph as dm, buildPutInput as dn, buildUpdateInput as dp, compileFragment as dq, compileMutationPlan as dr, compileSingleFragmentPlan as ds, cond as dt, contractOfMethodSpec as du, definePlan as dv, defineProjection as dw, defineProjections as dx, defineVersioned as dy, defineView as dz, type BatchExecOptions as e, isMutationFragment as e0, isMutationInputRef as e1, isParam as e2, isPlannedCommandMethod as e3, isProjectionDefinition as e4, isQueryModelContract as e5, isRetryableError as e6, isRetryableTransactionCancellation as e7, isViewDefinition as e8, k as e9, key as ea, lifecyclePhaseForIntent as eb, maintainTrigger as ec, mintContractKeyFieldRef as ed, mintContractParamRef as ee, mutation as ef, param as eg, preview as eh, publicCommandModel as ei, publicQueryModel as ej, query as ek, resolveLifecycle as el, whenMember as em, wholeKeysSentinel as en, 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 };