@rotorsoft/act 0.6.27 → 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.
- package/dist/.tsbuildinfo +1 -1
- package/dist/@types/act-builder.d.ts +286 -41
- package/dist/@types/act-builder.d.ts.map +1 -1
- package/dist/@types/act.d.ts +354 -52
- package/dist/@types/act.d.ts.map +1 -1
- package/dist/@types/adapters/InMemoryStore.d.ts +59 -8
- package/dist/@types/adapters/InMemoryStore.d.ts.map +1 -1
- package/dist/@types/config.d.ts +54 -6
- package/dist/@types/config.d.ts.map +1 -1
- package/dist/@types/ports.d.ts +149 -10
- package/dist/@types/ports.d.ts.map +1 -1
- package/dist/@types/state-builder.d.ts +318 -43
- package/dist/@types/state-builder.d.ts.map +1 -1
- package/dist/@types/types/action.d.ts +122 -10
- package/dist/@types/types/action.d.ts.map +1 -1
- package/dist/@types/types/errors.d.ts +211 -22
- package/dist/@types/types/errors.d.ts.map +1 -1
- package/dist/@types/types/ports.d.ts +204 -28
- package/dist/@types/types/ports.d.ts.map +1 -1
- package/dist/@types/types/reaction.d.ts +107 -18
- package/dist/@types/types/reaction.d.ts.map +1 -1
- package/dist/@types/utils.d.ts +378 -27
- package/dist/@types/utils.d.ts.map +1 -1
- package/dist/index.cjs +352 -50
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +352 -50
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/@types/ports.d.ts
CHANGED
|
@@ -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
|
|
44
|
+
* Registers resource cleanup functions for graceful shutdown.
|
|
45
45
|
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
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
|
-
*
|
|
50
|
-
*
|
|
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
|
-
*
|
|
128
|
+
* Gets or injects the singleton event store.
|
|
59
129
|
*
|
|
60
|
-
*
|
|
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
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
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
|
|
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
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
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
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
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
|
-
*
|
|
24
|
-
*
|
|
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
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
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
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
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
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
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
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
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
|
-
*
|
|
69
|
-
*
|
|
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
|
-
*
|
|
75
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
83
|
-
* -
|
|
84
|
-
* -
|
|
85
|
-
*
|
|
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
|
-
* @
|
|
296
|
+
* @example Basic counter state
|
|
297
|
+
* ```typescript
|
|
298
|
+
* import { state } from "@rotorsoft/act";
|
|
299
|
+
* import { z } from "zod";
|
|
94
300
|
*
|
|
95
|
-
*
|
|
96
|
-
* const machine = state("machine", myStateSchema)
|
|
301
|
+
* const Counter = state("Counter", z.object({ count: z.number() }))
|
|
97
302
|
* .init(() => ({ count: 0 }))
|
|
98
|
-
* .emits({
|
|
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
|
-
*
|
|
104
|
-
* .
|
|
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
|
|
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"}
|