@rotorsoft/act 0.9.0 → 0.10.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/dist/.tsbuildinfo +1 -1
- package/dist/@types/act-builder.d.ts +3 -2
- 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 +25 -20
- 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 +1 -1
- package/dist/index.cjs +92 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +90 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -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,10 @@ export type StateBuilder<S extends Schema, N extends string = string> = {
|
|
|
81
81
|
};
|
|
82
82
|
};
|
|
83
83
|
};
|
|
84
|
+
/** Helper: a single-key record mapping an action name to its Zod schema. */
|
|
85
|
+
type ActionEntry<K extends string = string, AX extends Schema = Schema> = {
|
|
86
|
+
[P in K]: ZodType<AX>;
|
|
87
|
+
};
|
|
84
88
|
/**
|
|
85
89
|
* Builder interface for defining actions (commands) on a state.
|
|
86
90
|
*
|
|
@@ -98,24 +102,27 @@ export type ActionBuilder<S extends Schema, E extends Schemas, A extends Schemas
|
|
|
98
102
|
* Defines an action (command) that can be executed on this state.
|
|
99
103
|
*
|
|
100
104
|
* Actions represent intents to change state - they should be named in imperative form
|
|
101
|
-
* (e.g., "
|
|
105
|
+
* (e.g., "CreateUser", "IncrementCounter", "PlaceOrder"). Actions are validated against
|
|
102
106
|
* their schema and must emit at least one event.
|
|
103
107
|
*
|
|
108
|
+
* Pass a `{ ActionName: schema }` record — use shorthand `{ ActionName }`
|
|
109
|
+
* when the variable name matches the action name. The key becomes the
|
|
110
|
+
* action name, the value the Zod schema.
|
|
111
|
+
*
|
|
104
112
|
* @template K - Action name (string literal type)
|
|
105
113
|
* @template AX - Action payload schema type
|
|
106
|
-
* @param
|
|
107
|
-
* @param schema - Zod schema for the action payload
|
|
114
|
+
* @param entry - Single-key record `{ ActionName: schema }`
|
|
108
115
|
* @returns An object with `.given()` and `.emit()` for further configuration
|
|
109
116
|
*
|
|
110
117
|
* @example Simple action without invariants
|
|
111
118
|
* ```typescript
|
|
112
|
-
* .on(
|
|
119
|
+
* .on({ increment: z.object({ by: z.number() }) })
|
|
113
120
|
* .emit((action) => ["Incremented", { amount: action.by }])
|
|
114
121
|
* ```
|
|
115
122
|
*
|
|
116
123
|
* @example Action with business rules
|
|
117
124
|
* ```typescript
|
|
118
|
-
* .on(
|
|
125
|
+
* .on({ withdraw: z.object({ amount: z.number() }) })
|
|
119
126
|
* .given([
|
|
120
127
|
* (_, snap) => snap.state.balance >= 0 || "Account closed",
|
|
121
128
|
* (_, snap, action) => snap.state.balance >= action.amount || "Insufficient funds"
|
|
@@ -123,17 +130,14 @@ export type ActionBuilder<S extends Schema, E extends Schemas, A extends Schemas
|
|
|
123
130
|
* .emit((action) => ["Withdrawn", { amount: action.amount }])
|
|
124
131
|
* ```
|
|
125
132
|
*
|
|
126
|
-
* @example Action
|
|
133
|
+
* @example Action with shorthand (variable name matches action name)
|
|
127
134
|
* ```typescript
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
* ["InventoryReserved", { orderId: action.orderId }],
|
|
132
|
-
* ["PaymentProcessed", { orderId: action.orderId }]
|
|
133
|
-
* ])
|
|
135
|
+
* const OpenTicket = z.object({ title: z.string() });
|
|
136
|
+
* .on({ OpenTicket })
|
|
137
|
+
* .emit((action) => ["TicketOpened", { title: action.title }])
|
|
134
138
|
* ```
|
|
135
139
|
*/
|
|
136
|
-
on: <K extends string, AX extends Schema>(
|
|
140
|
+
on: <K extends string, AX extends Schema>(entry: ActionEntry<K, AX>) => {
|
|
137
141
|
/**
|
|
138
142
|
* Adds business rule invariants that must hold before the action can execute.
|
|
139
143
|
*
|
|
@@ -265,7 +269,7 @@ export type ActionBuilder<S extends Schema, E extends Schemas, A extends Schemas
|
|
|
265
269
|
* .init(() => ({ count: 0 }))
|
|
266
270
|
* .emits({ Incremented: z.object({ amount: z.number() }) })
|
|
267
271
|
* .patch({ Incremented: (event, state) => ({ count: state.count + event.data.amount }) })
|
|
268
|
-
* .on(
|
|
272
|
+
* .on({ increment: z.object({ by: z.number() }) })
|
|
269
273
|
* .emit((action) => ["Incremented", { amount: action.by }])
|
|
270
274
|
* .build(); // Returns State<S, E, A, N>
|
|
271
275
|
* ```
|
|
@@ -306,7 +310,7 @@ export type ActionBuilder<S extends Schema, E extends Schemas, A extends Schemas
|
|
|
306
310
|
* .patch({
|
|
307
311
|
* Incremented: (event, state) => ({ count: state.count + event.data.amount })
|
|
308
312
|
* })
|
|
309
|
-
* .on(
|
|
313
|
+
* .on({ increment: z.object({ by: z.number() }) })
|
|
310
314
|
* .emit((action) => ["Incremented", { amount: action.by }])
|
|
311
315
|
* .build();
|
|
312
316
|
* ```
|
|
@@ -329,19 +333,19 @@ export type ActionBuilder<S extends Schema, E extends Schemas, A extends Schemas
|
|
|
329
333
|
* Withdrawn: (event, state) => ({ balance: state.balance - event.data.amount }),
|
|
330
334
|
* Closed: () => ({ status: "closed", balance: 0 })
|
|
331
335
|
* })
|
|
332
|
-
* .on(
|
|
336
|
+
* .on({ deposit: z.object({ amount: z.number() }) })
|
|
333
337
|
* .given([
|
|
334
338
|
* (_, snap) => snap.state.status === "open" || "Account must be open"
|
|
335
339
|
* ])
|
|
336
340
|
* .emit((action) => ["Deposited", { amount: action.amount }])
|
|
337
|
-
* .on(
|
|
341
|
+
* .on({ withdraw: z.object({ amount: z.number() }) })
|
|
338
342
|
* .given([
|
|
339
343
|
* (_, snap) => snap.state.status === "open" || "Account must be open",
|
|
340
344
|
* (_, snap, action) =>
|
|
341
345
|
* snap.state.balance >= action.amount || "Insufficient funds"
|
|
342
346
|
* ])
|
|
343
347
|
* .emit((action) => ["Withdrawn", { amount: action.amount }])
|
|
344
|
-
* .on(
|
|
348
|
+
* .on({ close: z.object({}) })
|
|
345
349
|
* .given([
|
|
346
350
|
* (_, snap) => snap.state.status === "open" || "Already closed",
|
|
347
351
|
* (_, snap) => snap.state.balance === 0 || "Balance must be zero"
|
|
@@ -366,9 +370,9 @@ export type ActionBuilder<S extends Schema, E extends Schemas, A extends Schemas
|
|
|
366
370
|
* UserCreated: (event) => event.data,
|
|
367
371
|
* UserLoggedIn: (_, state) => ({ loginCount: state.loginCount + 1 })
|
|
368
372
|
* })
|
|
369
|
-
* .on(
|
|
373
|
+
* .on({ createUser: z.object({ name: z.string(), email: z.string() }) })
|
|
370
374
|
* .emit((action) => ["UserCreated", action])
|
|
371
|
-
* .on(
|
|
375
|
+
* .on({ login: z.object({}) })
|
|
372
376
|
* .emit(() => ["UserLoggedIn", {}])
|
|
373
377
|
* .snap((snap) => snap.patches >= 10) // Snapshot every 10 events
|
|
374
378
|
* .build();
|
|
@@ -380,4 +384,5 @@ export type ActionBuilder<S extends Schema, E extends Schemas, A extends Schemas
|
|
|
380
384
|
* @see {@link https://rotorsoft.github.io/act-root/docs/examples/calculator | Calculator Example}
|
|
381
385
|
*/
|
|
382
386
|
export declare function state<N extends string, S extends Schema>(name: N, state: ZodType<S>): StateBuilder<S, N>;
|
|
387
|
+
export {};
|
|
383
388
|
//# 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
|
@@ -169,7 +169,7 @@ export declare const patch: <S extends Schema>(original: Readonly<S>, patches: R
|
|
|
169
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);
|
|
@@ -1571,7 +1656,11 @@ function state(name, state2) {
|
|
|
1571
1656
|
}
|
|
1572
1657
|
function action_builder(state2) {
|
|
1573
1658
|
return {
|
|
1574
|
-
on(
|
|
1659
|
+
on(entry) {
|
|
1660
|
+
const keys = Object.keys(entry);
|
|
1661
|
+
if (keys.length !== 1) throw new Error(".on() requires exactly one key");
|
|
1662
|
+
const action2 = keys[0];
|
|
1663
|
+
const schema = entry[action2];
|
|
1575
1664
|
if (action2 in state2.actions)
|
|
1576
1665
|
throw new Error(`Duplicate action "${action2}"`);
|
|
1577
1666
|
const actions = { ...state2.actions, [action2]: schema };
|
|
@@ -1625,10 +1714,12 @@ function action_builder(state2) {
|
|
|
1625
1714
|
dispose,
|
|
1626
1715
|
disposeAndExit,
|
|
1627
1716
|
extend,
|
|
1717
|
+
isProjection,
|
|
1628
1718
|
isSlice,
|
|
1629
1719
|
logger,
|
|
1630
1720
|
patch,
|
|
1631
1721
|
port,
|
|
1722
|
+
projection,
|
|
1632
1723
|
sleep,
|
|
1633
1724
|
slice,
|
|
1634
1725
|
state,
|