@rotorsoft/act 0.9.0 → 0.11.0
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 +27 -3
- package/dist/.tsbuildinfo +1 -1
- package/dist/@types/act-builder.d.ts +4 -3
- package/dist/@types/act-builder.d.ts.map +1 -1
- package/dist/@types/index.d.ts +1 -0
- package/dist/@types/index.d.ts.map +1 -1
- package/dist/@types/projection-builder.d.ts +123 -0
- package/dist/@types/projection-builder.d.ts.map +1 -0
- package/dist/@types/state-builder.d.ts +37 -29
- package/dist/@types/state-builder.d.ts.map +1 -1
- package/dist/@types/types/action.d.ts +1 -1
- package/dist/@types/utils.d.ts +3 -3
- package/dist/index.cjs +98 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +96 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/@types/index.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export * from "./act-builder.js";
|
|
|
8
8
|
export * from "./act.js";
|
|
9
9
|
export * from "./config.js";
|
|
10
10
|
export * from "./ports.js";
|
|
11
|
+
export * from "./projection-builder.js";
|
|
11
12
|
export * from "./slice-builder.js";
|
|
12
13
|
export * from "./state-builder.js";
|
|
13
14
|
export * from "./types/index.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,cAAc,CAAC;AAEtB;;;;GAIG;AACH,cAAc,kBAAkB,CAAC;AACjC,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,cAAc,CAAC;AAEtB;;;;GAIG;AACH,cAAc,kBAAkB,CAAC;AACjC,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,yBAAyB,CAAC;AACxC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module projection-builder
|
|
3
|
+
* @category Builders
|
|
4
|
+
*
|
|
5
|
+
* Fluent builder for composing projection handlers — read-model updaters
|
|
6
|
+
* that react to events and update external state (databases, caches, etc.).
|
|
7
|
+
*
|
|
8
|
+
* Projections differ from slices: they don't contain states, don't dispatch
|
|
9
|
+
* actions, and are pure side-effect handlers routed to a named stream.
|
|
10
|
+
*/
|
|
11
|
+
import type { ZodType } from "zod";
|
|
12
|
+
import type { Committed, EventRegister, ReactionResolver, Schema, Schemas } from "./types/index.js";
|
|
13
|
+
/**
|
|
14
|
+
* A self-contained projection grouping read-model update handlers.
|
|
15
|
+
* Projections are composed into an Act orchestrator via `act().with(projection)`.
|
|
16
|
+
*
|
|
17
|
+
* @template E - Event schemas handled by this projection
|
|
18
|
+
*/
|
|
19
|
+
export type Projection<E extends Schemas> = {
|
|
20
|
+
readonly _tag: "Projection";
|
|
21
|
+
readonly events: EventRegister<E>;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Type guard for distinguishing Projection from State and Slice objects.
|
|
25
|
+
*/
|
|
26
|
+
export declare function isProjection(x: any): x is Projection<any>;
|
|
27
|
+
/** Helper: a single-key record mapping an event name to its Zod schema. */
|
|
28
|
+
type EventEntry<K extends string = string, D extends Schema = Schema> = {
|
|
29
|
+
[P in K]: ZodType<D>;
|
|
30
|
+
};
|
|
31
|
+
/** Infer the handler-result type after registering one event. */
|
|
32
|
+
type DoResult<E extends Schemas, K extends string, D extends Schema> = ProjectionBuilder<E & {
|
|
33
|
+
[P in K]: D;
|
|
34
|
+
}> & {
|
|
35
|
+
to: (resolver: ReactionResolver<E & {
|
|
36
|
+
[P in K]: D;
|
|
37
|
+
}, K> | string) => ProjectionBuilder<E & {
|
|
38
|
+
[P in K]: D;
|
|
39
|
+
}>;
|
|
40
|
+
void: () => ProjectionBuilder<E & {
|
|
41
|
+
[P in K]: D;
|
|
42
|
+
}>;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Fluent builder interface for composing projections.
|
|
46
|
+
*
|
|
47
|
+
* Provides a chainable API for registering event handlers that update
|
|
48
|
+
* read models. Unlike slices, projections have no `.with()` for states
|
|
49
|
+
* and handlers do not receive a `Dispatcher`.
|
|
50
|
+
*
|
|
51
|
+
* When a default target is provided via `projection("target")`, all
|
|
52
|
+
* handlers inherit that resolver. Per-handler `.to()` or `.void()` can
|
|
53
|
+
* still override it.
|
|
54
|
+
*
|
|
55
|
+
* @template E - Event schemas
|
|
56
|
+
*/
|
|
57
|
+
export type ProjectionBuilder<E extends Schemas> = {
|
|
58
|
+
/**
|
|
59
|
+
* Begins defining a projection handler for a specific event.
|
|
60
|
+
*
|
|
61
|
+
* Pass a `{ EventName: schema }` record — use shorthand `{ EventName }`
|
|
62
|
+
* when the variable name matches the event name. The key becomes the
|
|
63
|
+
* event name, the value the Zod schema.
|
|
64
|
+
*/
|
|
65
|
+
on: <K extends string, D extends Schema>(entry: EventEntry<K, D>) => {
|
|
66
|
+
do: (handler: (event: Committed<E & {
|
|
67
|
+
[P in K]: D;
|
|
68
|
+
}, K>, stream: string) => Promise<void>) => DoResult<E, K, D>;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Builds and returns the Projection data structure.
|
|
72
|
+
*/
|
|
73
|
+
build: () => Projection<E>;
|
|
74
|
+
/**
|
|
75
|
+
* The registered event schemas and their reaction maps.
|
|
76
|
+
*/
|
|
77
|
+
readonly events: EventRegister<E>;
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* Creates a new projection builder for composing read-model update handlers.
|
|
81
|
+
*
|
|
82
|
+
* Projections enable separation of read-model concerns from command handling.
|
|
83
|
+
* Each `.on({ Event }).do(handler)` call registers a handler that updates
|
|
84
|
+
* a projection (database table, cache, etc.) in response to events.
|
|
85
|
+
*
|
|
86
|
+
* Pass a target stream name to `projection("target")` so every handler
|
|
87
|
+
* inherits that resolver automatically. Omit it and use per-handler
|
|
88
|
+
* `.to()` / `.void()` when handlers route to different streams.
|
|
89
|
+
*
|
|
90
|
+
* @param target - Optional default target stream for all handlers
|
|
91
|
+
*
|
|
92
|
+
* @example Default target (all handlers routed to "tickets")
|
|
93
|
+
* ```typescript
|
|
94
|
+
* const TicketProjection = projection("tickets")
|
|
95
|
+
* .on({ TicketOpened })
|
|
96
|
+
* .do(async ({ stream, data }) => {
|
|
97
|
+
* await db.insert(tickets).values({ id: stream, ...data });
|
|
98
|
+
* })
|
|
99
|
+
* .on({ TicketClosed })
|
|
100
|
+
* .do(async ({ stream, data }) => {
|
|
101
|
+
* await db.update(tickets).set(data).where(eq(tickets.id, stream));
|
|
102
|
+
* })
|
|
103
|
+
* .build();
|
|
104
|
+
* ```
|
|
105
|
+
*
|
|
106
|
+
* @example Per-handler routing
|
|
107
|
+
* ```typescript
|
|
108
|
+
* const MultiProjection = projection()
|
|
109
|
+
* .on({ OrderPlaced })
|
|
110
|
+
* .do(async (event) => { ... })
|
|
111
|
+
* .to("orders")
|
|
112
|
+
* .on({ PaymentReceived })
|
|
113
|
+
* .do(async (event) => { ... })
|
|
114
|
+
* .to("payments")
|
|
115
|
+
* .build();
|
|
116
|
+
* ```
|
|
117
|
+
*
|
|
118
|
+
* @see {@link ProjectionBuilder} for builder methods
|
|
119
|
+
* @see {@link Projection} for the output type
|
|
120
|
+
*/
|
|
121
|
+
export declare function projection<E extends Schemas = {}>(target?: string, events?: EventRegister<E>): ProjectionBuilder<E>;
|
|
122
|
+
export {};
|
|
123
|
+
//# sourceMappingURL=projection-builder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projection-builder.d.ts","sourceRoot":"","sources":["../../src/projection-builder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AAEnC,OAAO,KAAK,EACV,SAAS,EACT,aAAa,EAGb,gBAAgB,EAChB,MAAM,EACN,OAAO,EACR,MAAM,kBAAkB,CAAC;AAE1B;;;;;GAKG;AACH,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,OAAO,IAAI;IAC1C,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC;CACnC,CAAC;AAEF;;GAEG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,IAAI,UAAU,CAAC,GAAG,CAAC,CAEzD;AAED,2EAA2E;AAC3E,KAAK,UAAU,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EAAE,CAAC,SAAS,MAAM,GAAG,MAAM,IAAI;KACrE,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;CACrB,CAAC;AAEF,iEAAiE;AACjE,KAAK,QAAQ,CACX,CAAC,SAAS,OAAO,EACjB,CAAC,SAAS,MAAM,EAChB,CAAC,SAAS,MAAM,IACd,iBAAiB,CAAC,CAAC,GAAG;KAAG,CAAC,IAAI,CAAC,GAAG,CAAC;CAAE,CAAC,GAAG;IAC3C,EAAE,EAAE,CACF,QAAQ,EAAE,gBAAgB,CAAC,CAAC,GAAG;SAAG,CAAC,IAAI,CAAC,GAAG,CAAC;KAAE,EAAE,CAAC,CAAC,GAAG,MAAM,KACxD,iBAAiB,CAAC,CAAC,GAAG;SAAG,CAAC,IAAI,CAAC,GAAG,CAAC;KAAE,CAAC,CAAC;IAC5C,IAAI,EAAE,MAAM,iBAAiB,CAAC,CAAC,GAAG;SAAG,CAAC,IAAI,CAAC,GAAG,CAAC;KAAE,CAAC,CAAC;CACpD,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,iBAAiB,CAAC,CAAC,SAAS,OAAO,IAAI;IACjD;;;;;;OAMG;IACH,EAAE,EAAE,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,MAAM,EACrC,KAAK,EAAE,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,KACpB;QACH,EAAE,EAAE,CACF,OAAO,EAAE,CACP,KAAK,EAAE,SAAS,CAAC,CAAC,GAAG;aAAG,CAAC,IAAI,CAAC,GAAG,CAAC;SAAE,EAAE,CAAC,CAAC,EACxC,MAAM,EAAE,MAAM,KACX,OAAO,CAAC,IAAI,CAAC,KACf,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;KACxB,CAAC;IACF;;OAEG;IACH,KAAK,EAAE,MAAM,UAAU,CAAC,CAAC,CAAC,CAAC;IAC3B;;OAEG;IACH,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC;CACnC,CAAC;AAIF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,OAAO,GAAG,EAAE,EAC/C,MAAM,CAAC,EAAE,MAAM,EACf,MAAM,GAAE,aAAa,CAAC,CAAC,CAA0B,GAChD,iBAAiB,CAAC,CAAC,CAAC,CAyEtB"}
|
|
@@ -81,6 +81,14 @@ export type StateBuilder<S extends Schema, N extends string = string> = {
|
|
|
81
81
|
};
|
|
82
82
|
};
|
|
83
83
|
};
|
|
84
|
+
/** Helper: a single-key record mapping a state name to its Zod schema. */
|
|
85
|
+
type StateEntry<K extends string = string, S extends Schema = Schema> = {
|
|
86
|
+
[P in K]: ZodType<S>;
|
|
87
|
+
};
|
|
88
|
+
/** Helper: a single-key record mapping an action name to its Zod schema. */
|
|
89
|
+
type ActionEntry<K extends string = string, AX extends Schema = Schema> = {
|
|
90
|
+
[P in K]: ZodType<AX>;
|
|
91
|
+
};
|
|
84
92
|
/**
|
|
85
93
|
* Builder interface for defining actions (commands) on a state.
|
|
86
94
|
*
|
|
@@ -98,24 +106,27 @@ export type ActionBuilder<S extends Schema, E extends Schemas, A extends Schemas
|
|
|
98
106
|
* Defines an action (command) that can be executed on this state.
|
|
99
107
|
*
|
|
100
108
|
* Actions represent intents to change state - they should be named in imperative form
|
|
101
|
-
* (e.g., "
|
|
109
|
+
* (e.g., "CreateUser", "IncrementCounter", "PlaceOrder"). Actions are validated against
|
|
102
110
|
* their schema and must emit at least one event.
|
|
103
111
|
*
|
|
112
|
+
* Pass a `{ ActionName: schema }` record — use shorthand `{ ActionName }`
|
|
113
|
+
* when the variable name matches the action name. The key becomes the
|
|
114
|
+
* action name, the value the Zod schema.
|
|
115
|
+
*
|
|
104
116
|
* @template K - Action name (string literal type)
|
|
105
117
|
* @template AX - Action payload schema type
|
|
106
|
-
* @param
|
|
107
|
-
* @param schema - Zod schema for the action payload
|
|
118
|
+
* @param entry - Single-key record `{ ActionName: schema }`
|
|
108
119
|
* @returns An object with `.given()` and `.emit()` for further configuration
|
|
109
120
|
*
|
|
110
121
|
* @example Simple action without invariants
|
|
111
122
|
* ```typescript
|
|
112
|
-
* .on(
|
|
123
|
+
* .on({ increment: z.object({ by: z.number() }) })
|
|
113
124
|
* .emit((action) => ["Incremented", { amount: action.by }])
|
|
114
125
|
* ```
|
|
115
126
|
*
|
|
116
127
|
* @example Action with business rules
|
|
117
128
|
* ```typescript
|
|
118
|
-
* .on(
|
|
129
|
+
* .on({ withdraw: z.object({ amount: z.number() }) })
|
|
119
130
|
* .given([
|
|
120
131
|
* (_, snap) => snap.state.balance >= 0 || "Account closed",
|
|
121
132
|
* (_, snap, action) => snap.state.balance >= action.amount || "Insufficient funds"
|
|
@@ -123,17 +134,14 @@ export type ActionBuilder<S extends Schema, E extends Schemas, A extends Schemas
|
|
|
123
134
|
* .emit((action) => ["Withdrawn", { amount: action.amount }])
|
|
124
135
|
* ```
|
|
125
136
|
*
|
|
126
|
-
* @example Action
|
|
137
|
+
* @example Action with shorthand (variable name matches action name)
|
|
127
138
|
* ```typescript
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
* ["InventoryReserved", { orderId: action.orderId }],
|
|
132
|
-
* ["PaymentProcessed", { orderId: action.orderId }]
|
|
133
|
-
* ])
|
|
139
|
+
* const OpenTicket = z.object({ title: z.string() });
|
|
140
|
+
* .on({ OpenTicket })
|
|
141
|
+
* .emit((action) => ["TicketOpened", { title: action.title }])
|
|
134
142
|
* ```
|
|
135
143
|
*/
|
|
136
|
-
on: <K extends string, AX extends Schema>(
|
|
144
|
+
on: <K extends string, AX extends Schema>(entry: ActionEntry<K, AX>) => {
|
|
137
145
|
/**
|
|
138
146
|
* Adds business rule invariants that must hold before the action can execute.
|
|
139
147
|
*
|
|
@@ -261,11 +269,11 @@ export type ActionBuilder<S extends Schema, E extends Schemas, A extends Schemas
|
|
|
261
269
|
*
|
|
262
270
|
* @example
|
|
263
271
|
* ```typescript
|
|
264
|
-
* const Counter = state(
|
|
272
|
+
* const Counter = state({ Counter: schema })
|
|
265
273
|
* .init(() => ({ count: 0 }))
|
|
266
274
|
* .emits({ Incremented: z.object({ amount: z.number() }) })
|
|
267
275
|
* .patch({ Incremented: (event, state) => ({ count: state.count + event.data.amount }) })
|
|
268
|
-
* .on(
|
|
276
|
+
* .on({ increment: z.object({ by: z.number() }) })
|
|
269
277
|
* .emit((action) => ["Incremented", { amount: action.by }])
|
|
270
278
|
* .build(); // Returns State<S, E, A, N>
|
|
271
279
|
* ```
|
|
@@ -289,8 +297,7 @@ export type ActionBuilder<S extends Schema, E extends Schemas, A extends Schemas
|
|
|
289
297
|
* 6. Snapshotting strategy via `.snap()`
|
|
290
298
|
*
|
|
291
299
|
* @template S - Zod schema type defining the shape of the state
|
|
292
|
-
* @param
|
|
293
|
-
* @param state - Zod schema defining the structure of the state
|
|
300
|
+
* @param entry - Single-key record mapping state name to Zod schema (e.g., `{ Counter: z.object({ count: z.number() }) }`)
|
|
294
301
|
* @returns A StateBuilder instance for fluent API configuration
|
|
295
302
|
*
|
|
296
303
|
* @example Basic counter state
|
|
@@ -298,7 +305,7 @@ export type ActionBuilder<S extends Schema, E extends Schemas, A extends Schemas
|
|
|
298
305
|
* import { state } from "@rotorsoft/act";
|
|
299
306
|
* import { z } from "zod";
|
|
300
307
|
*
|
|
301
|
-
* const Counter = state(
|
|
308
|
+
* const Counter = state({ Counter: z.object({ count: z.number() }) })
|
|
302
309
|
* .init(() => ({ count: 0 }))
|
|
303
310
|
* .emits({
|
|
304
311
|
* Incremented: z.object({ amount: z.number() })
|
|
@@ -306,18 +313,18 @@ export type ActionBuilder<S extends Schema, E extends Schemas, A extends Schemas
|
|
|
306
313
|
* .patch({
|
|
307
314
|
* Incremented: (event, state) => ({ count: state.count + event.data.amount })
|
|
308
315
|
* })
|
|
309
|
-
* .on(
|
|
316
|
+
* .on({ increment: z.object({ by: z.number() }) })
|
|
310
317
|
* .emit((action) => ["Incremented", { amount: action.by }])
|
|
311
318
|
* .build();
|
|
312
319
|
* ```
|
|
313
320
|
*
|
|
314
321
|
* @example State with multiple events and invariants
|
|
315
322
|
* ```typescript
|
|
316
|
-
* const BankAccount = state(
|
|
323
|
+
* const BankAccount = state({ BankAccount: z.object({
|
|
317
324
|
* balance: z.number(),
|
|
318
325
|
* currency: z.string(),
|
|
319
326
|
* status: z.enum(["open", "closed"])
|
|
320
|
-
* }))
|
|
327
|
+
* }) })
|
|
321
328
|
* .init(() => ({ balance: 0, currency: "USD", status: "open" }))
|
|
322
329
|
* .emits({
|
|
323
330
|
* Deposited: z.object({ amount: z.number() }),
|
|
@@ -329,19 +336,19 @@ export type ActionBuilder<S extends Schema, E extends Schemas, A extends Schemas
|
|
|
329
336
|
* Withdrawn: (event, state) => ({ balance: state.balance - event.data.amount }),
|
|
330
337
|
* Closed: () => ({ status: "closed", balance: 0 })
|
|
331
338
|
* })
|
|
332
|
-
* .on(
|
|
339
|
+
* .on({ deposit: z.object({ amount: z.number() }) })
|
|
333
340
|
* .given([
|
|
334
341
|
* (_, snap) => snap.state.status === "open" || "Account must be open"
|
|
335
342
|
* ])
|
|
336
343
|
* .emit((action) => ["Deposited", { amount: action.amount }])
|
|
337
|
-
* .on(
|
|
344
|
+
* .on({ withdraw: z.object({ amount: z.number() }) })
|
|
338
345
|
* .given([
|
|
339
346
|
* (_, snap) => snap.state.status === "open" || "Account must be open",
|
|
340
347
|
* (_, snap, action) =>
|
|
341
348
|
* snap.state.balance >= action.amount || "Insufficient funds"
|
|
342
349
|
* ])
|
|
343
350
|
* .emit((action) => ["Withdrawn", { amount: action.amount }])
|
|
344
|
-
* .on(
|
|
351
|
+
* .on({ close: z.object({}) })
|
|
345
352
|
* .given([
|
|
346
353
|
* (_, snap) => snap.state.status === "open" || "Already closed",
|
|
347
354
|
* (_, snap) => snap.state.balance === 0 || "Balance must be zero"
|
|
@@ -352,11 +359,11 @@ export type ActionBuilder<S extends Schema, E extends Schemas, A extends Schemas
|
|
|
352
359
|
*
|
|
353
360
|
* @example State with snapshotting
|
|
354
361
|
* ```typescript
|
|
355
|
-
* const User = state(
|
|
362
|
+
* const User = state({ User: z.object({
|
|
356
363
|
* name: z.string(),
|
|
357
364
|
* email: z.string(),
|
|
358
365
|
* loginCount: z.number()
|
|
359
|
-
* }))
|
|
366
|
+
* }) })
|
|
360
367
|
* .init((data) => ({ ...data, loginCount: 0 }))
|
|
361
368
|
* .emits({
|
|
362
369
|
* UserCreated: z.object({ name: z.string(), email: z.string() }),
|
|
@@ -366,9 +373,9 @@ export type ActionBuilder<S extends Schema, E extends Schemas, A extends Schemas
|
|
|
366
373
|
* UserCreated: (event) => event.data,
|
|
367
374
|
* UserLoggedIn: (_, state) => ({ loginCount: state.loginCount + 1 })
|
|
368
375
|
* })
|
|
369
|
-
* .on(
|
|
376
|
+
* .on({ createUser: z.object({ name: z.string(), email: z.string() }) })
|
|
370
377
|
* .emit((action) => ["UserCreated", action])
|
|
371
|
-
* .on(
|
|
378
|
+
* .on({ login: z.object({}) })
|
|
372
379
|
* .emit(() => ["UserLoggedIn", {}])
|
|
373
380
|
* .snap((snap) => snap.patches >= 10) // Snapshot every 10 events
|
|
374
381
|
* .build();
|
|
@@ -379,5 +386,6 @@ export type ActionBuilder<S extends Schema, E extends Schemas, A extends Schemas
|
|
|
379
386
|
* @see {@link https://rotorsoft.github.io/act-root/docs/intro | Getting Started Guide}
|
|
380
387
|
* @see {@link https://rotorsoft.github.io/act-root/docs/examples/calculator | Calculator Example}
|
|
381
388
|
*/
|
|
382
|
-
export declare function state<N extends string, S extends Schema>(
|
|
389
|
+
export declare function state<N extends string, S extends Schema>(entry: StateEntry<N, S>): StateBuilder<S, N>;
|
|
390
|
+
export {};
|
|
383
391
|
//# 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;AAE1B;;;;;;;;;;GAUG;AACH,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,MAAM,GAAG,MAAM,IAAI;IACtE;;;;;;;;;;;;;;;;;;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,CACL,KAAK,EAAE,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,KAEvB,aAAa,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;SACjC,CAAC;KACH,CAAC;CACH,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,aAAa,CACvB,CAAC,SAAS,MAAM,EAChB,CAAC,SAAS,OAAO,EACjB,CAAC,SAAS,OAAO,EACjB,CAAC,SAAS,MAAM,GAAG,MAAM,IACvB;IACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAsCG;IACH,EAAE,EAAE,CAAC,CAAC,SAAS,MAAM,EAAE,EAAE,SAAS,MAAM,EACtC,
|
|
1
|
+
{"version":3,"file":"state-builder.d.ts","sourceRoot":"","sources":["../../src/state-builder.ts"],"names":[],"mappings":"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"}
|
|
@@ -187,7 +187,7 @@ export type Committed<E extends Schemas, K extends keyof E> = Message<E, K> & Co
|
|
|
187
187
|
*
|
|
188
188
|
* @example Using snapshot in action handler
|
|
189
189
|
* ```typescript
|
|
190
|
-
* .on(
|
|
190
|
+
* .on({ increment: z.object({ by: z.number() }) })
|
|
191
191
|
* .emit((action, snapshot) => {
|
|
192
192
|
* console.log("Current count:", snapshot.state.count);
|
|
193
193
|
* console.log("Events applied:", snapshot.patches);
|
package/dist/@types/utils.d.ts
CHANGED
|
@@ -83,7 +83,7 @@ import type { Patch, Schema } from "./types/index.js";
|
|
|
83
83
|
* import { state } from "@rotorsoft/act";
|
|
84
84
|
* import { z } from "zod";
|
|
85
85
|
*
|
|
86
|
-
* const Counter = state(
|
|
86
|
+
* const Counter = state({ Counter: z.object({ count: z.number() }) })
|
|
87
87
|
* .init(() => ({ count: 0 }))
|
|
88
88
|
* .emits({ Incremented: z.object({ by: z.number() }) })
|
|
89
89
|
* .patch({
|
|
@@ -166,10 +166,10 @@ export declare const patch: <S extends Schema>(original: Readonly<S>, patches: R
|
|
|
166
166
|
* import { state } from "@rotorsoft/act";
|
|
167
167
|
* import { z } from "zod";
|
|
168
168
|
*
|
|
169
|
-
* const Counter = state(
|
|
169
|
+
* const Counter = state({ Counter: z.object({ count: z.number() }) })
|
|
170
170
|
* .init(() => ({ count: 0 }))
|
|
171
171
|
* .emits({ Incremented: z.object({ by: z.number().positive() }) })
|
|
172
|
-
* .on(
|
|
172
|
+
* .on({ increment: z.object({ by: z.number() }) })
|
|
173
173
|
* .emit((action) => {
|
|
174
174
|
* // validate() is called automatically before this runs
|
|
175
175
|
* // action.by is guaranteed to be a number
|
package/dist/index.cjs
CHANGED
|
@@ -53,10 +53,12 @@ __export(index_exports, {
|
|
|
53
53
|
dispose: () => dispose,
|
|
54
54
|
disposeAndExit: () => disposeAndExit,
|
|
55
55
|
extend: () => extend,
|
|
56
|
+
isProjection: () => isProjection,
|
|
56
57
|
isSlice: () => isSlice,
|
|
57
58
|
logger: () => logger,
|
|
58
59
|
patch: () => patch,
|
|
59
60
|
port: () => port,
|
|
61
|
+
projection: () => projection,
|
|
60
62
|
sleep: () => sleep,
|
|
61
63
|
slice: () => slice,
|
|
62
64
|
state: () => state,
|
|
@@ -1428,6 +1430,70 @@ var _this_ = ({ stream }) => ({
|
|
|
1428
1430
|
});
|
|
1429
1431
|
var _void_ = () => void 0;
|
|
1430
1432
|
|
|
1433
|
+
// src/projection-builder.ts
|
|
1434
|
+
function isProjection(x) {
|
|
1435
|
+
return x != null && x._tag === "Projection";
|
|
1436
|
+
}
|
|
1437
|
+
function projection(target, events = {}) {
|
|
1438
|
+
const defaultResolver = target ? { target } : void 0;
|
|
1439
|
+
const builder = {
|
|
1440
|
+
on: (entry) => {
|
|
1441
|
+
const keys = Object.keys(entry);
|
|
1442
|
+
if (keys.length !== 1) throw new Error(".on() requires exactly one key");
|
|
1443
|
+
const event = keys[0];
|
|
1444
|
+
const schema = entry[event];
|
|
1445
|
+
if (!(event in events)) {
|
|
1446
|
+
events[event] = {
|
|
1447
|
+
schema,
|
|
1448
|
+
reactions: /* @__PURE__ */ new Map()
|
|
1449
|
+
};
|
|
1450
|
+
}
|
|
1451
|
+
return {
|
|
1452
|
+
do: (handler) => {
|
|
1453
|
+
const reaction = {
|
|
1454
|
+
handler,
|
|
1455
|
+
resolver: defaultResolver ?? _this_,
|
|
1456
|
+
options: {
|
|
1457
|
+
blockOnError: true,
|
|
1458
|
+
maxRetries: 3
|
|
1459
|
+
}
|
|
1460
|
+
};
|
|
1461
|
+
const register = events[event];
|
|
1462
|
+
const name = handler.name || `${event}_${register.reactions.size}`;
|
|
1463
|
+
register.reactions.set(name, reaction);
|
|
1464
|
+
const nextBuilder = projection(
|
|
1465
|
+
target,
|
|
1466
|
+
events
|
|
1467
|
+
);
|
|
1468
|
+
return {
|
|
1469
|
+
...nextBuilder,
|
|
1470
|
+
to(resolver) {
|
|
1471
|
+
register.reactions.set(name, {
|
|
1472
|
+
...reaction,
|
|
1473
|
+
resolver: typeof resolver === "string" ? { target: resolver } : resolver
|
|
1474
|
+
});
|
|
1475
|
+
return nextBuilder;
|
|
1476
|
+
},
|
|
1477
|
+
void() {
|
|
1478
|
+
register.reactions.set(name, {
|
|
1479
|
+
...reaction,
|
|
1480
|
+
resolver: _void_
|
|
1481
|
+
});
|
|
1482
|
+
return nextBuilder;
|
|
1483
|
+
}
|
|
1484
|
+
};
|
|
1485
|
+
}
|
|
1486
|
+
};
|
|
1487
|
+
},
|
|
1488
|
+
build: () => ({
|
|
1489
|
+
_tag: "Projection",
|
|
1490
|
+
events
|
|
1491
|
+
}),
|
|
1492
|
+
events
|
|
1493
|
+
};
|
|
1494
|
+
return builder;
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1431
1497
|
// src/slice-builder.ts
|
|
1432
1498
|
function isSlice(x) {
|
|
1433
1499
|
return x != null && x._tag === "Slice";
|
|
@@ -1486,6 +1552,25 @@ function act(states = /* @__PURE__ */ new Map(), registry = {
|
|
|
1486
1552
|
}) {
|
|
1487
1553
|
const builder = {
|
|
1488
1554
|
with: ((input) => {
|
|
1555
|
+
if (isProjection(input)) {
|
|
1556
|
+
for (const eventName of Object.keys(input.events)) {
|
|
1557
|
+
const projRegister = input.events[eventName];
|
|
1558
|
+
const existing = registry.events[eventName];
|
|
1559
|
+
if (!existing) {
|
|
1560
|
+
registry.events[eventName] = {
|
|
1561
|
+
schema: projRegister.schema,
|
|
1562
|
+
reactions: new Map(projRegister.reactions)
|
|
1563
|
+
};
|
|
1564
|
+
} else {
|
|
1565
|
+
for (const [name, reaction] of projRegister.reactions) {
|
|
1566
|
+
let key = name;
|
|
1567
|
+
while (existing.reactions.has(key)) key = `${key}_p`;
|
|
1568
|
+
existing.reactions.set(key, reaction);
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
return act(states, registry);
|
|
1573
|
+
}
|
|
1489
1574
|
if (isSlice(input)) {
|
|
1490
1575
|
for (const s of input.states.values()) {
|
|
1491
1576
|
registerState(s, states, registry.actions, registry.events);
|
|
@@ -1546,7 +1631,11 @@ function act(states = /* @__PURE__ */ new Map(), registry = {
|
|
|
1546
1631
|
}
|
|
1547
1632
|
|
|
1548
1633
|
// src/state-builder.ts
|
|
1549
|
-
function state(
|
|
1634
|
+
function state(entry) {
|
|
1635
|
+
const keys = Object.keys(entry);
|
|
1636
|
+
if (keys.length !== 1) throw new Error("state() requires exactly one key");
|
|
1637
|
+
const name = keys[0];
|
|
1638
|
+
const stateSchema = entry[name];
|
|
1550
1639
|
return {
|
|
1551
1640
|
init(init) {
|
|
1552
1641
|
return {
|
|
@@ -1556,7 +1645,7 @@ function state(name, state2) {
|
|
|
1556
1645
|
return action_builder({
|
|
1557
1646
|
events,
|
|
1558
1647
|
actions: {},
|
|
1559
|
-
state:
|
|
1648
|
+
state: stateSchema,
|
|
1560
1649
|
name,
|
|
1561
1650
|
init,
|
|
1562
1651
|
patch: patch2,
|
|
@@ -1571,7 +1660,11 @@ function state(name, state2) {
|
|
|
1571
1660
|
}
|
|
1572
1661
|
function action_builder(state2) {
|
|
1573
1662
|
return {
|
|
1574
|
-
on(
|
|
1663
|
+
on(entry) {
|
|
1664
|
+
const keys = Object.keys(entry);
|
|
1665
|
+
if (keys.length !== 1) throw new Error(".on() requires exactly one key");
|
|
1666
|
+
const action2 = keys[0];
|
|
1667
|
+
const schema = entry[action2];
|
|
1575
1668
|
if (action2 in state2.actions)
|
|
1576
1669
|
throw new Error(`Duplicate action "${action2}"`);
|
|
1577
1670
|
const actions = { ...state2.actions, [action2]: schema };
|
|
@@ -1625,10 +1718,12 @@ function action_builder(state2) {
|
|
|
1625
1718
|
dispose,
|
|
1626
1719
|
disposeAndExit,
|
|
1627
1720
|
extend,
|
|
1721
|
+
isProjection,
|
|
1628
1722
|
isSlice,
|
|
1629
1723
|
logger,
|
|
1630
1724
|
patch,
|
|
1631
1725
|
port,
|
|
1726
|
+
projection,
|
|
1632
1727
|
sleep,
|
|
1633
1728
|
slice,
|
|
1634
1729
|
state,
|