@rotorsoft/act 0.6.28 → 0.6.29

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.
@@ -41,13 +41,83 @@ type Injector<Port extends Disposable> = (adapter?: Port) => Port;
41
41
  export declare function port<Port extends Disposable>(injector: Injector<Port>): (adapter?: Port) => Port;
42
42
  export declare function disposeAndExit(code?: ExitCode): Promise<void>;
43
43
  /**
44
- * Registers resource disposers that are triggered on process exit.
44
+ * Registers resource cleanup functions for graceful shutdown.
45
45
  *
46
- * @param disposer The disposer function to register
47
- * @returns A function that triggers all registered disposers and terminates the process
46
+ * Disposers are called automatically when the process exits (SIGINT, SIGTERM)
47
+ * or when manually triggered. They execute in reverse registration order,
48
+ * allowing proper cleanup of dependent resources.
48
49
  *
49
- * @example
50
- * dispose(async () => { await myResource.close(); });
50
+ * Act automatically disposes registered stores and adapters. Use this function
51
+ * to register additional cleanup for your own resources (database connections,
52
+ * file handles, timers, etc.).
53
+ *
54
+ * @param disposer - Async function to call during cleanup
55
+ * @returns Function to manually trigger disposal and exit
56
+ *
57
+ * @example Register custom resource cleanup
58
+ * ```typescript
59
+ * import { dispose } from "@rotorsoft/act";
60
+ *
61
+ * const redis = createRedisClient();
62
+ *
63
+ * dispose(async () => {
64
+ * console.log("Closing Redis connection...");
65
+ * await redis.quit();
66
+ * });
67
+ *
68
+ * // On SIGINT/SIGTERM, Redis will be cleaned up automatically
69
+ * ```
70
+ *
71
+ * @example Multiple disposers in order
72
+ * ```typescript
73
+ * import { dispose } from "@rotorsoft/act";
74
+ *
75
+ * const db = connectDatabase();
76
+ * dispose(async () => {
77
+ * console.log("Closing database...");
78
+ * await db.close();
79
+ * });
80
+ *
81
+ * const cache = connectCache();
82
+ * dispose(async () => {
83
+ * console.log("Closing cache...");
84
+ * await cache.disconnect();
85
+ * });
86
+ *
87
+ * // On exit: cache closes first, then database
88
+ * ```
89
+ *
90
+ * @example Manual cleanup trigger
91
+ * ```typescript
92
+ * import { dispose } from "@rotorsoft/act";
93
+ *
94
+ * const shutdown = dispose(async () => {
95
+ * await cleanup();
96
+ * });
97
+ *
98
+ * // Manually trigger cleanup and exit
99
+ * process.on("SIGUSR2", async () => {
100
+ * console.log("Manual shutdown requested");
101
+ * await shutdown("EXIT");
102
+ * });
103
+ * ```
104
+ *
105
+ * @example With error handling
106
+ * ```typescript
107
+ * import { dispose } from "@rotorsoft/act";
108
+ *
109
+ * dispose(async () => {
110
+ * try {
111
+ * await expensiveCleanup();
112
+ * } catch (error) {
113
+ * console.error("Cleanup failed:", error);
114
+ * // Error doesn't prevent other disposers from running
115
+ * }
116
+ * });
117
+ * ```
118
+ *
119
+ * @see {@link Disposer} for disposer function type
120
+ * @see {@link Disposable} for disposable interface
51
121
  */
52
122
  export declare function dispose(disposer?: Disposer): (code?: ExitCode) => Promise<void>;
53
123
  /**
@@ -55,13 +125,82 @@ export declare function dispose(disposer?: Disposer): (code?: ExitCode) => Promi
55
125
  */
56
126
  export declare const SNAP_EVENT = "__snapshot__";
57
127
  /**
58
- * Singleton event store port. By default, uses the in-memory store.
128
+ * Gets or injects the singleton event store.
59
129
  *
60
- * You can inject a persistent store (e.g., Postgres) by calling `store(myAdapter)`.
130
+ * By default, Act uses an in-memory store suitable for development and testing.
131
+ * For production, inject a persistent store like PostgresStore before building
132
+ * your application.
61
133
  *
62
- * @example
63
- * const myStore = store();
64
- * const customStore = store(new MyCustomStore());
134
+ * **Important:** Store injection must happen before creating any Act instances.
135
+ * Once set, the store cannot be changed without restarting the application.
136
+ *
137
+ * @param adapter - Optional store implementation to inject
138
+ * @returns The singleton store instance
139
+ *
140
+ * @example Using default in-memory store
141
+ * ```typescript
142
+ * import { store } from "@rotorsoft/act";
143
+ *
144
+ * const currentStore = store(); // Returns InMemoryStore
145
+ * ```
146
+ *
147
+ * @example Injecting PostgreSQL store
148
+ * ```typescript
149
+ * import { store } from "@rotorsoft/act";
150
+ * import { PostgresStore } from "@rotorsoft/act-pg";
151
+ *
152
+ * // Inject before building your app
153
+ * store(new PostgresStore({
154
+ * host: "localhost",
155
+ * port: 5432,
156
+ * database: "myapp",
157
+ * user: "postgres",
158
+ * password: "secret",
159
+ * schema: "public",
160
+ * table: "events"
161
+ * }));
162
+ *
163
+ * // Now build your app - it will use PostgreSQL
164
+ * const app = act()
165
+ * .with(Counter)
166
+ * .build();
167
+ * ```
168
+ *
169
+ * @example With environment-based configuration
170
+ * ```typescript
171
+ * import { store } from "@rotorsoft/act";
172
+ * import { PostgresStore } from "@rotorsoft/act-pg";
173
+ *
174
+ * if (process.env.NODE_ENV === "production") {
175
+ * store(new PostgresStore({
176
+ * host: process.env.DB_HOST,
177
+ * port: parseInt(process.env.DB_PORT || "5432"),
178
+ * database: process.env.DB_NAME,
179
+ * user: process.env.DB_USER,
180
+ * password: process.env.DB_PASSWORD
181
+ * }));
182
+ * }
183
+ * // Development uses default in-memory store
184
+ * ```
185
+ *
186
+ * @example Testing with fresh store
187
+ * ```typescript
188
+ * import { store } from "@rotorsoft/act";
189
+ *
190
+ * beforeEach(async () => {
191
+ * // Reset store between tests
192
+ * await store().seed();
193
+ * });
194
+ *
195
+ * afterAll(async () => {
196
+ * // Cleanup
197
+ * await store().drop();
198
+ * });
199
+ * ```
200
+ *
201
+ * @see {@link Store} for the store interface
202
+ * @see {@link InMemoryStore} for the default implementation
203
+ * @see {@link PostgresStore} for production use
65
204
  */
66
205
  export declare const store: (adapter?: Store | undefined) => Store;
67
206
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"ports.d.ts","sourceRoot":"","sources":["../../src/ports.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,UAAU,EACV,QAAQ,EACR,KAAK,EACL,KAAK,EACL,QAAQ,EACR,OAAO,EACP,KAAK,EACN,MAAM,kBAAkB,CAAC;AAE1B;;;;;;;;;;GAUG;AAEH;;GAEG;AACH,eAAO,MAAM,SAAS,4BAA6B,CAAC;AAEpD;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC;AAElD;;;;;;;GAOG;AACH,eAAO,MAAM,MAAM,uCAajB,CAAC;AAEH;;;;;;;;;GASG;AACH,KAAK,QAAQ,CAAC,IAAI,SAAS,UAAU,IAAI,CAAC,OAAO,CAAC,EAAE,IAAI,KAAK,IAAI,CAAC;AAElE,wBAAgB,IAAI,CAAC,IAAI,SAAS,UAAU,EAAE,QAAQ,EAAE,QAAQ,CAAC,IAAI,CAAC,IACnD,UAAU,IAAI,KAAG,IAAI,CAQvC;AAGD,wBAAsB,cAAc,CAAC,IAAI,GAAE,QAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAa3E;AAED;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CACrB,QAAQ,CAAC,EAAE,QAAQ,GAClB,CAAC,IAAI,CAAC,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,CAGpC;AAED;;GAEG;AACH,eAAO,MAAM,UAAU,iBAAiB,CAAC;AAEzC;;;;;;;;GAQG;AACH,eAAO,MAAM,KAAK,wCAEhB,CAAC;AAEH;;GAEG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,QAAQ,GAAG;IAChD,OAAO,EAAE,CAAC,CAAC,SAAS,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;IACxD,UAAU,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,IAAI,CAAC;IACtC,MAAM,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,IAAI,CAAC;IAClC,KAAK,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,IAAI,CAAC;IACjC,OAAO,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,GAAG;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,KAAK,IAAI,CAAC;CAC7D,CAkDA"}
1
+ {"version":3,"file":"ports.d.ts","sourceRoot":"","sources":["../../src/ports.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,UAAU,EACV,QAAQ,EACR,KAAK,EACL,KAAK,EACL,QAAQ,EACR,OAAO,EACP,KAAK,EACN,MAAM,kBAAkB,CAAC;AAE1B;;;;;;;;;;GAUG;AAEH;;GAEG;AACH,eAAO,MAAM,SAAS,4BAA6B,CAAC;AAEpD;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC;AAElD;;;;;;;GAOG;AACH,eAAO,MAAM,MAAM,uCAajB,CAAC;AAEH;;;;;;;;;GASG;AACH,KAAK,QAAQ,CAAC,IAAI,SAAS,UAAU,IAAI,CAAC,OAAO,CAAC,EAAE,IAAI,KAAK,IAAI,CAAC;AAElE,wBAAgB,IAAI,CAAC,IAAI,SAAS,UAAU,EAAE,QAAQ,EAAE,QAAQ,CAAC,IAAI,CAAC,IACnD,UAAU,IAAI,KAAG,IAAI,CAQvC;AAGD,wBAAsB,cAAc,CAAC,IAAI,GAAE,QAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAa3E;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8EG;AACH,wBAAgB,OAAO,CACrB,QAAQ,CAAC,EAAE,QAAQ,GAClB,CAAC,IAAI,CAAC,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,CAGpC;AAED;;GAEG;AACH,eAAO,MAAM,UAAU,iBAAiB,CAAC;AAEzC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6EG;AACH,eAAO,MAAM,KAAK,wCAEhB,CAAC;AAEH;;GAEG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,QAAQ,GAAG;IAChD,OAAO,EAAE,CAAC,CAAC,SAAS,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;IACxD,UAAU,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,IAAI,CAAC;IACtC,MAAM,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,IAAI,CAAC;IAClC,KAAK,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,IAAI,CAAC;IACjC,OAAO,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,GAAG;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,KAAK,IAAI,CAAC;CAC7D,CAkDA"}
@@ -6,46 +6,170 @@
6
6
  */
7
7
  import { ZodType } from "zod";
8
8
  import { ActionHandler, Invariant, PatchHandlers, Schema, Schemas, Snapshot, State, ZodTypes } from "./types/index.js";
9
+ /**
10
+ * Builder interface for defining a state with event sourcing.
11
+ *
12
+ * Provides a fluent API to configure the initial state, event types,
13
+ * and event handlers (reducers) before moving to action configuration.
14
+ *
15
+ * @template S - State schema type
16
+ *
17
+ * @see {@link state} for usage examples
18
+ * @see {@link ActionBuilder} for action configuration
19
+ */
9
20
  export type StateBuilder<S extends Schema> = {
10
21
  /**
11
- * Define the initial state for the state machine.
12
- * @param init Function returning the initial state
13
- * @returns An object with .emits() to declare event types
22
+ * Defines the initial state for new state instances.
23
+ *
24
+ * The init function is called when a new stream is created (first event).
25
+ * It can accept initial data or return a default state.
26
+ *
27
+ * @param init - Function returning the initial state
28
+ * @returns A builder with `.emits()` to declare event types
29
+ *
30
+ * @example
31
+ * ```typescript
32
+ * .init(() => ({ count: 0, created: new Date() }))
33
+ * ```
34
+ *
35
+ * @example With initial data
36
+ * ```typescript
37
+ * .init((data) => ({ ...data, createdAt: new Date() }))
38
+ * ```
14
39
  */
15
40
  init: (init: () => Readonly<S>) => {
16
41
  /**
17
- * Declare the event types the state machine can emit.
18
- * @param events Zod schemas for each event
19
- * @returns An object with .patch() to define event handlers
42
+ * Declares the event types that this state can emit.
43
+ *
44
+ * Events represent facts that have happened - they should be named in past tense.
45
+ * Each event is defined with a Zod schema for type safety and runtime validation.
46
+ *
47
+ * @template E - Event schemas type
48
+ * @param events - Object mapping event names to Zod schemas
49
+ * @returns A builder with `.patch()` to define event handlers
50
+ *
51
+ * @example
52
+ * ```typescript
53
+ * .emits({
54
+ * Incremented: z.object({ amount: z.number() }),
55
+ * Decremented: z.object({ amount: z.number() }),
56
+ * Reset: z.object({})
57
+ * })
58
+ * ```
20
59
  */
21
60
  emits: <E extends Schemas>(events: ZodTypes<E>) => {
22
61
  /**
23
- * Define how each event updates state.
24
- * @param patch Event handler functions
62
+ * Defines how each event updates (patches) the state.
63
+ *
64
+ * Patch handlers are reducers - pure functions that take an event and current state,
65
+ * and return the changes to apply. Return partial state objects; unchanged fields
66
+ * are preserved automatically.
67
+ *
68
+ * @param patch - Object mapping event names to patch handler functions
25
69
  * @returns An ActionBuilder for defining actions
70
+ *
71
+ * @example
72
+ * ```typescript
73
+ * .patch({
74
+ * Incremented: (event, state) => ({ count: state.count + event.data.amount }),
75
+ * Decremented: (event, state) => ({ count: state.count - event.data.amount }),
76
+ * Reset: () => ({ count: 0 })
77
+ * })
78
+ * ```
26
79
  */
27
80
  patch: (patch: PatchHandlers<S, E>) => ActionBuilder<S, E, {}>;
28
81
  };
29
82
  };
30
83
  };
84
+ /**
85
+ * Builder interface for defining actions (commands) on a state.
86
+ *
87
+ * Actions represent user/system intents to modify state. Each action is validated
88
+ * against a schema, can have business rule invariants, and must emit one or more events.
89
+ *
90
+ * @template S - State schema type
91
+ * @template E - Event schemas type
92
+ * @template A - Action schemas type
93
+ *
94
+ * @see {@link state} for complete usage examples
95
+ */
31
96
  export type ActionBuilder<S extends Schema, E extends Schemas, A extends Schemas> = {
32
97
  /**
33
- * Define an action for the state machine.
34
- * @param action The action name
35
- * @param schema The Zod schema for the action payload
36
- * @returns An object with .given() and .emit() for further configuration
98
+ * Defines an action (command) that can be executed on this state.
99
+ *
100
+ * Actions represent intents to change state - they should be named in imperative form
101
+ * (e.g., "createUser", "incrementCounter", "placeOrder"). Actions are validated against
102
+ * their schema and must emit at least one event.
103
+ *
104
+ * @template K - Action name (string literal type)
105
+ * @template AX - Action payload schema type
106
+ * @param action - The action name (should be unique within this state)
107
+ * @param schema - Zod schema for the action payload
108
+ * @returns An object with `.given()` and `.emit()` for further configuration
109
+ *
110
+ * @example Simple action without invariants
111
+ * ```typescript
112
+ * .on("increment", z.object({ by: z.number() }))
113
+ * .emit((action) => ["Incremented", { amount: action.by }])
114
+ * ```
115
+ *
116
+ * @example Action with business rules
117
+ * ```typescript
118
+ * .on("withdraw", z.object({ amount: z.number() }))
119
+ * .given([
120
+ * (_, snap) => snap.state.balance >= 0 || "Account closed",
121
+ * (_, snap, action) => snap.state.balance >= action.amount || "Insufficient funds"
122
+ * ])
123
+ * .emit((action) => ["Withdrawn", { amount: action.amount }])
124
+ * ```
125
+ *
126
+ * @example Action emitting multiple events
127
+ * ```typescript
128
+ * .on("completeOrder", z.object({ orderId: z.string() }))
129
+ * .emit((action) => [
130
+ * ["OrderCompleted", { orderId: action.orderId }],
131
+ * ["InventoryReserved", { orderId: action.orderId }],
132
+ * ["PaymentProcessed", { orderId: action.orderId }]
133
+ * ])
134
+ * ```
37
135
  */
38
136
  on: <K extends string, AX extends Schema>(action: K, schema: ZodType<AX>) => {
39
137
  /**
40
- * Constrain the action with invariants (business rules).
41
- * @param rules Array of invariants
42
- * @returns An object with .emit() to finalize the action
138
+ * Adds business rule invariants that must hold before the action can execute.
139
+ *
140
+ * Invariants are checked after loading the current state but before emitting events.
141
+ * Each invariant should return `true` or an error message string. All invariants
142
+ * must pass for the action to succeed.
143
+ *
144
+ * @param rules - Array of invariant functions
145
+ * @returns An object with `.emit()` to finalize the action
146
+ *
147
+ * @example
148
+ * ```typescript
149
+ * .given([
150
+ * (_, snap) => snap.state.status === "active" || "Must be active",
151
+ * (target, snap) => snap.state.ownerId === target.actor.id || "Not authorized"
152
+ * ])
153
+ * ```
43
154
  */
44
155
  given: (rules: Invariant<S>[]) => {
45
156
  /**
46
- * Finalize the action by providing the event emission handler.
47
- * @param handler The action handler function
48
- * @returns The ActionBuilder for chaining
157
+ * Defines the action handler that emits events.
158
+ *
159
+ * The handler receives the action payload and current state snapshot,
160
+ * and must return one or more events to emit. Events are applied to state
161
+ * via the patch handlers defined earlier.
162
+ *
163
+ * @param handler - Function that returns events to emit
164
+ * @returns The ActionBuilder for chaining more actions
165
+ *
166
+ * @example
167
+ * ```typescript
168
+ * .emit((action, snapshot) => {
169
+ * const newBalance = snapshot.state.balance + action.amount;
170
+ * return ["Deposited", { amount: action.amount, newBalance }];
171
+ * })
172
+ * ```
49
173
  */
50
174
  emit: (handler: ActionHandler<S, E, {
51
175
  [P in K]: AX;
@@ -54,9 +178,40 @@ export type ActionBuilder<S extends Schema, E extends Schemas, A extends Schemas
54
178
  }>;
55
179
  };
56
180
  /**
57
- * Finalize the action by providing the event emission handler.
58
- * @param handler The action handler function
59
- * @returns The ActionBuilder for chaining
181
+ * Defines the action handler that emits events.
182
+ *
183
+ * The handler receives the action payload and current state snapshot,
184
+ * and must return one or more events to emit. Return a single event as
185
+ * `["EventName", data]` or multiple events as an array of event tuples.
186
+ *
187
+ * @param handler - Function that returns events to emit
188
+ * @returns The ActionBuilder for chaining more actions
189
+ *
190
+ * @example Single event
191
+ * ```typescript
192
+ * .emit((action) => ["Incremented", { amount: action.by }])
193
+ * ```
194
+ *
195
+ * @example Multiple events
196
+ * ```typescript
197
+ * .emit((action) => [
198
+ * ["Incremented", { amount: action.by }],
199
+ * ["LogUpdated", { message: `Incremented by ${action.by}` }]
200
+ * ])
201
+ * ```
202
+ *
203
+ * @example Conditional events
204
+ * ```typescript
205
+ * .emit((action, snapshot) => {
206
+ * if (snapshot.state.count + action.by >= 100) {
207
+ * return [
208
+ * ["Incremented", { amount: action.by }],
209
+ * ["MilestoneReached", { milestone: 100 }]
210
+ * ];
211
+ * }
212
+ * return ["Incremented", { amount: action.by }];
213
+ * })
214
+ * ```
60
215
  */
61
216
  emit: (handler: ActionHandler<S, E, {
62
217
  [P in K]: AX;
@@ -65,44 +220,164 @@ export type ActionBuilder<S extends Schema, E extends Schemas, A extends Schemas
65
220
  }>;
66
221
  };
67
222
  /**
68
- * Define a snapshotting strategy to reduce recomputations.
69
- * @param snap Function that determines when to snapshot
223
+ * Defines a snapshotting strategy to optimize state reconstruction.
224
+ *
225
+ * Snapshots store the current state at a point in time, allowing faster state loading
226
+ * by avoiding replaying all events from the beginning. The snap function is called
227
+ * after each event is applied and should return `true` when a snapshot should be taken.
228
+ *
229
+ * @param snap - Predicate function that returns true when a snapshot should be taken
70
230
  * @returns The ActionBuilder for chaining
231
+ *
232
+ * @example Snapshot every 10 events
233
+ * ```typescript
234
+ * .snap((snapshot) => snapshot.patches >= 10)
235
+ * ```
236
+ *
237
+ * @example Snapshot based on state size
238
+ * ```typescript
239
+ * .snap((snapshot) => {
240
+ * const estimatedSize = JSON.stringify(snapshot.state).length;
241
+ * return estimatedSize > 10000 || snapshot.patches >= 50;
242
+ * })
243
+ * ```
244
+ *
245
+ * @example Time-based snapshotting
246
+ * ```typescript
247
+ * .snap((snapshot) => {
248
+ * const hoursSinceLastSnapshot = snapshot.patches * 0.1; // Estimate
249
+ * return hoursSinceLastSnapshot >= 24;
250
+ * })
251
+ * ```
71
252
  */
72
253
  snap: (snap: (snapshot: Snapshot<S, E>) => boolean) => ActionBuilder<S, E, A>;
73
254
  /**
74
- * Finalize and build the state machine definition.
75
- * @returns The strongly-typed State definition
255
+ * Finalizes and builds the state definition.
256
+ *
257
+ * Call this method after defining all actions, invariants, and patches to create
258
+ * the complete State object that can be registered with Act.
259
+ *
260
+ * @returns The complete strongly-typed State definition
261
+ *
262
+ * @example
263
+ * ```typescript
264
+ * const Counter = state("Counter", schema)
265
+ * .init(() => ({ count: 0 }))
266
+ * .emits({ Incremented: z.object({ amount: z.number() }) })
267
+ * .patch({ Incremented: (event, state) => ({ count: state.count + event.data.amount }) })
268
+ * .on("increment", z.object({ by: z.number() }))
269
+ * .emit((action) => ["Incremented", { amount: action.by }])
270
+ * .build(); // Returns State<S, E, A>
271
+ * ```
76
272
  */
77
273
  build: () => State<S, E, A>;
78
274
  };
79
275
  /**
80
- * Fluent interface for defining a strongly-typed state machine using Zod schemas.
276
+ * Creates a new state definition with event sourcing capabilities.
277
+ *
278
+ * States are the core building blocks of Act. Each state represents a consistency
279
+ * boundary (aggregate) that processes actions, emits events, and maintains its own
280
+ * state through event patches (reducers). States use event sourcing to maintain a
281
+ * complete audit trail and enable time-travel capabilities.
282
+ *
283
+ * The state builder provides a fluent API for defining:
284
+ * 1. Initial state via `.init()`
285
+ * 2. Event types via `.emits()`
286
+ * 3. Event handlers (reducers) via `.patch()`
287
+ * 4. Actions (commands) via `.on()` → `.emit()`
288
+ * 5. Business rules (invariants) via `.given()`
289
+ * 6. Snapshotting strategy via `.snap()`
81
290
  *
82
- * This builder helps you model a system where:
83
- * - You start by defining the state schema with `state(name, zodSchema)`
84
- * - Then, provide the initial state using `.init(() => defaultState)`
85
- * - Declare the event types your system can emit using `.emits({ ... })`
86
- * - Define how emitted events update state with `.patch({ ... })`
87
- * - Define actions using `.on("actionName", actionSchema)`
88
- * - Optionally constrain the action with `.given([...invariants])`
89
- * - Then finalize the action behavior with `.emit(handler)`
90
- * - (Optional) Define a `.snap(snapshot => boolean)` function to reduce recomputations
91
- * - Finalize the state machine definition using `.build()`
291
+ * @template S - Zod schema type defining the shape of the state
292
+ * @param name - Unique identifier for this state type (e.g., "Counter", "User", "Order")
293
+ * @param state - Zod schema defining the structure of the state
294
+ * @returns A StateBuilder instance for fluent API configuration
92
295
  *
93
- * @template S The type of state
296
+ * @example Basic counter state
297
+ * ```typescript
298
+ * import { state } from "@rotorsoft/act";
299
+ * import { z } from "zod";
94
300
  *
95
- * @example
96
- * const machine = state("machine", myStateSchema)
301
+ * const Counter = state("Counter", z.object({ count: z.number() }))
97
302
  * .init(() => ({ count: 0 }))
98
- * .emits({ Incremented: z.object({ amount: z.number() }) })
303
+ * .emits({
304
+ * Incremented: z.object({ amount: z.number() })
305
+ * })
99
306
  * .patch({
100
- * Incremented: (event, state) => ({ count: state.count + event.amount })
307
+ * Incremented: (event, state) => ({ count: state.count + event.data.amount })
101
308
  * })
102
309
  * .on("increment", z.object({ by: z.number() }))
103
- * .given([{ description: "must be positive", valid: (s, a) => a?.by > 0 }])
104
- * .emit((action, state) => ({ type: "Incremented", amount: action.by }))
310
+ * .emit((action) => ["Incremented", { amount: action.by }])
311
+ * .build();
312
+ * ```
313
+ *
314
+ * @example State with multiple events and invariants
315
+ * ```typescript
316
+ * const BankAccount = state("BankAccount", z.object({
317
+ * balance: z.number(),
318
+ * currency: z.string(),
319
+ * status: z.enum(["open", "closed"])
320
+ * }))
321
+ * .init(() => ({ balance: 0, currency: "USD", status: "open" }))
322
+ * .emits({
323
+ * Deposited: z.object({ amount: z.number() }),
324
+ * Withdrawn: z.object({ amount: z.number() }),
325
+ * Closed: z.object({})
326
+ * })
327
+ * .patch({
328
+ * Deposited: (event, state) => ({ balance: state.balance + event.data.amount }),
329
+ * Withdrawn: (event, state) => ({ balance: state.balance - event.data.amount }),
330
+ * Closed: () => ({ status: "closed", balance: 0 })
331
+ * })
332
+ * .on("deposit", z.object({ amount: z.number() }))
333
+ * .given([
334
+ * (_, snap) => snap.state.status === "open" || "Account must be open"
335
+ * ])
336
+ * .emit((action) => ["Deposited", { amount: action.amount }])
337
+ * .on("withdraw", z.object({ amount: z.number() }))
338
+ * .given([
339
+ * (_, snap) => snap.state.status === "open" || "Account must be open",
340
+ * (_, snap, action) =>
341
+ * snap.state.balance >= action.amount || "Insufficient funds"
342
+ * ])
343
+ * .emit((action) => ["Withdrawn", { amount: action.amount }])
344
+ * .on("close", z.object({}))
345
+ * .given([
346
+ * (_, snap) => snap.state.status === "open" || "Already closed",
347
+ * (_, snap) => snap.state.balance === 0 || "Balance must be zero"
348
+ * ])
349
+ * .emit(() => ["Closed", {}])
105
350
  * .build();
351
+ * ```
352
+ *
353
+ * @example State with snapshotting
354
+ * ```typescript
355
+ * const User = state("User", z.object({
356
+ * name: z.string(),
357
+ * email: z.string(),
358
+ * loginCount: z.number()
359
+ * }))
360
+ * .init((data) => ({ ...data, loginCount: 0 }))
361
+ * .emits({
362
+ * UserCreated: z.object({ name: z.string(), email: z.string() }),
363
+ * UserLoggedIn: z.object({})
364
+ * })
365
+ * .patch({
366
+ * UserCreated: (event) => event.data,
367
+ * UserLoggedIn: (_, state) => ({ loginCount: state.loginCount + 1 })
368
+ * })
369
+ * .on("createUser", z.object({ name: z.string(), email: z.string() }))
370
+ * .emit((action) => ["UserCreated", action])
371
+ * .on("login", z.object({}))
372
+ * .emit(() => ["UserLoggedIn", {}])
373
+ * .snap((snap) => snap.patches >= 10) // Snapshot every 10 events
374
+ * .build();
375
+ * ```
376
+ *
377
+ * @see {@link StateBuilder} for available builder methods
378
+ * @see {@link ActionBuilder} for action configuration methods
379
+ * @see {@link https://rotorsoft.github.io/act-root/docs/intro | Getting Started Guide}
380
+ * @see {@link https://rotorsoft.github.io/act-root/docs/examples/calculator | Calculator Example}
106
381
  */
107
382
  export declare function state<S extends Schema>(name: string, state: ZodType<S>): StateBuilder<S>;
108
383
  //# sourceMappingURL=state-builder.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"state-builder.d.ts","sourceRoot":"","sources":["../../src/state-builder.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AAC9B,OAAO,EACL,aAAa,EAGb,SAAS,EACT,aAAa,EACb,MAAM,EACN,OAAO,EACP,QAAQ,EACR,KAAK,EACL,QAAQ,EACT,MAAM,kBAAkB,CAAC;AAG1B,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,MAAM,IAAI;IAC3C;;;;OAIG;IACH,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,QAAQ,CAAC,CAAC,CAAC,KAAK;QACjC;;;;WAIG;QACH,KAAK,EAAE,CAAC,CAAC,SAAS,OAAO,EACvB,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,KAChB;YACH;;;;eAIG;YACH,KAAK,EAAE,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,aAAa,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;SAChE,CAAC;KACH,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,aAAa,CACvB,CAAC,SAAS,MAAM,EAChB,CAAC,SAAS,OAAO,EACjB,CAAC,SAAS,OAAO,IACf;IACF;;;;;OAKG;IACH,EAAE,EAAE,CAAC,CAAC,SAAS,MAAM,EAAE,EAAE,SAAS,MAAM,EACtC,MAAM,EAAE,CAAC,EACT,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,KAChB;QACH;;;;WAIG;QACH,KAAK,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK;YAChC;;;;eAIG;YACH,IAAI,EAAE,CACJ,OAAO,EAAE,aAAa,CAAC,CAAC,EAAE,CAAC,EAAE;iBAAG,CAAC,IAAI,CAAC,GAAG,EAAE;aAAE,EAAE,CAAC,CAAC,KAC9C,aAAa,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG;iBAAG,CAAC,IAAI,CAAC,GAAG,EAAE;aAAE,CAAC,CAAC;SAChD,CAAC;QACF;;;;WAIG;QACH,IAAI,EAAE,CACJ,OAAO,EAAE,aAAa,CAAC,CAAC,EAAE,CAAC,EAAE;aAAG,CAAC,IAAI,CAAC,GAAG,EAAE;SAAE,EAAE,CAAC,CAAC,KAC9C,aAAa,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG;aAAG,CAAC,IAAI,CAAC,GAAG,EAAE;SAAE,CAAC,CAAC;KAChD,CAAC;IACF;;;;OAIG;IACH,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,OAAO,KAAK,aAAa,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAC9E;;;OAGG;IACH,KAAK,EAAE,MAAM,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;CAC7B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,KAAK,CAAC,CAAC,SAAS,MAAM,EACpC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,GAChB,YAAY,CAAC,CAAC,CAAC,CAsBjB"}
1
+ {"version":3,"file":"state-builder.d.ts","sourceRoot":"","sources":["../../src/state-builder.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AAC9B,OAAO,EACL,aAAa,EAGb,SAAS,EACT,aAAa,EACb,MAAM,EACN,OAAO,EACP,QAAQ,EACR,KAAK,EACL,QAAQ,EACT,MAAM,kBAAkB,CAAC;AAG1B;;;;;;;;;;GAUG;AACH,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,MAAM,IAAI;IAC3C;;;;;;;;;;;;;;;;;;OAkBG;IACH,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,QAAQ,CAAC,CAAC,CAAC,KAAK;QACjC;;;;;;;;;;;;;;;;;;WAkBG;QACH,KAAK,EAAE,CAAC,CAAC,SAAS,OAAO,EACvB,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,KAChB;YACH;;;;;;;;;;;;;;;;;;eAkBG;YACH,KAAK,EAAE,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,aAAa,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;SAChE,CAAC;KACH,CAAC;CACH,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,aAAa,CACvB,CAAC,SAAS,MAAM,EAChB,CAAC,SAAS,OAAO,EACjB,CAAC,SAAS,OAAO,IACf;IACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAsCG;IACH,EAAE,EAAE,CAAC,CAAC,SAAS,MAAM,EAAE,EAAE,SAAS,MAAM,EACtC,MAAM,EAAE,CAAC,EACT,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,KAChB;QACH;;;;;;;;;;;;;;;;;WAiBG;QACH,KAAK,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK;YAChC;;;;;;;;;;;;;;;;;eAiBG;YACH,IAAI,EAAE,CACJ,OAAO,EAAE,aAAa,CAAC,CAAC,EAAE,CAAC,EAAE;iBAAG,CAAC,IAAI,CAAC,GAAG,EAAE;aAAE,EAAE,CAAC,CAAC,KAC9C,aAAa,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG;iBAAG,CAAC,IAAI,CAAC,GAAG,EAAE;aAAE,CAAC,CAAC;SAChD,CAAC;QACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAmCG;QACH,IAAI,EAAE,CACJ,OAAO,EAAE,aAAa,CAAC,CAAC,EAAE,CAAC,EAAE;aAAG,CAAC,IAAI,CAAC,GAAG,EAAE;SAAE,EAAE,CAAC,CAAC,KAC9C,aAAa,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG;aAAG,CAAC,IAAI,CAAC,GAAG,EAAE;SAAE,CAAC,CAAC;KAChD,CAAC;IACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8BG;IACH,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,OAAO,KAAK,aAAa,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAC9E;;;;;;;;;;;;;;;;;;OAkBG;IACH,KAAK,EAAE,MAAM,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;CAC7B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0GG;AACH,wBAAgB,KAAK,CAAC,CAAC,SAAS,MAAM,EACpC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,GAChB,YAAY,CAAC,CAAC,CAAC,CAsBjB"}