@rotorsoft/act 1.32.30 → 1.33.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.
- package/README.md +0 -1
- package/dist/.tsbuildinfo +1 -1
- package/dist/@types/act.d.ts +96 -385
- package/dist/@types/act.d.ts.map +1 -1
- package/dist/@types/adapters/in-memory-store.d.ts.map +1 -1
- package/dist/@types/builders/act-builder.d.ts +2 -115
- package/dist/@types/builders/act-builder.d.ts.map +1 -1
- package/dist/@types/builders/builder-utils.d.ts +1 -1
- package/dist/@types/builders/builder-utils.d.ts.map +1 -1
- package/dist/@types/builders/event-builder.d.ts +5 -5
- package/dist/@types/builders/event-builder.d.ts.map +1 -1
- package/dist/@types/builders/merge.d.ts +1 -1
- package/dist/@types/builders/merge.d.ts.map +1 -1
- package/dist/@types/builders/projection-builder.d.ts +2 -144
- package/dist/@types/builders/projection-builder.d.ts.map +1 -1
- package/dist/@types/builders/slice-builder.d.ts +2 -71
- package/dist/@types/builders/slice-builder.d.ts.map +1 -1
- package/dist/@types/builders/state-builder.d.ts +2 -376
- package/dist/@types/builders/state-builder.d.ts.map +1 -1
- package/dist/@types/config.d.ts +6 -43
- package/dist/@types/config.d.ts.map +1 -1
- package/dist/@types/csv.d.ts +1 -15
- package/dist/@types/csv.d.ts.map +1 -1
- package/dist/@types/index.d.ts +0 -1
- package/dist/@types/index.d.ts.map +1 -1
- package/dist/@types/internal/autoclose-policy.d.ts +1 -59
- package/dist/@types/internal/autoclose-policy.d.ts.map +1 -1
- package/dist/@types/internal/autoclose-reaction.d.ts +1 -2
- package/dist/@types/internal/autoclose-reaction.d.ts.map +1 -1
- package/dist/@types/internal/autoclose-window.d.ts +1 -1
- package/dist/@types/internal/autoclose-window.d.ts.map +1 -1
- package/dist/@types/internal/circuit-breaker.d.ts +1 -2
- package/dist/@types/internal/circuit-breaker.d.ts.map +1 -1
- package/dist/@types/{builders/build-classify.d.ts → internal/classify.d.ts} +2 -15
- package/dist/@types/internal/classify.d.ts.map +1 -0
- package/dist/@types/internal/close-cycle.d.ts.map +1 -1
- package/dist/@types/internal/config.d.ts +2 -42
- package/dist/@types/internal/config.d.ts.map +1 -1
- package/dist/@types/internal/correlate-cycle.d.ts +1 -1
- package/dist/@types/internal/correlate-cycle.d.ts.map +1 -1
- package/dist/@types/internal/{correlator.d.ts → correlation-id.d.ts} +1 -1
- package/dist/@types/internal/correlation-id.d.ts.map +1 -0
- package/dist/@types/internal/defer-config.d.ts.map +1 -1
- package/dist/@types/internal/drain-cycle.d.ts +1 -1
- package/dist/@types/internal/drain-cycle.d.ts.map +1 -1
- package/dist/@types/internal/{drain.d.ts → drain-ops.d.ts} +1 -1
- package/dist/@types/internal/drain-ops.d.ts.map +1 -0
- package/dist/@types/internal/event-sourcing.d.ts +1 -8
- package/dist/@types/internal/event-sourcing.d.ts.map +1 -1
- package/dist/@types/internal/{sensitive.d.ts → pii.d.ts} +4 -71
- package/dist/@types/internal/pii.d.ts.map +1 -0
- package/dist/@types/internal/projection-fold.d.ts +1 -2
- package/dist/@types/internal/projection-fold.d.ts.map +1 -1
- package/dist/@types/internal/{close-signal.d.ts → reaction-dispatch.d.ts} +47 -2
- package/dist/@types/internal/reaction-dispatch.d.ts.map +1 -0
- package/dist/@types/internal/report-once.d.ts +1 -1
- package/dist/@types/internal/settle.d.ts +21 -12
- package/dist/@types/internal/settle.d.ts.map +1 -1
- package/dist/@types/internal/tracing.d.ts +3 -3
- package/dist/@types/internal/tracing.d.ts.map +1 -1
- package/dist/@types/ports.d.ts +3 -43
- package/dist/@types/ports.d.ts.map +1 -1
- package/dist/@types/scoped.d.ts +1 -6
- package/dist/@types/scoped.d.ts.map +1 -1
- package/dist/@types/sensitive.d.ts +56 -0
- package/dist/@types/sensitive.d.ts.map +1 -0
- package/dist/@types/test/index.d.ts +1 -0
- package/dist/@types/test/index.d.ts.map +1 -1
- package/dist/@types/test/sandbox.d.ts +2 -26
- package/dist/@types/test/sandbox.d.ts.map +1 -1
- package/dist/@types/types/act.d.ts +214 -0
- package/dist/@types/types/act.d.ts.map +1 -0
- package/dist/@types/types/action.d.ts +69 -2
- package/dist/@types/types/action.d.ts.map +1 -1
- package/dist/@types/types/builders.d.ts +721 -0
- package/dist/@types/types/builders.d.ts.map +1 -0
- package/dist/@types/types/errors.d.ts +31 -1
- package/dist/@types/types/errors.d.ts.map +1 -1
- package/dist/@types/types/index.d.ts +38 -0
- package/dist/@types/types/index.d.ts.map +1 -1
- package/dist/@types/types/ports.d.ts +20 -18
- package/dist/@types/types/ports.d.ts.map +1 -1
- package/dist/@types/types/reaction.d.ts +11 -1
- package/dist/@types/types/reaction.d.ts.map +1 -1
- package/dist/@types/types/sandbox.d.ts +31 -0
- package/dist/@types/types/sandbox.d.ts.map +1 -0
- package/dist/@types/types/schemas.d.ts +12 -17
- package/dist/@types/types/schemas.d.ts.map +1 -1
- package/dist/chunk-NNWP65YM.cjs +264 -0
- package/dist/chunk-NNWP65YM.cjs.map +1 -0
- package/dist/{chunk-X3MMQIIP.js → chunk-NYH6FNTM.js} +174 -22
- package/dist/chunk-NYH6FNTM.js.map +1 -0
- package/dist/{chunk-HHKLVRR4.js → chunk-TJ4JRRUR.js} +38 -57
- package/dist/chunk-TJ4JRRUR.js.map +1 -0
- package/dist/chunk-YKL44PYX.cjs +1233 -0
- package/dist/chunk-YKL44PYX.cjs.map +1 -0
- package/dist/index.cjs +1954 -3440
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +911 -1063
- package/dist/index.js.map +1 -1
- package/dist/test/index.cjs +16 -1113
- package/dist/test/index.cjs.map +1 -1
- package/dist/test/index.js +2 -2
- package/dist/test/index.js.map +1 -1
- package/dist/types/index.cjs +58 -276
- package/dist/types/index.cjs.map +1 -1
- package/dist/types/index.js +15 -1
- package/package.json +2 -2
- package/dist/@types/builders/build-classify.d.ts.map +0 -1
- package/dist/@types/builders/builder-base.d.ts +0 -70
- package/dist/@types/builders/builder-base.d.ts.map +0 -1
- package/dist/@types/builders/reaction-builder.d.ts +0 -55
- package/dist/@types/builders/reaction-builder.d.ts.map +0 -1
- package/dist/@types/internal/close-signal.d.ts.map +0 -1
- package/dist/@types/internal/correlator.d.ts.map +0 -1
- package/dist/@types/internal/defer-signal.d.ts +0 -32
- package/dist/@types/internal/defer-signal.d.ts.map +0 -1
- package/dist/@types/internal/drain.d.ts.map +0 -1
- package/dist/@types/internal/index.d.ts +0 -38
- package/dist/@types/internal/index.d.ts.map +0 -1
- package/dist/@types/internal/sensitive.d.ts.map +0 -1
- package/dist/chunk-HHKLVRR4.js.map +0 -1
- package/dist/chunk-X3MMQIIP.js.map +0 -1
|
@@ -0,0 +1,721 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public contracts of the fluent builders: what `state()`, `slice()`,
|
|
3
|
+
* `projection()` and `act()` return at each step of the chain. The
|
|
4
|
+
* builders in `../builders/` implement them.
|
|
5
|
+
*/
|
|
6
|
+
import type { ZodType } from "zod";
|
|
7
|
+
import type { Act } from "../act.js";
|
|
8
|
+
import type { ActionHandler, ActionOptions, ActOptions, Actor, AutocloseArchiver, AutoclosePolicy, BatchHandler, CacheEntry, Committed, DeferWhen, EventRegister, FoldOptions, IAct, Invariant, LaneConfig, PatchHandlers, ReactionOptions, ReactionResolver, Schema, SchemaRegister, Schemas, Snapshot, State, ZodTypes } from "./index.js";
|
|
9
|
+
import type { DEFAULT_LANE } from "./reaction.js";
|
|
10
|
+
/**
|
|
11
|
+
* The `.do(handler)` step, parameterized by the builder it returns (`TReturn`).
|
|
12
|
+
* Identical for both builders modulo that return type.
|
|
13
|
+
*/
|
|
14
|
+
export type ReactionDo<TReturn, TEvents extends Schemas, TActions extends Schemas, TActor extends Actor, TLanes extends string, TKey extends keyof TEvents> = (handler: (event: Committed<TEvents, TKey>, stream: string, app: IAct<TEvents, TActions, TActor>) => Promise<Snapshot<Schema, TEvents> | void>, options?: Partial<ReactionOptions>) => TReturn & {
|
|
15
|
+
to: (resolver: ReactionResolver<TEvents, TKey, TLanes> | string) => TReturn;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* The object `.on(event)` returns: run immediately with `.do(...)`, or hold
|
|
19
|
+
* with `.defer(when).do(...)`. Parameterized by the return builder so
|
|
20
|
+
* `act()` and `slice()` share one shape.
|
|
21
|
+
*/
|
|
22
|
+
export type ReactionOn<TReturn, TEvents extends Schemas, TActions extends Schemas, TActor extends Actor, TLanes extends string, TKey extends keyof TEvents> = {
|
|
23
|
+
do: ReactionDo<TReturn, TEvents, TActions, TActor, TLanes, TKey>;
|
|
24
|
+
defer: (schedule: DeferWhen | ((event: Committed<TEvents, TKey>) => DeferWhen)) => {
|
|
25
|
+
do: ReactionDo<TReturn, TEvents, TActions, TActor, TLanes, TKey>;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Builder members that return `Self` unchanged — shared by both builders.
|
|
30
|
+
*
|
|
31
|
+
* @template Self - the concrete builder type returned for chaining
|
|
32
|
+
* @template TEvents - event schemas
|
|
33
|
+
* @template TActions - action schemas
|
|
34
|
+
* @template TActor - actor type
|
|
35
|
+
* @template TLanes - declared lane-name union
|
|
36
|
+
*/
|
|
37
|
+
export interface BuilderBase<Self, TEvents extends Schemas, TActions extends Schemas, TActor extends Actor, TLanes extends string> {
|
|
38
|
+
/**
|
|
39
|
+
* Registers a standalone projection. Its events must be a subset of events
|
|
40
|
+
* already registered via `.withState()` (or `.withSlice()` on the Act
|
|
41
|
+
* builder); handlers keep their `(event, stream)` signature.
|
|
42
|
+
*/
|
|
43
|
+
withProjection: <TNewEvents extends Schemas>(projection: [Exclude<keyof TNewEvents, keyof TEvents>] extends [never] ? Projection<TNewEvents> : never) => Self;
|
|
44
|
+
/**
|
|
45
|
+
* Begins defining a reaction. Chain `.do(...)` to run immediately, or
|
|
46
|
+
* `.defer(when).do(...)` to hold the reaction until a schedule is due.
|
|
47
|
+
* `.to(...)` follows `.do(...)` in both cases and, for a deferred
|
|
48
|
+
* reaction, routes it onto its own target so the hold doesn't stall the
|
|
49
|
+
* stream's other reactions.
|
|
50
|
+
*/
|
|
51
|
+
on: <TKey extends keyof TEvents>(event: TKey) => ReactionOn<Self, TEvents, TActions, TActor, TLanes, TKey>;
|
|
52
|
+
/** The registered event schemas and their reaction maps. */
|
|
53
|
+
readonly events: EventRegister<TEvents>;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Fluent builder interface for composing event-sourced applications.
|
|
57
|
+
*
|
|
58
|
+
* Provides a chainable API for:
|
|
59
|
+
* - Registering states via `.withState()`
|
|
60
|
+
* - Registering slices via `.withSlice()`
|
|
61
|
+
* - Registering projections via `.withProjection()`
|
|
62
|
+
* - Locking a custom actor type via `.withActor<TActor>()`
|
|
63
|
+
* - Declaring drain lanes via `.withLane({name, ...})`
|
|
64
|
+
* - Defining event reactions via `.on()` → `.do()` → `.to()`
|
|
65
|
+
* - Building the orchestrator via `.build()`
|
|
66
|
+
*
|
|
67
|
+
* @template TSchemaReg - Schema register for states (maps action names to state schemas)
|
|
68
|
+
* @template TEvents - Event schemas (maps event names to event data schemas)
|
|
69
|
+
* @template TActions - Action schemas (maps action names to action payload schemas)
|
|
70
|
+
* @template TStateMap - Map of state names to state schemas
|
|
71
|
+
* @template TActor - Actor type extending base Actor
|
|
72
|
+
* @template TLanes - Union of declared lane names. Narrowed by
|
|
73
|
+
* `.withLane({name})` calls so `.to({lane})` and `ActOptions.onlyLanes`
|
|
74
|
+
* reject typos at compile time. Starts at `"default"`.
|
|
75
|
+
*
|
|
76
|
+
* @see {@link act} for usage examples
|
|
77
|
+
* @see {@link Act} for the built orchestrator API
|
|
78
|
+
*/
|
|
79
|
+
export interface ActBuilder<TSchemaReg extends SchemaRegister<TActions>, TEvents extends Schemas, TActions extends Schemas, TStateMap extends Record<string, Schema> = {}, TActor extends Actor = Actor, TLanes extends string = typeof DEFAULT_LANE> extends BuilderBase<ActBuilder<TSchemaReg, TEvents, TActions, TStateMap, TActor, TLanes>, TEvents, TActions, TActor, TLanes> {
|
|
80
|
+
/**
|
|
81
|
+
* Registers a state definition with the builder.
|
|
82
|
+
*
|
|
83
|
+
* State names, action names, and event names must be unique across the
|
|
84
|
+
* application (partial states with the same name are merged automatically).
|
|
85
|
+
*
|
|
86
|
+
* @throws {Error} If duplicate action or event names are detected
|
|
87
|
+
*/
|
|
88
|
+
withState: <TNewState extends Schema, TNewEvents extends Schemas, TNewActions extends Schemas, TNewName extends string = string>(state: State<TNewState, TNewEvents, TNewActions, TNewName>) => ActBuilder<TSchemaReg & {
|
|
89
|
+
[K in keyof TNewActions]: TNewState;
|
|
90
|
+
}, TEvents & TNewEvents, TActions & TNewActions, TStateMap & {
|
|
91
|
+
[K in TNewName]: TNewState;
|
|
92
|
+
}, TActor, TLanes>;
|
|
93
|
+
/**
|
|
94
|
+
* Registers a slice with the builder.
|
|
95
|
+
*
|
|
96
|
+
* Merges all the slice's states and reactions into the application.
|
|
97
|
+
* State names, action names, and event names must be unique across the
|
|
98
|
+
* application (partial states with the same name are merged automatically).
|
|
99
|
+
*
|
|
100
|
+
* @throws {Error} If duplicate action or event names are detected
|
|
101
|
+
*/
|
|
102
|
+
withSlice: <TNewSchemaReg extends SchemaRegister<TNewActions>, TNewEvents extends Schemas, TNewActions extends Schemas, TNewMap extends Record<string, Schema>, TNewLanes extends string>(slice: Slice<TNewSchemaReg, TNewEvents, TNewActions, TNewMap, Actor, TNewLanes>) => ActBuilder<TSchemaReg & TNewSchemaReg, TEvents & TNewEvents, TActions & TNewActions, TStateMap & TNewMap, TActor, TLanes | TNewLanes>;
|
|
103
|
+
/**
|
|
104
|
+
* Locks a custom actor type for this application.
|
|
105
|
+
*
|
|
106
|
+
* This is a pure type-level method — it returns the same builder at
|
|
107
|
+
* runtime but narrows the `TActor` generic so that `app.do()` and
|
|
108
|
+
* reaction dispatchers require the richer actor shape.
|
|
109
|
+
*
|
|
110
|
+
* @template TNewActor - Custom actor type extending base Actor
|
|
111
|
+
* @returns The same builder with `TActor` locked to `TNewActor`
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* ```typescript
|
|
115
|
+
* type MyActor = { id: string; name: string; role: string; tenantId: string };
|
|
116
|
+
*
|
|
117
|
+
* const app = act()
|
|
118
|
+
* .withActor<MyActor>()
|
|
119
|
+
* .withState(Counter)
|
|
120
|
+
* .build();
|
|
121
|
+
*
|
|
122
|
+
* // Now app.do() requires MyActor in the target
|
|
123
|
+
* await app.do("increment", {
|
|
124
|
+
* stream: "counter-1",
|
|
125
|
+
* actor: { id: "1", name: "Alice", role: "admin", tenantId: "t1" }
|
|
126
|
+
* }, { by: 5 });
|
|
127
|
+
* ```
|
|
128
|
+
*/
|
|
129
|
+
withActor: <TNewActor extends Actor>() => ActBuilder<TSchemaReg, TEvents, TActions, TStateMap, TNewActor, TLanes>;
|
|
130
|
+
/**
|
|
131
|
+
* Declares a drain lane. Lane name narrows `TLanes` so
|
|
132
|
+
* `.to({lane})` and `ActOptions.onlyLanes` type-check against it.
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* ```typescript
|
|
136
|
+
* const app = act()
|
|
137
|
+
* .withState(Counter)
|
|
138
|
+
* .withLane({ name: "slow", leaseMillis: 60_000, streamLimit: 5 })
|
|
139
|
+
* .on("OrderConfirmed")
|
|
140
|
+
* .do(deliverWebhook)
|
|
141
|
+
* .to({ target: "webhooks-out", lane: "slow" })
|
|
142
|
+
* .build();
|
|
143
|
+
* ```
|
|
144
|
+
*/
|
|
145
|
+
withLane: <const TConfig extends LaneConfig>(config: TConfig) => ActBuilder<TSchemaReg, TEvents, TActions, TStateMap, TActor, TLanes | TConfig["name"]>;
|
|
146
|
+
/**
|
|
147
|
+
* Builds and returns the Act orchestrator instance.
|
|
148
|
+
*
|
|
149
|
+
* @param options - Optional runtime overrides (see {@link ActOptions}).
|
|
150
|
+
* `options.onlyLanes` is narrowed to the declared `TLanes` union, so
|
|
151
|
+
* `onlyLanes: ["typo"]` is a compile error when the lane wasn't
|
|
152
|
+
* declared via `.withLane(...)`.
|
|
153
|
+
* @returns The Act orchestrator instance
|
|
154
|
+
*
|
|
155
|
+
* @see {@link Act} for available orchestrator methods
|
|
156
|
+
*/
|
|
157
|
+
build: (options?: ActOptions<TLanes>) => Act<TSchemaReg, TEvents, TActions, TStateMap, TActor>;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Builder interface for defining a state with event sourcing.
|
|
161
|
+
*
|
|
162
|
+
* Provides a fluent API to configure the initial state, event types,
|
|
163
|
+
* and event handlers (reducers) before moving to action configuration.
|
|
164
|
+
*
|
|
165
|
+
* @template TState - State schema type
|
|
166
|
+
* @template TName - State name literal type
|
|
167
|
+
*
|
|
168
|
+
* @see {@link state} for usage examples
|
|
169
|
+
* @see {@link ActionBuilder} for action configuration
|
|
170
|
+
*/
|
|
171
|
+
export type StateBuilder<TState extends Schema, TName extends string = string> = {
|
|
172
|
+
/**
|
|
173
|
+
* Defines the initial state for new state instances.
|
|
174
|
+
*
|
|
175
|
+
* The init function is called when a new stream is created (first event).
|
|
176
|
+
* It can accept initial data or return a default state.
|
|
177
|
+
*
|
|
178
|
+
* @param init - Function returning the initial state
|
|
179
|
+
* @returns A builder with `.emits()` to declare event types
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* ```typescript
|
|
183
|
+
* .init(() => ({ count: 0, created: new Date() }))
|
|
184
|
+
* ```
|
|
185
|
+
*/
|
|
186
|
+
init: (init: () => Readonly<TState>) => {
|
|
187
|
+
/**
|
|
188
|
+
* Declares the event types that this state can emit.
|
|
189
|
+
*
|
|
190
|
+
* Events represent facts that have happened - they should be named in past tense.
|
|
191
|
+
* Each event is defined with a Zod schema for type safety and runtime validation.
|
|
192
|
+
*
|
|
193
|
+
* @template TEvents - Event schemas type
|
|
194
|
+
* @param events - Object mapping event names to Zod schemas
|
|
195
|
+
* @returns An ActionBuilder (with optional `.patch()` to override specific reducers)
|
|
196
|
+
*
|
|
197
|
+
* @example
|
|
198
|
+
* ```typescript
|
|
199
|
+
* .emits({
|
|
200
|
+
* Incremented: z.object({ amount: z.number() }),
|
|
201
|
+
* Decremented: z.object({ amount: z.number() }),
|
|
202
|
+
* Reset: z.object({})
|
|
203
|
+
* })
|
|
204
|
+
* ```
|
|
205
|
+
*/
|
|
206
|
+
emits: <TEvents extends Schemas>(events: ZodTypes<TEvents>) => ActionBuilder<TState, TEvents, {}, TName> & {
|
|
207
|
+
/**
|
|
208
|
+
* Overrides specific event reducers. Events without a custom patch
|
|
209
|
+
* default to passthrough: `({ data }) => data` (event data merges
|
|
210
|
+
* into state).
|
|
211
|
+
*
|
|
212
|
+
* @param patch - Partial map of event names to patch handler functions
|
|
213
|
+
* @returns An ActionBuilder for defining actions
|
|
214
|
+
*
|
|
215
|
+
* @example Only override the events that need custom logic
|
|
216
|
+
* ```typescript
|
|
217
|
+
* .emits({ TicketOpened, TicketClosed, TicketResolved })
|
|
218
|
+
* .patch({
|
|
219
|
+
* TicketOpened: ({ data }) => {
|
|
220
|
+
* const { message, messageId, userId, ...other } = data;
|
|
221
|
+
* return { ...other, userId, messages: { [messageId]: { ... } } };
|
|
222
|
+
* },
|
|
223
|
+
* })
|
|
224
|
+
* // TicketClosed and TicketResolved use passthrough
|
|
225
|
+
* ```
|
|
226
|
+
*/
|
|
227
|
+
patch: (patch: Partial<PatchHandlers<TState, TEvents>>) => ActionBuilder<TState, TEvents, {}, TName>;
|
|
228
|
+
};
|
|
229
|
+
};
|
|
230
|
+
};
|
|
231
|
+
/** Helper: a single-key record mapping a state name to its Zod schema. */
|
|
232
|
+
export type StateEntry<TKey extends string = string, TState extends Schema = Schema> = {
|
|
233
|
+
[P in TKey]: ZodType<TState>;
|
|
234
|
+
};
|
|
235
|
+
/** Helper: a single-key record mapping an action name to its Zod schema. */
|
|
236
|
+
export type ActionEntry<TKey extends string = string, TNewActions extends Schema = Schema> = {
|
|
237
|
+
[P in TKey]: ZodType<TNewActions>;
|
|
238
|
+
};
|
|
239
|
+
/**
|
|
240
|
+
* Builder interface for defining actions (commands) on a state.
|
|
241
|
+
*
|
|
242
|
+
* Actions represent user/system intents to modify state. Each action is validated
|
|
243
|
+
* against a schema, can have business rule invariants, and must emit one or more events.
|
|
244
|
+
*
|
|
245
|
+
* @template TState - State schema type
|
|
246
|
+
* @template TEvents - Event schemas type
|
|
247
|
+
* @template TActions - Action schemas type
|
|
248
|
+
* @template TName - State name literal type
|
|
249
|
+
* @template TSnap - `true` once `.snap(...)` has been called. Gates the
|
|
250
|
+
* `.autocloses({ keep })` rolling-window option — a windowed close is
|
|
251
|
+
* meaningless without snapshots, so `keep` only typechecks after
|
|
252
|
+
* `.snap` in the chain.
|
|
253
|
+
*
|
|
254
|
+
* @see {@link state} for complete usage examples
|
|
255
|
+
*/
|
|
256
|
+
export type ActionBuilder<TState extends Schema, TEvents extends Schemas, TActions extends Schemas, TName extends string = string, TSnap extends boolean = false> = {
|
|
257
|
+
/**
|
|
258
|
+
* Defines an action (command) that can be executed on this state.
|
|
259
|
+
*
|
|
260
|
+
* Actions represent intents to change state - they should be named in imperative form
|
|
261
|
+
* (e.g., "CreateUser", "IncrementCounter", "PlaceOrder"). Actions are validated against
|
|
262
|
+
* their schema and must emit at least one event.
|
|
263
|
+
*
|
|
264
|
+
* Pass a `{ ActionName: schema }` record — use shorthand `{ ActionName }`
|
|
265
|
+
* when the variable name matches the action name. The key becomes the
|
|
266
|
+
* action name, the value the Zod schema.
|
|
267
|
+
*
|
|
268
|
+
* Pass an optional second argument to declare a per-action retry
|
|
269
|
+
* policy — the orchestrator retries this action on
|
|
270
|
+
* {@link ConcurrencyError} up to `maxRetries` extra times, applying
|
|
271
|
+
* `backoff` between attempts when set. Omit the argument to keep the
|
|
272
|
+
* current single-attempt behavior (`ConcurrencyError` surfaces on
|
|
273
|
+
* first conflict).
|
|
274
|
+
*
|
|
275
|
+
* @template TKey - Action name (string literal type)
|
|
276
|
+
* @template TNewActions - Action payload schema type
|
|
277
|
+
* @param entry - Single-key record `{ ActionName: schema }`
|
|
278
|
+
* @param options - Optional per-action retry policy
|
|
279
|
+
* ({@link ActionOptions}).
|
|
280
|
+
* @returns An object with `.given()` and `.emit()` for further configuration
|
|
281
|
+
*
|
|
282
|
+
* @example Simple action without invariants
|
|
283
|
+
* ```typescript
|
|
284
|
+
* .on({ increment: z.object({ by: z.number() }) })
|
|
285
|
+
* .emit((action) => ["Incremented", { amount: action.by }])
|
|
286
|
+
* ```
|
|
287
|
+
*/
|
|
288
|
+
on: <TKey extends string, TNewActions extends Schema>(entry: ActionEntry<TKey, TNewActions>, options?: ActionOptions) => {
|
|
289
|
+
/**
|
|
290
|
+
* Adds business rule invariants that must hold before the action can execute.
|
|
291
|
+
*
|
|
292
|
+
* Invariants are checked after loading the current state but before emitting
|
|
293
|
+
* events. Each invariant pairs a `description` with a `valid(state, actor?)`
|
|
294
|
+
* predicate — when a predicate returns `false`, the action throws
|
|
295
|
+
* `InvariantError` carrying the description. All invariants must pass for
|
|
296
|
+
* the action to succeed.
|
|
297
|
+
*
|
|
298
|
+
* @param rules - Array of {@link Invariant} objects (`{ description, valid }`)
|
|
299
|
+
* @returns An object with `.emit()` to finalize the action
|
|
300
|
+
*
|
|
301
|
+
* @example
|
|
302
|
+
* ```typescript
|
|
303
|
+
* .given([
|
|
304
|
+
* { description: "Must be active", valid: (state) => state.status === "active" },
|
|
305
|
+
* { description: "Must be the owner", valid: (state, actor) => state.ownerId === actor?.id }
|
|
306
|
+
* ])
|
|
307
|
+
* ```
|
|
308
|
+
*/
|
|
309
|
+
given: (rules: Invariant<TState>[]) => {
|
|
310
|
+
/**
|
|
311
|
+
* Defines the action handler that emits events.
|
|
312
|
+
*
|
|
313
|
+
* The handler receives the action payload and current state snapshot,
|
|
314
|
+
* and must return one or more events to emit. Events are applied to state
|
|
315
|
+
* via the patch handlers defined earlier.
|
|
316
|
+
*
|
|
317
|
+
* Pass a string event name for passthrough: the action payload becomes
|
|
318
|
+
* the event data directly.
|
|
319
|
+
*
|
|
320
|
+
* @param handler - Function that returns events to emit, or event name string for passthrough
|
|
321
|
+
* @returns The ActionBuilder for chaining more actions
|
|
322
|
+
*
|
|
323
|
+
* @example Custom handler
|
|
324
|
+
* ```typescript
|
|
325
|
+
* .emit((action, snapshot) => {
|
|
326
|
+
* const newBalance = snapshot.state.balance + action.amount;
|
|
327
|
+
* return ["Deposited", { amount: action.amount, newBalance }];
|
|
328
|
+
* })
|
|
329
|
+
* ```
|
|
330
|
+
*/
|
|
331
|
+
emit: {
|
|
332
|
+
/** Custom handler — receives `(action, snapshot)` and returns one
|
|
333
|
+
* or more `[EventName, data]` tuples (or `undefined`). */
|
|
334
|
+
(handler: ActionHandler<TState, TEvents, {
|
|
335
|
+
[P in TKey]: TNewActions;
|
|
336
|
+
}, TKey>): ActionBuilder<TState, TEvents, TActions & {
|
|
337
|
+
[P in TKey]: TNewActions;
|
|
338
|
+
}, TName, TSnap>;
|
|
339
|
+
/** Passthrough — the action payload becomes the event data
|
|
340
|
+
* directly. Must reference an event declared in `.emits()`. */
|
|
341
|
+
(event_name: keyof TEvents & string): ActionBuilder<TState, TEvents, TActions & {
|
|
342
|
+
[P in TKey]: TNewActions;
|
|
343
|
+
}, TName, TSnap>;
|
|
344
|
+
};
|
|
345
|
+
};
|
|
346
|
+
/**
|
|
347
|
+
* Defines the action handler that emits events. Same two overloads as
|
|
348
|
+
* the post-`.given()` form above:
|
|
349
|
+
*
|
|
350
|
+
* - **Function** — receives `(action, snapshot)` and returns one or
|
|
351
|
+
* more `[EventName, data]` tuples (or `undefined`).
|
|
352
|
+
* - **String** — passthrough: the action payload becomes the event
|
|
353
|
+
* data directly. Must reference an event declared in `.emits()`.
|
|
354
|
+
*
|
|
355
|
+
* The two overloads are kept separate (rather than merged into a
|
|
356
|
+
* `handler | string` union) so that TS contextual typing of the
|
|
357
|
+
* function alternative isn't degraded by considering the string
|
|
358
|
+
* branch — under the union form `TState` could collapse to its
|
|
359
|
+
* `Schema` constraint inside the callback.
|
|
360
|
+
*
|
|
361
|
+
* @example Passthrough (action payload = event data)
|
|
362
|
+
* ```typescript
|
|
363
|
+
* .emit("Incremented")
|
|
364
|
+
* ```
|
|
365
|
+
*/
|
|
366
|
+
emit: {
|
|
367
|
+
(handler: ActionHandler<TState, TEvents, {
|
|
368
|
+
[P in TKey]: TNewActions;
|
|
369
|
+
}, TKey>): ActionBuilder<TState, TEvents, TActions & {
|
|
370
|
+
[P in TKey]: TNewActions;
|
|
371
|
+
}, TName, TSnap>;
|
|
372
|
+
(event_name: keyof TEvents & string): ActionBuilder<TState, TEvents, TActions & {
|
|
373
|
+
[P in TKey]: TNewActions;
|
|
374
|
+
}, TName, TSnap>;
|
|
375
|
+
};
|
|
376
|
+
};
|
|
377
|
+
/**
|
|
378
|
+
* Defines a snapshotting strategy to optimize state reconstruction.
|
|
379
|
+
*
|
|
380
|
+
* Snapshots store the current state at a point in time, allowing faster state loading
|
|
381
|
+
* by avoiding replaying all events from the beginning. The snap function is called
|
|
382
|
+
* after each event is applied and should return `true` when a snapshot should be taken.
|
|
383
|
+
*
|
|
384
|
+
* @param snap - Predicate function that returns true when a snapshot should be taken
|
|
385
|
+
* @returns The ActionBuilder for chaining
|
|
386
|
+
*
|
|
387
|
+
* @example Snapshot every 10 events
|
|
388
|
+
* ```typescript
|
|
389
|
+
* .snap((snapshot) => snapshot.patches >= 10)
|
|
390
|
+
* ```
|
|
391
|
+
*/
|
|
392
|
+
snap: (snap: (snapshot: Snapshot<TState, TEvents>) => boolean) => ActionBuilder<TState, TEvents, TActions, TName, true>;
|
|
393
|
+
/**
|
|
394
|
+
* Declares the disclosure predicate for `sensitive(...)`-marked event
|
|
395
|
+
* fields. Gates external reads: returning `true` allows the actor to see
|
|
396
|
+
* plaintext on the event; returning `false` substitutes `"[REDACTED]"`.
|
|
397
|
+
* When absent, the framework default-denies on every external read —
|
|
398
|
+
* fail-safe.
|
|
399
|
+
*
|
|
400
|
+
* One predicate per state. A second `.discloses(...)` call replaces the
|
|
401
|
+
* first (same shape as snapshots being state-level, not per-event).
|
|
402
|
+
*
|
|
403
|
+
* The predicate receives the full event including merged PII so it can
|
|
404
|
+
* branch on the payload itself (e.g.
|
|
405
|
+
* `event.data.ownerId === actor.id`). Reducers, projections, and
|
|
406
|
+
* reactions are unaffected — they follow separate visibility rules.
|
|
407
|
+
*
|
|
408
|
+
* @param disclose - Predicate `(event, actor) => boolean`. `true` =
|
|
409
|
+
* plaintext, `false` = `"[REDACTED]"` substitution.
|
|
410
|
+
* @returns The ActionBuilder for chaining.
|
|
411
|
+
*
|
|
412
|
+
* @example Owner-or-admin disclosure
|
|
413
|
+
* ```typescript
|
|
414
|
+
* state({ User: userSchema })
|
|
415
|
+
* .init(() => ({ ... }))
|
|
416
|
+
* .emits({ UserRegistered: z.object({ email: sensitive(z.string()) }) })
|
|
417
|
+
* .discloses((event, actor) =>
|
|
418
|
+
* actor.id === event.stream || actor.roles?.includes("admin"))
|
|
419
|
+
* ```
|
|
420
|
+
*/
|
|
421
|
+
discloses: (disclose: (event: Committed<TEvents, keyof TEvents & string>, actor: Actor) => boolean) => ActionBuilder<TState, TEvents, TActions, TName, TSnap>;
|
|
422
|
+
/**
|
|
423
|
+
* Declares the online close predicate for this state. The
|
|
424
|
+
* orchestrator's autoclose cycle iterates the state's streams once
|
|
425
|
+
* per tick and calls the predicate per candidate; truthy results are
|
|
426
|
+
* scheduled for atomic truncate-and-seed via `Store.truncate` on the
|
|
427
|
+
* next batch.
|
|
428
|
+
*
|
|
429
|
+
* One predicate per state. A second `.autocloses(...)` call replaces
|
|
430
|
+
* the first (same shape as `.snap` / `.discloses` — state-level, not
|
|
431
|
+
* per-event). Absent → the state opts out of online close entirely;
|
|
432
|
+
* the cycle skips it and pays zero per-tick cost for it.
|
|
433
|
+
*
|
|
434
|
+
* Pass a declarative {@link AutoclosePolicy} object literal covering the
|
|
435
|
+
* three operational pressure points (`after`, `is`, `reaches`). Top-level
|
|
436
|
+
* fields combine with AND; an optional `or: {...}` block opens an
|
|
437
|
+
* alternative OR path. Validated via Zod at build time; misconfiguration
|
|
438
|
+
* throws before `act().build()` completes.
|
|
439
|
+
*
|
|
440
|
+
* Under the hood this compiles to a synthesized reaction that runs
|
|
441
|
+
* on a per-aggregate synthetic stream: it defers to `head.created + the
|
|
442
|
+
* policy's min after` while the cooldown holds and closes the stream once
|
|
443
|
+
* the policy matches. There is no background sweep.
|
|
444
|
+
*
|
|
445
|
+
* There is no function-predicate form: an opaque predicate has no
|
|
446
|
+
* derivable due-time or terminal event to react to. For conditions the
|
|
447
|
+
* declarative form can't express, call `app.close(...)` from your own logic.
|
|
448
|
+
*
|
|
449
|
+
* @param policy The declarative {@link AutoclosePolicy} bag.
|
|
450
|
+
* @returns The ActionBuilder for chaining.
|
|
451
|
+
*
|
|
452
|
+
* @example Declarative — cooldown after terminal (a Ticket closes
|
|
453
|
+
* 90 days after resolution).
|
|
454
|
+
* ```typescript
|
|
455
|
+
* .autocloses({ is: "TicketResolved", after: { days: 90 } })
|
|
456
|
+
* ```
|
|
457
|
+
*/
|
|
458
|
+
autocloses: (policy: [TSnap] extends [true] ? AutoclosePolicy : Omit<AutoclosePolicy, "keep"> & {
|
|
459
|
+
/** Rolling-window retention requires `.snap(...)` earlier in
|
|
460
|
+
* the builder chain — a windowed close prunes behind a real
|
|
461
|
+
* snapshot, so a state that never snapshots has nothing to
|
|
462
|
+
* prune behind. */
|
|
463
|
+
keep?: never;
|
|
464
|
+
}) => ActionBuilder<TState, TEvents, TActions, TName, TSnap>;
|
|
465
|
+
/**
|
|
466
|
+
* Declares the archiver the online close cycle runs **before**
|
|
467
|
+
* truncating a stream this state's `.autocloses(...)` predicate
|
|
468
|
+
* accepted. Hosts use it to write events to durable storage (S3,
|
|
469
|
+
* an analytics warehouse, cold tier) before the tombstone lands,
|
|
470
|
+
* so the truncate doesn't lose history that the operator still
|
|
471
|
+
* needs.
|
|
472
|
+
*
|
|
473
|
+
* Threads into `CloseTarget.archive` via the same plumbing
|
|
474
|
+
* `app.close({ stream, archive })` already uses — the cycle holds
|
|
475
|
+
* the stream's guard while the archiver runs, and a thrown
|
|
476
|
+
* archiver leaves the stream guarded but un-truncated. No partial
|
|
477
|
+
* truncate state, no data loss. It is not retried automatically;
|
|
478
|
+
* `app.close([{ stream, archive }])` resumes the guarded stream.
|
|
479
|
+
*
|
|
480
|
+
* One archiver per state. A second `.archives(...)` call replaces
|
|
481
|
+
* the first (same shape as `.snap` / `.discloses` /
|
|
482
|
+
* `.autocloses`). Absent → the cycle truncates without an archive
|
|
483
|
+
* step.
|
|
484
|
+
*
|
|
485
|
+
* @param archive `(stream, head) => Promise<void>`. Runs while
|
|
486
|
+
* the stream is locked against new writes; the truncate runs
|
|
487
|
+
* immediately after a successful resolve.
|
|
488
|
+
* @returns The ActionBuilder for chaining.
|
|
489
|
+
*
|
|
490
|
+
* @example Archive to S3 before truncate.
|
|
491
|
+
* ```typescript
|
|
492
|
+
* state({ Ticket: ticketSchema })
|
|
493
|
+
* .emits({ TicketOpened, TicketResolved })
|
|
494
|
+
* // ...
|
|
495
|
+
* .autocloses({ is: "TicketResolved" })
|
|
496
|
+
* .archives(async (stream) => {
|
|
497
|
+
* const events = await loadEvents(stream);
|
|
498
|
+
* await s3.upload(`tickets/${stream}.jsonl`, events);
|
|
499
|
+
* })
|
|
500
|
+
* ```
|
|
501
|
+
*/
|
|
502
|
+
archives: (archive: AutocloseArchiver<TEvents>) => ActionBuilder<TState, TEvents, TActions, TName, TSnap>;
|
|
503
|
+
/**
|
|
504
|
+
* Finalizes and builds the state definition.
|
|
505
|
+
*
|
|
506
|
+
* Call this method after defining all actions, invariants, and patches to create
|
|
507
|
+
* the complete State object that can be registered with Act.
|
|
508
|
+
*
|
|
509
|
+
* @returns The complete strongly-typed State definition
|
|
510
|
+
*
|
|
511
|
+
* @example
|
|
512
|
+
* ```typescript
|
|
513
|
+
* const Counter = state({ Counter: schema })
|
|
514
|
+
* .init(() => ({ count: 0 }))
|
|
515
|
+
* .emits({ Incremented: z.object({ amount: z.number() }) })
|
|
516
|
+
* .patch({ Incremented: ({ data }, state) => ({ count: state.count + data.amount }) })
|
|
517
|
+
* .on({ increment: z.object({ by: z.number() }) })
|
|
518
|
+
* .emit((action) => ["Incremented", { amount: action.by }])
|
|
519
|
+
* .build(); // Returns State<TState, TEvents, TActions, TName>
|
|
520
|
+
* ```
|
|
521
|
+
*/
|
|
522
|
+
build: () => State<TState, TEvents, TActions, TName>;
|
|
523
|
+
};
|
|
524
|
+
/**
|
|
525
|
+
* A self-contained functional slice grouping partial states with their
|
|
526
|
+
* scoped reactions. Slices are composed into an Act orchestrator via
|
|
527
|
+
* `act().withSlice(slice)`.
|
|
528
|
+
*
|
|
529
|
+
* @template TSchemaReg - Schema register for states
|
|
530
|
+
* @template TEvents - Event schemas from this slice's states
|
|
531
|
+
* @template TActions - Action schemas from this slice's states
|
|
532
|
+
* @template TStateMap - Map of state names to state schemas
|
|
533
|
+
* @template TActor - Actor type extending base Actor
|
|
534
|
+
*/
|
|
535
|
+
export type Slice<TSchemaReg extends SchemaRegister<TActions>, TEvents extends Schemas, TActions extends Schemas, TStateMap extends Record<string, Schema> = {}, TActor extends Actor = Actor, TLanes extends string = typeof DEFAULT_LANE> = {
|
|
536
|
+
readonly _tag: "Slice";
|
|
537
|
+
readonly states: Map<string, State<any, any, any>>;
|
|
538
|
+
readonly events: EventRegister<TEvents>;
|
|
539
|
+
readonly projections: ReadonlyArray<Projection<any>>;
|
|
540
|
+
/**
|
|
541
|
+
* Drain lanes declared on this slice via `.withLane(...)`.
|
|
542
|
+
* `act().withSlice(slice)` merges these into the Act's lane set so
|
|
543
|
+
* `.to({lane})` is statically checked at the slice's call site against
|
|
544
|
+
* the lanes the slice itself declared.
|
|
545
|
+
*/
|
|
546
|
+
readonly lanes: ReadonlyArray<LaneConfig>;
|
|
547
|
+
/** @internal phantom field for type-level state schema tracking */
|
|
548
|
+
readonly _S?: TSchemaReg;
|
|
549
|
+
/** @internal phantom field for type-level state name tracking */
|
|
550
|
+
readonly _M?: TStateMap;
|
|
551
|
+
/** @internal phantom field for type-level actor tracking */
|
|
552
|
+
readonly _TActor?: TActor;
|
|
553
|
+
/** @internal phantom field for type-level lane union tracking */
|
|
554
|
+
readonly _TLanes?: TLanes;
|
|
555
|
+
};
|
|
556
|
+
/**
|
|
557
|
+
* Fluent builder interface for composing functional slices.
|
|
558
|
+
*
|
|
559
|
+
* Provides a chainable API for registering states and projections,
|
|
560
|
+
* and defining reactions scoped to the slice's own events.
|
|
561
|
+
*
|
|
562
|
+
* @template TSchemaReg - Schema register for states
|
|
563
|
+
* @template TEvents - Event schemas
|
|
564
|
+
* @template TActions - Action schemas
|
|
565
|
+
* @template TStateMap - Map of state names to state schemas
|
|
566
|
+
* @template TActor - Actor type extending base Actor
|
|
567
|
+
*/
|
|
568
|
+
export interface SliceBuilder<TSchemaReg extends SchemaRegister<TActions>, TEvents extends Schemas, TActions extends Schemas, TStateMap extends Record<string, Schema> = {}, TActor extends Actor = Actor, TLanes extends string = typeof DEFAULT_LANE> extends BuilderBase<SliceBuilder<TSchemaReg, TEvents, TActions, TStateMap, TActor, TLanes>, TEvents, TActions, TActor, TLanes> {
|
|
569
|
+
/**
|
|
570
|
+
* Registers a state definition with the slice.
|
|
571
|
+
*
|
|
572
|
+
* Include every state whose actions your reaction handlers need to
|
|
573
|
+
* dispatch. Duplicate registrations (same state in multiple slices)
|
|
574
|
+
* are handled automatically at composition time.
|
|
575
|
+
*/
|
|
576
|
+
withState: <TNewState extends Schema, TNewEvents extends Schemas, TNewActions extends Schemas, TNewName extends string = string>(state: State<TNewState, TNewEvents, TNewActions, TNewName>) => SliceBuilder<TSchemaReg & {
|
|
577
|
+
[K in keyof TNewActions]: TNewState;
|
|
578
|
+
}, TEvents & TNewEvents, TActions & TNewActions, TStateMap & {
|
|
579
|
+
[K in TNewName]: TNewState;
|
|
580
|
+
}, TActor, TLanes>;
|
|
581
|
+
/**
|
|
582
|
+
* Declares a drain lane on this slice. Merged into the
|
|
583
|
+
* parent Act's lane set by `act().withSlice(slice)`.
|
|
584
|
+
*/
|
|
585
|
+
withLane: <const TConfig extends LaneConfig>(config: TConfig) => SliceBuilder<TSchemaReg, TEvents, TActions, TStateMap, TActor, TLanes | TConfig["name"]>;
|
|
586
|
+
/**
|
|
587
|
+
* Builds and returns the Slice data structure.
|
|
588
|
+
*/
|
|
589
|
+
build: () => Slice<TSchemaReg, TEvents, TActions, TStateMap, TActor, TLanes>;
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* A self-contained projection grouping read-model update handlers.
|
|
593
|
+
* Projections are composed into an Act orchestrator via `act().withProjection(projection)`.
|
|
594
|
+
*
|
|
595
|
+
* @template TEvents - Event schemas handled by this projection
|
|
596
|
+
*/
|
|
597
|
+
export type Projection<TEvents extends Schemas> = {
|
|
598
|
+
readonly _tag: "Projection";
|
|
599
|
+
readonly events: EventRegister<TEvents>;
|
|
600
|
+
readonly target?: string;
|
|
601
|
+
readonly batchHandler?: BatchHandler<TEvents>;
|
|
602
|
+
/**
|
|
603
|
+
* State-fold spec from `.of()`. The builder only records intent — the
|
|
604
|
+
* orchestrator resolves the REGISTRY-MERGED full state at
|
|
605
|
+
* `act().build()` and synthesizes the batch handler there, so the
|
|
606
|
+
* fold always covers every reducer of the state, including partials
|
|
607
|
+
* merged by slices the projection never saw.
|
|
608
|
+
* @internal
|
|
609
|
+
*/
|
|
610
|
+
readonly fold?: {
|
|
611
|
+
readonly name: string;
|
|
612
|
+
readonly flush: (rows: ReadonlyArray<CacheEntry<any>>) => Promise<void>;
|
|
613
|
+
readonly config: Required<FoldOptions>;
|
|
614
|
+
};
|
|
615
|
+
};
|
|
616
|
+
/**
|
|
617
|
+
* The `.of()` continuation: state projections flush the cache layer
|
|
618
|
+
* outward — the rows ARE the streams' {@link CacheEntry} values, one
|
|
619
|
+
* per dirty stream per flush round. Must be an idempotent upsert keyed
|
|
620
|
+
* on `stream` (guard with `event_id` for order safety when a rebuild
|
|
621
|
+
* races a live worker).
|
|
622
|
+
*/
|
|
623
|
+
export type FoldFlush<TState extends Schema, TE extends Schemas> = {
|
|
624
|
+
flush: (handler: (rows: ReadonlyArray<CacheEntry<TState>>) => Promise<void>) => {
|
|
625
|
+
build: () => Projection<TE>;
|
|
626
|
+
};
|
|
627
|
+
};
|
|
628
|
+
/**
|
|
629
|
+
* `.of()` accepts the partials of ONE state (same name, enforced at the
|
|
630
|
+
* type level via `TName`) purely for typing and event registration —
|
|
631
|
+
* the fold itself always runs on the registry-merged full state,
|
|
632
|
+
* resolved at `act().build()`. Passing every partial is required: the
|
|
633
|
+
* orchestrator validates completeness at build and throws on missing
|
|
634
|
+
* events.
|
|
635
|
+
*/
|
|
636
|
+
export type OfSignatures = {
|
|
637
|
+
<TS1 extends Schema, TE1 extends Schemas, TA1 extends Schemas, TN extends string>(s1: State<TS1, TE1, TA1, TN>, options?: FoldOptions): FoldFlush<TS1, TE1>;
|
|
638
|
+
<TS1 extends Schema, TE1 extends Schemas, TA1 extends Schemas, TS2 extends Schema, TE2 extends Schemas, TA2 extends Schemas, TN extends string>(s1: State<TS1, TE1, TA1, TN>, s2: State<TS2, TE2, TA2, TN>, options?: FoldOptions): FoldFlush<TS1 & TS2, TE1 & TE2>;
|
|
639
|
+
<TS1 extends Schema, TE1 extends Schemas, TA1 extends Schemas, TS2 extends Schema, TE2 extends Schemas, TA2 extends Schemas, TS3 extends Schema, TE3 extends Schemas, TA3 extends Schemas, TN extends string>(s1: State<TS1, TE1, TA1, TN>, s2: State<TS2, TE2, TA2, TN>, s3: State<TS3, TE3, TA3, TN>, options?: FoldOptions): FoldFlush<TS1 & TS2 & TS3, TE1 & TE2 & TE3>;
|
|
640
|
+
<TS1 extends Schema, TE1 extends Schemas, TA1 extends Schemas, TS2 extends Schema, TE2 extends Schemas, TA2 extends Schemas, TS3 extends Schema, TE3 extends Schemas, TA3 extends Schemas, TS4 extends Schema, TE4 extends Schemas, TA4 extends Schemas, TN extends string>(s1: State<TS1, TE1, TA1, TN>, s2: State<TS2, TE2, TA2, TN>, s3: State<TS3, TE3, TA3, TN>, s4: State<TS4, TE4, TA4, TN>, options?: FoldOptions): FoldFlush<TS1 & TS2 & TS3 & TS4, TE1 & TE2 & TE3 & TE4>;
|
|
641
|
+
};
|
|
642
|
+
/** Helper: a single-key record mapping an event name to its Zod schema. */
|
|
643
|
+
export type EventEntry<TKey extends string = string, TData extends Schema = Schema> = {
|
|
644
|
+
[P in TKey]: ZodType<TData>;
|
|
645
|
+
};
|
|
646
|
+
/** Infer the handler-result type after registering one event. */
|
|
647
|
+
export type DoResult<TEvents extends Schemas, TKey extends string, TData extends Schema, TTarget extends string | undefined = undefined> = ProjectionBuilder<TEvents & {
|
|
648
|
+
[P in TKey]: TData;
|
|
649
|
+
}, TTarget, true> & {
|
|
650
|
+
to: (resolver: ReactionResolver<TEvents & {
|
|
651
|
+
[P in TKey]: TData;
|
|
652
|
+
}, TKey> | string) => ProjectionBuilder<TEvents & {
|
|
653
|
+
[P in TKey]: TData;
|
|
654
|
+
}, TTarget, true>;
|
|
655
|
+
};
|
|
656
|
+
/**
|
|
657
|
+
* Fluent builder interface for composing projections.
|
|
658
|
+
*
|
|
659
|
+
* When a static target is provided via `projection("target")`, the builder
|
|
660
|
+
* exposes a `.batch()` method for registering a batch handler that processes
|
|
661
|
+
* all events in a single call.
|
|
662
|
+
*
|
|
663
|
+
* @template TEvents - Event schemas
|
|
664
|
+
* @template TTarget - Static target string or undefined
|
|
665
|
+
*/
|
|
666
|
+
export type ProjectionBuilder<TEvents extends Schemas, TTarget extends string | undefined = undefined, THasHandlers extends boolean = false> = {
|
|
667
|
+
/**
|
|
668
|
+
* Begins defining a projection handler for a specific event.
|
|
669
|
+
*
|
|
670
|
+
* Pass a `{ EventName: schema }` record — use shorthand `{ EventName }`
|
|
671
|
+
* when the variable name matches the event name. The key becomes the
|
|
672
|
+
* event name, the value the Zod schema.
|
|
673
|
+
*/
|
|
674
|
+
on: <TKey extends string, TData extends Schema>(entry: EventEntry<TKey, TData>) => {
|
|
675
|
+
do: (handler: (event: Committed<TEvents & {
|
|
676
|
+
[P in TKey]: TData;
|
|
677
|
+
}, TKey>, stream: string) => Promise<void>) => DoResult<TEvents, TKey, TData, TTarget>;
|
|
678
|
+
};
|
|
679
|
+
/**
|
|
680
|
+
* Builds and returns the Projection data structure.
|
|
681
|
+
*/
|
|
682
|
+
build: () => Projection<TEvents>;
|
|
683
|
+
/**
|
|
684
|
+
* The registered event schemas and their reaction maps.
|
|
685
|
+
*/
|
|
686
|
+
readonly events: EventRegister<TEvents>;
|
|
687
|
+
} & (TTarget extends string ? {
|
|
688
|
+
/**
|
|
689
|
+
* Registers a batch handler that processes all events in a single call.
|
|
690
|
+
*
|
|
691
|
+
* Only available on projections with a static target (`projection("target")`).
|
|
692
|
+
* The handler receives a discriminated union of all declared events,
|
|
693
|
+
* enabling bulk DB operations in a single transaction.
|
|
694
|
+
*
|
|
695
|
+
* When defined, the batch handler is always called — even for a single event.
|
|
696
|
+
* Individual `.do()` handlers serve as fallback for projections without `.batch()`.
|
|
697
|
+
*/
|
|
698
|
+
batch: (handler: BatchHandler<TEvents>) => {
|
|
699
|
+
build: () => Projection<TEvents>;
|
|
700
|
+
};
|
|
701
|
+
} & (THasHandlers extends false ? {
|
|
702
|
+
/**
|
|
703
|
+
* Declares a state projection: fold every event of the given
|
|
704
|
+
* state through its own reducers and flush one row per stream —
|
|
705
|
+
* the queryable list of the aggregates themselves.
|
|
706
|
+
*
|
|
707
|
+
* The state is the filter: the projection consumes exactly the
|
|
708
|
+
* state's event register, so in a multi-state app only that
|
|
709
|
+
* state's streams are folded — and every event of a folded
|
|
710
|
+
* stream reaches the reducer. Write amplification tracks the
|
|
711
|
+
* distinct stream count, not the event count; `app.reset`
|
|
712
|
+
* rebuilds in O(streams) upserts.
|
|
713
|
+
*
|
|
714
|
+
* The fluent chain enforces the shape: `.of()` is only offered
|
|
715
|
+
* before any `.on()` handler, and narrows to `.flush()` +
|
|
716
|
+
* `.build()` — a projection either folds a state or declares
|
|
717
|
+
* handlers, never both.
|
|
718
|
+
*/
|
|
719
|
+
of: OfSignatures;
|
|
720
|
+
} : {}) : {});
|
|
721
|
+
//# sourceMappingURL=builders.d.ts.map
|