graphddb 0.3.0 → 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.
@@ -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()`.
@@ -2115,19 +2115,33 @@ interface SnapshotEffect {
2115
2115
  /** The consistency the write lands under (default `eventual`). */
2116
2116
  readonly consistency?: MaintainConsistency;
2117
2117
  }
2118
+ /** The sort direction a maintained collection orders by (mirrors the read-side `read.order`). */
2119
+ type CollectionOrderDirection = 'ASC' | 'DESC';
2118
2120
  /**
2119
2121
  * Options for the bounded collection a {@link CollectionEffect} maintains: the
2120
2122
  * target {@link field} that holds the collection, an optional {@link maxItems}
2121
2123
  * cap (oldest items are evicted past it), and an optional {@link orderBy} source
2122
- * path the items are ordered by.
2124
+ * path the items are ordered by (with an {@link orderDir} direction).
2123
2125
  */
2124
2126
  interface CollectionOptions {
2125
2127
  /** The target attribute that holds the maintained collection. */
2126
2128
  readonly field: string;
2127
2129
  /** Cap on the collection size; items past it are evicted (unbounded if omitted). */
2128
2130
  readonly maxItems?: number;
2129
- /** A path-rooted source value the collection is ordered by (e.g. `$.entity.createdAt`). */
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
+ */
2130
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;
2131
2145
  }
2132
2146
  /**
2133
2147
  * A **collection** maintenance effect (issue #120): on a {@link MaintainTrigger}
@@ -2216,12 +2230,81 @@ interface CounterEffect {
2216
2230
  /** The consistency the write lands under (default `eventual`). */
2217
2231
  readonly consistency?: MaintainConsistency;
2218
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
+ }
2219
2301
  /**
2220
2302
  * The union of maintenance effects a {@link SnapshotEffect}, {@link CollectionEffect},
2221
- * or {@link CounterEffect} can be. The shared IR the maintenance graph (#124) and
2222
- * compile injection (#125 snapshot/collection, #141 counter) consume.
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.
2223
2306
  */
2224
- type MaintainEffect = SnapshotEffect | CollectionEffect | CounterEffect;
2307
+ type MaintainEffect = SnapshotEffect | CollectionEffect | CounterEffect | MembershipEffect;
2225
2308
  /**
2226
2309
  * A domain event (proposal §2 `emits`): an outbox `Put` drained to Streams /
2227
2310
  * `src/cdc/`. Derived by #87 — stored opaquely here.
@@ -2668,6 +2751,448 @@ declare function mutation(name: string, body: MutationBody): CommandPlan;
2668
2751
  */
2669
2752
  declare const definePlan: typeof mutation;
2670
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
+
2671
3196
  /**
2672
3197
  * Maintenance graph builder (Epic #118 issue #124 — the RFC B-案 core).
2673
3198
  *
@@ -2823,7 +3348,7 @@ interface MaintenanceGraph {
2823
3348
  * @param registry The models to scan. Defaults to `MetadataRegistry.getAll()`;
2824
3349
  * an explicit map is accepted for tests / scoped builds.
2825
3350
  */
2826
- declare function buildMaintenanceGraph(registry?: ReadonlyMap<Function, EntityMetadata>): MaintenanceGraph;
3351
+ declare function buildMaintenanceGraph(registry?: ReadonlyMap<Function, EntityMetadata>, views?: readonly ViewDefinition[]): MaintenanceGraph;
2827
3352
 
2828
3353
  /**
2829
3354
  * Serializable static operation specs and manifest (issue #42, Python-bridge
@@ -3240,6 +3765,8 @@ interface MaintainCollectionSpec {
3240
3765
  readonly maxItems?: number;
3241
3766
  /** The mutation-input field the items are ordered by (recorded for #130). */
3242
3767
  readonly orderBy?: string;
3768
+ /** The direction `orderBy` sorts by, from the relation's `read.order` (recorded for #130). */
3769
+ readonly orderDir?: 'ASC' | 'DESC';
3243
3770
  }
3244
3771
  /**
3245
3772
  * The maintenance payload an `Update` {@link TransactionItemSpec} carries when it
@@ -3576,48 +4103,6 @@ interface BridgeBundle {
3576
4103
  readonly operations: OperationsDocument;
3577
4104
  }
3578
4105
 
3579
- /**
3580
- * Single-fragment mutation → command-op compiler (issue #83, Epic #80; proposal
3581
- * `docs/mutation-command-derivation.md` §3).
3582
- *
3583
- * A {@link CommandPlan} (a `mutation(...)` result) is the internal IR: a **list**
3584
- * of write fragments to compose atomically. This module compiles the
3585
- * **single-fragment** case end-to-end into the **same** {@link ContractMethodOp}
3586
- * the #58 contract recorder produces, so the existing serializer
3587
- * (`opToDefinition` → `buildCommandSpec`, `src/spec/contracts.ts` /
3588
- * `src/spec/operations.ts`) and the existing runtimes consume it with no new
3589
- * write substrate. The IR is already a list, so the **N-fragment atomic merge is
3590
- * #90** — this module rejects N>1 with a clear pointer rather than half-merging.
3591
- *
3592
- * ## Intent → base op (proposal §3, "single fragment compile")
3593
- *
3594
- * | intent | base op |
3595
- * |----------|-------------------------------------------|
3596
- * | `create` | `PutItem` (`attribute_not_exists(PK)`) |
3597
- * | `update` | `UpdateItem` (key + changed fields) |
3598
- * | `remove` | `DeleteItem` (key) |
3599
- *
3600
- * ## `use:` resolution (the resolved fork — proposal §3, pinned spec correction)
3601
- *
3602
- * The fragment's **intent** selects the lifecycle; `use:` is **optional**:
3603
- *
3604
- * - `use:` present → adopt that {@link EntityWritesDefinition} (the **whole**
3605
- * `writes` set; never a single lifecycle).
3606
- * - `use:` omitted → default to the **target model's own** `entityWrites` save
3607
- * contract ({@link getEntityWrites}).
3608
- * - the resolved save contract (custom or default) has **no** entry for the
3609
- * intent's lifecycle, **or the target declares no `writes` at all** → the
3610
- * **trivial base op** for the intent (create → `Put` `attribute_not_exists(PK)`,
3611
- * update → `Update`, remove → `Delete`) against the plain model. So
3612
- * `{ create: () => PostModel, key, input }` works on a bare model.
3613
- *
3614
- * Either way #83 emits the **same** base op — the resolved
3615
- * {@link LifecycleContract} only matters as the **per-fragment hook** the
3616
- * effect-derivation issues (#84–#87) read: the resolved lifecycle's
3617
- * {@link LifecycleContract.effects} are exposed on {@link CompiledFragment} and
3618
- * consumed by NONE of #83 (the empty hook).
3619
- */
3620
-
3621
4106
  /**
3622
4107
  * A **referential-integrity assertion** derived from a `requires`
3623
4108
  * ({@link RequiresEffect}) effect (issue #84; proposal §2/§3). It is the
@@ -3734,11 +4219,32 @@ interface DerivedMaintainWrite {
3734
4219
  readonly relationProperty: string;
3735
4220
  /** The trigger (`<sourceLogicalName>.<event>`) that fired this maintenance. */
3736
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';
3737
4242
  /**
3738
4243
  * Whether the write mirrors a single row (`snapshot`), appends a collection item
3739
- * (`collection`), or applies a scalar `ADD` counter (`counter`, #141).
4244
+ * (`collection`), applies a scalar `ADD` counter (`counter`, #141), or puts/deletes a
4245
+ * predicate-gated view row (`membership`, #133 sparse view — stream-only).
3740
4246
  */
3741
- readonly kind: 'snapshot' | 'collection' | 'counter';
4247
+ readonly kind: 'snapshot' | 'collection' | 'counter' | 'membership';
3742
4248
  /**
3743
4249
  * The destination row's key binding: target key field → the mutation-input param
3744
4250
  * that supplies it (resolved from the effect's `$.entity.<sourceField>` paths —
@@ -3766,18 +4272,44 @@ interface DerivedMaintainWrite {
3766
4272
  readonly field: string;
3767
4273
  readonly maxItems?: number;
3768
4274
  readonly orderBy?: string;
4275
+ readonly orderDir?: 'ASC' | 'DESC';
3769
4276
  };
3770
4277
  /**
3771
- * For a `counter` write (#141 `@aggregate(..., { pattern: 'counter', value:
3772
- * count() })`), the scalar `ADD`: the target {@link counter.attribute} and the
3773
- * signed {@link counter.delta} the trigger applies (`+1` created / `-1` removed).
3774
- * A counter `ADD` is commutative, so two counter writes on the SAME row MERGE (the
3775
- * #92 symmetric-counter collapse) rather than reject — unlike snapshot/collection.
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
+ *
3776
4289
  * Absent for a `snapshot` / `collection`.
3777
4290
  */
3778
4291
  readonly counter?: {
3779
4292
  readonly attribute: string;
4293
+ readonly op: 'count';
3780
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;
3781
4313
  };
3782
4314
  }
3783
4315
  /**
@@ -3891,6 +4423,43 @@ interface DerivedIdempotencyGuard {
3891
4423
  /** The guard marker-row `Put` (one item; `attribute_not_exists`, `literalKey`). */
3892
4424
  readonly items: readonly TransactionItemSpec[];
3893
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
+ }
3894
4463
  /**
3895
4464
  * The result of compiling one fragment: the {@link ContractMethodOp} the existing
3896
4465
  * serializer / runtime consume, the resolved contract Key fields (the model's
@@ -3984,8 +4553,26 @@ interface CompiledFragment {
3984
4553
  * realizes into the atomic `TransactWriteItems`; #125 derives it and rejects a
3985
4554
  * same-target collision (論点2 = b). The cross-fragment AGGREGATION and the
3986
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.
3987
4561
  */
3988
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[];
3989
4576
  }
3990
4577
  /**
3991
4578
  * Resolve the {@link LifecycleContract} a fragment adopts for its intent:
@@ -4716,10 +5303,22 @@ interface CommandMethodSpec<TKey, TParams, TResult> {
4716
5303
  * otherwise single-op method to a transaction: the runtime (#127) realizes each
4717
5304
  * into the method's atomic `TransactWriteItems`, so the maintained row moves
4718
5305
  * atomically with the source write (a failure rolls the WHOLE set back). #127 is
4719
- * the synchronous (`updateMode: 'mutation'`) path only; the async stream path is
4720
- * #130 (loud-rejected at compile time, never silently dropped).
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.
4721
5309
  */
4722
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[];
4723
5322
  /** @internal Phantom carrier retaining the formal `CommandMethod` type. */
4724
5323
  readonly __signature?: CommandMethod<TKey, TParams, TResult>;
4725
5324
  }
@@ -5336,6 +5935,18 @@ interface ExecutableCommandContract {
5336
5935
  * maintainer / a #64 hand-written method.
5337
5936
  */
5338
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[];
5339
5950
  /**
5340
5951
  * The return projection of a `mutation`-derived method (issue #83). When
5341
5952
  * present, a **single-key** call performs the write then issues a
@@ -5861,7 +6472,7 @@ interface RelationLimitOptions {
5861
6472
  * `(string & {})` tail so a future preset can be authored before this union is
5862
6473
  * widened, without a breaking change to callers.
5863
6474
  */
5864
- type RelationPattern = 'samePartition' | 'edge' | 'counter' | 'embeddedSnapshot' | 'materializedView' | 'sparseView' | 'externalProjection' | (string & {});
6475
+ type RelationPattern = 'samePartition' | 'edge' | 'dualEdge' | 'counter' | 'embeddedSnapshot' | 'materializedView' | 'sparseView' | 'versioned' | 'externalProjection' | (string & {});
5865
6476
  /**
5866
6477
  * Consistency boundary a maintained write is declared against (Epic #118 §4.A.7):
5867
6478
  * `transactional` = same `TransactWriteItems`; `eventual` = asynchronous catch-up.
@@ -6150,4 +6761,4 @@ interface CdcEmulatorOptions {
6150
6761
  }
6151
6762
  type ShardId = string;
6152
6763
 
6153
- export { type Param as $, type AggregateOptions as A, type BatchGetExecInput as B, type ChangeEvent as C, type DynamoDBOperation as D, type Executor as E, type FieldOptions as F, type GsiDefinition as G, type ResolvedKey as H, type Item as I, type CdcEmulatorOptions as J, type KeyDefinition as K, type ChangeHandler as L, type ModelStatic as M, type Unsubscribe as N, type FaultSpec as O, type PutInput as P, type ConcurrentRecomputeRef as Q, type ReadExecOptions as R, type SelectableOf as S, type TransactWriteExecItem as T, type UpdateInput as U, type EventLog as V, type WriteExecOptions as W, type ReplayOptions as X, type ShardId as Y, type RelationMetadata as Z, type TransactionItemSpec as _, type ExecutorResult as a, type ContractKeySpec as a$, type ParamDescriptor as a0, type DefinitionMap as a1, type OperationDefinition as a2, type WriteDefinitionOptions as a3, type PartialQueryKeyOf as a4, type StrictSelectSpec as a5, type EntityInput as a6, type UniqueQueryKeyOf as a7, type EntityRef as a8, type ConditionInput as a9, type CollectionEffect as aA, type CollectionOptions as aB, type Column as aC, type ColumnMap as aD, type CommandContractMethodSpec as aE, type CommandInputShape as aF, type CommandMethod as aG, type CommandPlan as aH, type CommandResolutionTarget as aI, type CommandResultKind as aJ, type CommandSelectShape as aK, type CompiledFragment as aL, type ComposeSpec as aM, type CondSlot as aN, type ConditionCheckInput as aO, type Connection as aP, type ContractCallSignature as aQ, type ContractCardinality as aR, type ContractCommandParams as aS, type ContractCommandResult as aT, type ContractComposeNode as aU, type ContractFromRef as aV, type ContractInputArity as aW, type ContractItem as aX, type ContractKeyFieldRef as aY, type ContractKeyInput as aZ, type ContractKeyRef as a_, type QueryModelContract as aa, type QueryMethodSpec as ab, type CommandModelContract as ac, type CommandMethodSpec as ad, type ContractSpec as ae, type QuerySpec as af, type CommandSpec as ag, type ContextSpec as ah, type OperationsDocument as ai, type AnyOperationDefinition as aj, type BridgeBundle as ak, type ConditionSpec as al, type AggregateMetadata as am, type BatchDeleteRequest as an, type BatchGetOptions as ao, type BatchGetRequest as ap, BatchGetResult as aq, type BatchPutRequest as ar, type BatchResult as as, type BatchWriteRequest as at, CONTRACT_RANGE_FANOUT_CONCURRENCY as au, type CdcMode as av, type Change as aw, type ChangeBatch as ax, type ChangeEventName as ay, type ClockMode as az, type WriteResult as b, type MutationInputProxy as b$, type ContractKind as b0, type ContractMethodOp as b1, type ContractParamRef as b2, type ContractQueryParams as b3, type ContractResolution as b4, type CounterAggregate as b5, type CounterEffect as b6, type CtxBase as b7, DEFAULT_MAX_ATTEMPTS as b8, DEFAULT_RETRY_POLICY as b9, type KeyedResult as bA, LIFECYCLE_CONTRACT_MARKER as bB, type LifecycleContract as bC, type LifecycleEffects as bD, type LiteralParam as bE, type MaintainConsistency as bF, type MaintainEffect as bG, type MaintainEvent as bH, type MaintainItem as bI, type MaintainTrigger as bJ, type MaintainUpdateMode as bK, type MaintenanceGraph as bL, type ManifestEntity as bM, type ManifestField as bN, type ManifestFieldType as bO, type ManifestGsi as bP, type ManifestKey as bQ, type ManifestRelation as bR, type ManifestTable as bS, type ModelRef as bT, type MutateMode as bU, type MutateOptions as bV, type MutateParallelResult as bW, type MutateTransactionResult as bX, type MutationBody as bY, type MutationDescriptorMap as bZ, type MutationFragment as b_, type DeleteOptions as ba, type DeriveEffect as bb, type DerivedEdgeWrite as bc, type DerivedUpdate as bd, type DescriptorBinding as be, ENTITY_WRITES_MARKER as bf, type EdgeEffect as bg, type EffectPath as bh, type EmbeddedMetadata as bi, type EmitEffect as bj, type EntityWritesDefinition as bk, type EntityWritesShape as bl, type ExecutableCommandContract as bm, type ExecutableQueryContract as bn, type FilterInput as bo, type FilterSpec as bp, type FragmentInput as bq, type GsiDefinitionMarker as br, type GsiOptions as bs, type IdempotencyEffect as bt, type InProcessWriteDescriptor as bu, type InputArity as bv, type KeyDefinitionMarker as bw, type KeySegment as bx, type KeySlot as by, type KeyStructure as bz, type DeleteInput as c, type WriteKind as c$, type MutationInputRef as c0, type MutationIntent as c1, type NumberParam as c2, type OperationKind as c3, type OperationSpec as c4, type ParallelOpResult as c5, type ParamKind as c6, type ParamSpec as c7, type ParamStructure as c8, type PersistCtx as c9, type RelationSelect as cA, type RelationSpec as cB, type RelationUpdateMode as cC, type RelationWriteOptions as cD, type RequiresEffect as cE, type Resolution as cF, type RetryInfo as cG, type RetryOperationKind as cH, SPEC_VERSION as cI, type SegmentSpec as cJ, type SegmentedKey as cK, type SelectBuilder as cL, type SelectOf as cM, type SnapshotEffect as cN, type StartingPosition as cO, type StreamViewType as cP, type StringParam as cQ, TransactionContext as cR, type TransactionItemType as cS, type UniqueEffect as cT, type Updatable as cU, type UpdateOptions as cV, type WhenSpec as cW, type WriteCtx as cX, type WriteDescriptor as cY, type WriteEnvelope as cZ, type WriteInput as c_, type PersistOrigin as ca, type PlannedCommandMethod as cb, type ProjectionMap as cc, type ProjectionTransform as cd, type ProjectionTransformOp as ce, type PutOptions as cf, type QueryContractMethodSpec as cg, type QueryEnvelopeResult as ch, type QueryKeyOf as ci, type QueryMethod as cj, type QueryResult as ck, type RangeConditionSpec as cl, type ReadEnvelope as cm, type ReadOpCtx as cn, type ReadOpKind as co, type ReadOperationType as cp, type ReadRouteDescriptor as cq, type ReadRouteOptions as cr, type ReadRouteResult as cs, type RecordedCompose as ct, type RelationBuilder as cu, type RelationConsistency as cv, type RelationLimitOptions as cw, type RelationPattern as cx, type RelationProjection as cy, type RelationReadOptions as cz, type BatchWriteExecItem as d, resolveLifecycle as d$, type WriteLifecyclePhase as d0, type WriteMiddleware as d1, type WriteOperationType as d2, type WriteRecorder as d3, type WriteResultProjection as d4, attachModelClass as d5, buildDeleteInput as d6, buildMaintenanceGraph as d7, buildPutInput as d8, buildUpdateInput as d9, isContractFromRef as dA, isContractKeyFieldRef as dB, isContractKeyRef as dC, isContractParamRef as dD, isEntityWritesDefinition as dE, isKeySegment as dF, isLifecycleContract as dG, isMaintainTrigger as dH, isMutationFragment as dI, isMutationInputRef as dJ, isParam as dK, isPlannedCommandMethod as dL, isQueryModelContract as dM, isRetryableError as dN, isRetryableTransactionCancellation as dO, k as dP, key as dQ, lifecyclePhaseForIntent as dR, maintainTrigger as dS, mintContractKeyFieldRef as dT, mintContractParamRef as dU, mutation as dV, param as dW, preview as dX, publicCommandModel as dY, publicQueryModel as dZ, query as d_, compileFragment as da, compileMutationPlan as db, compileSingleFragmentPlan as dc, cond as dd, contractOfMethodSpec as de, definePlan as df, entityWrites as dg, executeBatchGet as dh, executeBatchWrite as di, executeCommandMethod as dj, executeDelete as dk, executeKeyedBatchGet as dl, executePut as dm, executeQueryMethod as dn, executeRangeFanout as dp, executeTransaction as dq, executeUpdate as dr, from as ds, getEntityWrites as dt, gsi as du, identity as dv, isColumn as dw, isCommandModelContract as dx, isCommandPlan as dy, isContractComposeNode as dz, type BatchExecOptions as e, wholeKeysSentinel as e0, DDBModel as f, type PrimaryKeyOf as g, type RequestContext as h, type Middleware as i, type ReadRequestKind as j, type CtxModel as k, type ReadParams as l, type ReadRequestCtx as m, type RetryPolicy as n, type ExecutionPlanSpec as o, type EntityMetadata as p, type DynamoType as q, type RelationOptions as r, type AggregateValue as s, type SelectBuilderSpec as t, type RawCondition as u, type TransactionSpec as v, type Manifest as w, type RetryOverride as x, type ExecutionPlan as y, type FieldMetadata as z };
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 };