@rytejs/core 0.1.0 → 0.3.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 +100 -0
- package/dist/index.cjs +223 -44
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +63 -8
- package/dist/index.d.ts +63 -8
- package/dist/index.js +220 -43
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ZodType, z } from 'zod';
|
|
2
2
|
|
|
3
3
|
interface WorkflowConfig {
|
|
4
|
+
modelVersion?: number;
|
|
4
5
|
states: Record<string, ZodType>;
|
|
5
6
|
commands: Record<string, ZodType>;
|
|
6
7
|
events: Record<string, ZodType>;
|
|
@@ -29,7 +30,7 @@ type Workflow<TConfig extends WorkflowConfig = WorkflowConfig> = {
|
|
|
29
30
|
}[StateNames<TConfig>];
|
|
30
31
|
type PipelineError<TConfig extends WorkflowConfig = WorkflowConfig> = {
|
|
31
32
|
category: "validation";
|
|
32
|
-
source: "command" | "state" | "event" | "transition";
|
|
33
|
+
source: "command" | "state" | "event" | "transition" | "restore";
|
|
33
34
|
issues: z.core.$ZodIssue[];
|
|
34
35
|
message: string;
|
|
35
36
|
} | {
|
|
@@ -54,9 +55,9 @@ type DispatchResult<TConfig extends WorkflowConfig = WorkflowConfig> = {
|
|
|
54
55
|
};
|
|
55
56
|
/** Thrown internally when Zod validation fails during dispatch. */
|
|
56
57
|
declare class ValidationError extends Error {
|
|
57
|
-
readonly source: "command" | "state" | "event" | "transition";
|
|
58
|
+
readonly source: "command" | "state" | "event" | "transition" | "restore";
|
|
58
59
|
readonly issues: z.core.$ZodIssue[];
|
|
59
|
-
constructor(source: "command" | "state" | "event" | "transition", issues: z.core.$ZodIssue[]);
|
|
60
|
+
constructor(source: "command" | "state" | "event" | "transition" | "restore", issues: z.core.$ZodIssue[]);
|
|
60
61
|
}
|
|
61
62
|
/** Thrown internally when a handler calls ctx.error(). Caught by the router. */
|
|
62
63
|
declare class DomainErrorSignal extends Error {
|
|
@@ -65,6 +66,17 @@ declare class DomainErrorSignal extends Error {
|
|
|
65
66
|
constructor(code: string, data: unknown);
|
|
66
67
|
}
|
|
67
68
|
|
|
69
|
+
/** A plain, JSON-safe representation of a workflow's state. */
|
|
70
|
+
interface WorkflowSnapshot<TConfig extends WorkflowConfig = WorkflowConfig> {
|
|
71
|
+
readonly id: string;
|
|
72
|
+
readonly definitionName: string;
|
|
73
|
+
readonly state: StateNames<TConfig>;
|
|
74
|
+
readonly data: unknown;
|
|
75
|
+
readonly createdAt: string;
|
|
76
|
+
readonly updatedAt: string;
|
|
77
|
+
readonly modelVersion: number;
|
|
78
|
+
}
|
|
79
|
+
|
|
68
80
|
/** The result of defineWorkflow() — holds schemas and creates workflow instances. */
|
|
69
81
|
interface WorkflowDefinition<TConfig extends WorkflowConfig = WorkflowConfig> {
|
|
70
82
|
readonly config: TConfig;
|
|
@@ -78,6 +90,14 @@ interface WorkflowDefinition<TConfig extends WorkflowConfig = WorkflowConfig> {
|
|
|
78
90
|
getEventSchema(eventName: string): ZodType;
|
|
79
91
|
getErrorSchema(errorCode: string): ZodType;
|
|
80
92
|
hasState(stateName: string): boolean;
|
|
93
|
+
snapshot(workflow: Workflow<TConfig>): WorkflowSnapshot<TConfig>;
|
|
94
|
+
restore(snapshot: WorkflowSnapshot<TConfig>): {
|
|
95
|
+
ok: true;
|
|
96
|
+
workflow: Workflow<TConfig>;
|
|
97
|
+
} | {
|
|
98
|
+
ok: false;
|
|
99
|
+
error: ValidationError;
|
|
100
|
+
};
|
|
81
101
|
}
|
|
82
102
|
/**
|
|
83
103
|
* Creates a workflow definition from a name and Zod schema configuration.
|
|
@@ -125,6 +145,9 @@ interface Context<TConfig extends WorkflowConfig, TDeps, TState extends StateNam
|
|
|
125
145
|
/** Terminal handler function — receives fully typed context with state and command narrowing. */
|
|
126
146
|
type Handler<TConfig extends WorkflowConfig, TDeps, TState extends StateNames<TConfig>, TCommand extends CommandNames<TConfig>> = (ctx: Context<TConfig, TDeps, TState, TCommand>) => void | Promise<void>;
|
|
127
147
|
|
|
148
|
+
/** The lifecycle hook event names. */
|
|
149
|
+
type HookEvent = "dispatch:start" | "dispatch:end" | "transition" | "error" | "event";
|
|
150
|
+
|
|
128
151
|
/**
|
|
129
152
|
* Koa-style middleware function with full context narrowing via defaults.
|
|
130
153
|
*
|
|
@@ -134,11 +157,20 @@ type Handler<TConfig extends WorkflowConfig, TDeps, TState extends StateNames<TC
|
|
|
134
157
|
*/
|
|
135
158
|
type Middleware<TConfig extends WorkflowConfig, TDeps, TState extends StateNames<TConfig> = StateNames<TConfig>, TCommand extends CommandNames<TConfig> = CommandNames<TConfig>> = (ctx: Context<TConfig, TDeps, TState, TCommand>, next: () => Promise<void>) => Promise<void>;
|
|
136
159
|
|
|
160
|
+
/**
|
|
161
|
+
* Read-only subset of Context for hook callbacks.
|
|
162
|
+
* Includes context-key access (set/get) but excludes dispatch mutation methods.
|
|
163
|
+
*/
|
|
164
|
+
type ReadonlyContext<TConfig extends WorkflowConfig, TDeps, TState extends StateNames<TConfig> = StateNames<TConfig>, TCommand extends CommandNames<TConfig> = CommandNames<TConfig>> = Omit<Context<TConfig, TDeps, TState, TCommand>, "update" | "transition" | "emit" | "error" | "getWorkflowSnapshot">;
|
|
165
|
+
|
|
137
166
|
type AnyMiddleware = (ctx: any, next: () => Promise<void>) => Promise<void>;
|
|
138
167
|
type HandlerEntry = {
|
|
139
168
|
inlineMiddleware: AnyMiddleware[];
|
|
140
169
|
handler: AnyMiddleware;
|
|
141
170
|
};
|
|
171
|
+
interface RouterOptions {
|
|
172
|
+
onHookError?: (error: unknown) => void;
|
|
173
|
+
}
|
|
142
174
|
declare class StateBuilder<TConfig extends WorkflowConfig, TDeps, TState extends StateNames<TConfig>> {
|
|
143
175
|
/** @internal */ readonly middleware: AnyMiddleware[];
|
|
144
176
|
/** @internal */ readonly handlers: Map<string, HandlerEntry>;
|
|
@@ -158,13 +190,26 @@ declare class WorkflowRouter<TConfig extends WorkflowConfig, TDeps = {}> {
|
|
|
158
190
|
private singleStateBuilders;
|
|
159
191
|
private multiStateBuilders;
|
|
160
192
|
private wildcardHandlers;
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
193
|
+
private hookRegistry;
|
|
194
|
+
private readonly onHookError;
|
|
195
|
+
constructor(definition: WorkflowDefinition<TConfig>, deps?: TDeps, options?: RouterOptions);
|
|
196
|
+
/** Adds global middleware, merges another router, or applies a plugin. */
|
|
197
|
+
use(arg: ((ctx: Context<TConfig, TDeps>, next: () => Promise<void>) => Promise<void>) | WorkflowRouter<TConfig, TDeps> | Plugin<TConfig, TDeps>): this;
|
|
198
|
+
private merge;
|
|
199
|
+
private mergeStateBuilders;
|
|
164
200
|
/** Registers handlers for one or more states. */
|
|
165
201
|
state<P extends StateNames<TConfig> | readonly StateNames<TConfig>[]>(name: P, setup: (state: StateBuilder<TConfig, TDeps, P extends readonly (infer S)[] ? S & StateNames<TConfig> : P & StateNames<TConfig>>) => void): this;
|
|
202
|
+
/** Registers a lifecycle hook callback. */
|
|
203
|
+
on(event: "dispatch:start", callback: (ctx: ReadonlyContext<TConfig, TDeps>) => void | Promise<void>): this;
|
|
204
|
+
on(event: "dispatch:end", callback: (ctx: ReadonlyContext<TConfig, TDeps>, result: DispatchResult<TConfig>) => void | Promise<void>): this;
|
|
205
|
+
on(event: "transition", callback: (from: StateNames<TConfig>, to: StateNames<TConfig>, workflow: Workflow<TConfig>) => void | Promise<void>): this;
|
|
206
|
+
on(event: "error", callback: (error: PipelineError<TConfig>, ctx: ReadonlyContext<TConfig, TDeps>) => void | Promise<void>): this;
|
|
207
|
+
on(event: "event", callback: (event: {
|
|
208
|
+
type: EventNames<TConfig>;
|
|
209
|
+
data: unknown;
|
|
210
|
+
}, workflow: Workflow<TConfig>) => void | Promise<void>): this;
|
|
166
211
|
/** Registers a wildcard handler that matches any state. */
|
|
167
|
-
on<C extends CommandNames<TConfig>>(
|
|
212
|
+
on<C extends CommandNames<TConfig>>(state: "*", command: C, ...fns: [
|
|
168
213
|
...AnyMiddleware[],
|
|
169
214
|
(ctx: Context<TConfig, TDeps, StateNames<TConfig>, C>) => void | Promise<void>
|
|
170
215
|
]): this;
|
|
@@ -175,4 +220,14 @@ declare class WorkflowRouter<TConfig extends WorkflowConfig, TDeps = {}> {
|
|
|
175
220
|
}): Promise<DispatchResult<TConfig>>;
|
|
176
221
|
}
|
|
177
222
|
|
|
178
|
-
|
|
223
|
+
declare const PLUGIN_SYMBOL: unique symbol;
|
|
224
|
+
/** A branded plugin function that can be passed to router.use(). */
|
|
225
|
+
type Plugin<TConfig extends WorkflowConfig, TDeps> = ((router: WorkflowRouter<TConfig, TDeps>) => void) & {
|
|
226
|
+
readonly [PLUGIN_SYMBOL]: true;
|
|
227
|
+
};
|
|
228
|
+
/** Brands a function as a Ryte plugin for use with router.use(). */
|
|
229
|
+
declare function definePlugin<TConfig extends WorkflowConfig, TDeps>(fn: (router: WorkflowRouter<TConfig, TDeps>) => void): Plugin<TConfig, TDeps>;
|
|
230
|
+
/** Checks whether a value is a branded Ryte plugin. */
|
|
231
|
+
declare function isPlugin(value: unknown): value is Plugin<WorkflowConfig, unknown>;
|
|
232
|
+
|
|
233
|
+
export { type CommandNames, type CommandPayload, type Context, type ContextKey, type DispatchResult, DomainErrorSignal, type ErrorCodes, type ErrorData, type EventData, type EventNames, type Handler, type HookEvent, type Middleware, type PipelineError, type Plugin, type ReadonlyContext, type RouterOptions, type StateData, type StateNames, ValidationError, type Workflow, type WorkflowConfig, type WorkflowDefinition, type WorkflowOf, WorkflowRouter, type WorkflowSnapshot, createKey, definePlugin, defineWorkflow, isPlugin };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ZodType, z } from 'zod';
|
|
2
2
|
|
|
3
3
|
interface WorkflowConfig {
|
|
4
|
+
modelVersion?: number;
|
|
4
5
|
states: Record<string, ZodType>;
|
|
5
6
|
commands: Record<string, ZodType>;
|
|
6
7
|
events: Record<string, ZodType>;
|
|
@@ -29,7 +30,7 @@ type Workflow<TConfig extends WorkflowConfig = WorkflowConfig> = {
|
|
|
29
30
|
}[StateNames<TConfig>];
|
|
30
31
|
type PipelineError<TConfig extends WorkflowConfig = WorkflowConfig> = {
|
|
31
32
|
category: "validation";
|
|
32
|
-
source: "command" | "state" | "event" | "transition";
|
|
33
|
+
source: "command" | "state" | "event" | "transition" | "restore";
|
|
33
34
|
issues: z.core.$ZodIssue[];
|
|
34
35
|
message: string;
|
|
35
36
|
} | {
|
|
@@ -54,9 +55,9 @@ type DispatchResult<TConfig extends WorkflowConfig = WorkflowConfig> = {
|
|
|
54
55
|
};
|
|
55
56
|
/** Thrown internally when Zod validation fails during dispatch. */
|
|
56
57
|
declare class ValidationError extends Error {
|
|
57
|
-
readonly source: "command" | "state" | "event" | "transition";
|
|
58
|
+
readonly source: "command" | "state" | "event" | "transition" | "restore";
|
|
58
59
|
readonly issues: z.core.$ZodIssue[];
|
|
59
|
-
constructor(source: "command" | "state" | "event" | "transition", issues: z.core.$ZodIssue[]);
|
|
60
|
+
constructor(source: "command" | "state" | "event" | "transition" | "restore", issues: z.core.$ZodIssue[]);
|
|
60
61
|
}
|
|
61
62
|
/** Thrown internally when a handler calls ctx.error(). Caught by the router. */
|
|
62
63
|
declare class DomainErrorSignal extends Error {
|
|
@@ -65,6 +66,17 @@ declare class DomainErrorSignal extends Error {
|
|
|
65
66
|
constructor(code: string, data: unknown);
|
|
66
67
|
}
|
|
67
68
|
|
|
69
|
+
/** A plain, JSON-safe representation of a workflow's state. */
|
|
70
|
+
interface WorkflowSnapshot<TConfig extends WorkflowConfig = WorkflowConfig> {
|
|
71
|
+
readonly id: string;
|
|
72
|
+
readonly definitionName: string;
|
|
73
|
+
readonly state: StateNames<TConfig>;
|
|
74
|
+
readonly data: unknown;
|
|
75
|
+
readonly createdAt: string;
|
|
76
|
+
readonly updatedAt: string;
|
|
77
|
+
readonly modelVersion: number;
|
|
78
|
+
}
|
|
79
|
+
|
|
68
80
|
/** The result of defineWorkflow() — holds schemas and creates workflow instances. */
|
|
69
81
|
interface WorkflowDefinition<TConfig extends WorkflowConfig = WorkflowConfig> {
|
|
70
82
|
readonly config: TConfig;
|
|
@@ -78,6 +90,14 @@ interface WorkflowDefinition<TConfig extends WorkflowConfig = WorkflowConfig> {
|
|
|
78
90
|
getEventSchema(eventName: string): ZodType;
|
|
79
91
|
getErrorSchema(errorCode: string): ZodType;
|
|
80
92
|
hasState(stateName: string): boolean;
|
|
93
|
+
snapshot(workflow: Workflow<TConfig>): WorkflowSnapshot<TConfig>;
|
|
94
|
+
restore(snapshot: WorkflowSnapshot<TConfig>): {
|
|
95
|
+
ok: true;
|
|
96
|
+
workflow: Workflow<TConfig>;
|
|
97
|
+
} | {
|
|
98
|
+
ok: false;
|
|
99
|
+
error: ValidationError;
|
|
100
|
+
};
|
|
81
101
|
}
|
|
82
102
|
/**
|
|
83
103
|
* Creates a workflow definition from a name and Zod schema configuration.
|
|
@@ -125,6 +145,9 @@ interface Context<TConfig extends WorkflowConfig, TDeps, TState extends StateNam
|
|
|
125
145
|
/** Terminal handler function — receives fully typed context with state and command narrowing. */
|
|
126
146
|
type Handler<TConfig extends WorkflowConfig, TDeps, TState extends StateNames<TConfig>, TCommand extends CommandNames<TConfig>> = (ctx: Context<TConfig, TDeps, TState, TCommand>) => void | Promise<void>;
|
|
127
147
|
|
|
148
|
+
/** The lifecycle hook event names. */
|
|
149
|
+
type HookEvent = "dispatch:start" | "dispatch:end" | "transition" | "error" | "event";
|
|
150
|
+
|
|
128
151
|
/**
|
|
129
152
|
* Koa-style middleware function with full context narrowing via defaults.
|
|
130
153
|
*
|
|
@@ -134,11 +157,20 @@ type Handler<TConfig extends WorkflowConfig, TDeps, TState extends StateNames<TC
|
|
|
134
157
|
*/
|
|
135
158
|
type Middleware<TConfig extends WorkflowConfig, TDeps, TState extends StateNames<TConfig> = StateNames<TConfig>, TCommand extends CommandNames<TConfig> = CommandNames<TConfig>> = (ctx: Context<TConfig, TDeps, TState, TCommand>, next: () => Promise<void>) => Promise<void>;
|
|
136
159
|
|
|
160
|
+
/**
|
|
161
|
+
* Read-only subset of Context for hook callbacks.
|
|
162
|
+
* Includes context-key access (set/get) but excludes dispatch mutation methods.
|
|
163
|
+
*/
|
|
164
|
+
type ReadonlyContext<TConfig extends WorkflowConfig, TDeps, TState extends StateNames<TConfig> = StateNames<TConfig>, TCommand extends CommandNames<TConfig> = CommandNames<TConfig>> = Omit<Context<TConfig, TDeps, TState, TCommand>, "update" | "transition" | "emit" | "error" | "getWorkflowSnapshot">;
|
|
165
|
+
|
|
137
166
|
type AnyMiddleware = (ctx: any, next: () => Promise<void>) => Promise<void>;
|
|
138
167
|
type HandlerEntry = {
|
|
139
168
|
inlineMiddleware: AnyMiddleware[];
|
|
140
169
|
handler: AnyMiddleware;
|
|
141
170
|
};
|
|
171
|
+
interface RouterOptions {
|
|
172
|
+
onHookError?: (error: unknown) => void;
|
|
173
|
+
}
|
|
142
174
|
declare class StateBuilder<TConfig extends WorkflowConfig, TDeps, TState extends StateNames<TConfig>> {
|
|
143
175
|
/** @internal */ readonly middleware: AnyMiddleware[];
|
|
144
176
|
/** @internal */ readonly handlers: Map<string, HandlerEntry>;
|
|
@@ -158,13 +190,26 @@ declare class WorkflowRouter<TConfig extends WorkflowConfig, TDeps = {}> {
|
|
|
158
190
|
private singleStateBuilders;
|
|
159
191
|
private multiStateBuilders;
|
|
160
192
|
private wildcardHandlers;
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
193
|
+
private hookRegistry;
|
|
194
|
+
private readonly onHookError;
|
|
195
|
+
constructor(definition: WorkflowDefinition<TConfig>, deps?: TDeps, options?: RouterOptions);
|
|
196
|
+
/** Adds global middleware, merges another router, or applies a plugin. */
|
|
197
|
+
use(arg: ((ctx: Context<TConfig, TDeps>, next: () => Promise<void>) => Promise<void>) | WorkflowRouter<TConfig, TDeps> | Plugin<TConfig, TDeps>): this;
|
|
198
|
+
private merge;
|
|
199
|
+
private mergeStateBuilders;
|
|
164
200
|
/** Registers handlers for one or more states. */
|
|
165
201
|
state<P extends StateNames<TConfig> | readonly StateNames<TConfig>[]>(name: P, setup: (state: StateBuilder<TConfig, TDeps, P extends readonly (infer S)[] ? S & StateNames<TConfig> : P & StateNames<TConfig>>) => void): this;
|
|
202
|
+
/** Registers a lifecycle hook callback. */
|
|
203
|
+
on(event: "dispatch:start", callback: (ctx: ReadonlyContext<TConfig, TDeps>) => void | Promise<void>): this;
|
|
204
|
+
on(event: "dispatch:end", callback: (ctx: ReadonlyContext<TConfig, TDeps>, result: DispatchResult<TConfig>) => void | Promise<void>): this;
|
|
205
|
+
on(event: "transition", callback: (from: StateNames<TConfig>, to: StateNames<TConfig>, workflow: Workflow<TConfig>) => void | Promise<void>): this;
|
|
206
|
+
on(event: "error", callback: (error: PipelineError<TConfig>, ctx: ReadonlyContext<TConfig, TDeps>) => void | Promise<void>): this;
|
|
207
|
+
on(event: "event", callback: (event: {
|
|
208
|
+
type: EventNames<TConfig>;
|
|
209
|
+
data: unknown;
|
|
210
|
+
}, workflow: Workflow<TConfig>) => void | Promise<void>): this;
|
|
166
211
|
/** Registers a wildcard handler that matches any state. */
|
|
167
|
-
on<C extends CommandNames<TConfig>>(
|
|
212
|
+
on<C extends CommandNames<TConfig>>(state: "*", command: C, ...fns: [
|
|
168
213
|
...AnyMiddleware[],
|
|
169
214
|
(ctx: Context<TConfig, TDeps, StateNames<TConfig>, C>) => void | Promise<void>
|
|
170
215
|
]): this;
|
|
@@ -175,4 +220,14 @@ declare class WorkflowRouter<TConfig extends WorkflowConfig, TDeps = {}> {
|
|
|
175
220
|
}): Promise<DispatchResult<TConfig>>;
|
|
176
221
|
}
|
|
177
222
|
|
|
178
|
-
|
|
223
|
+
declare const PLUGIN_SYMBOL: unique symbol;
|
|
224
|
+
/** A branded plugin function that can be passed to router.use(). */
|
|
225
|
+
type Plugin<TConfig extends WorkflowConfig, TDeps> = ((router: WorkflowRouter<TConfig, TDeps>) => void) & {
|
|
226
|
+
readonly [PLUGIN_SYMBOL]: true;
|
|
227
|
+
};
|
|
228
|
+
/** Brands a function as a Ryte plugin for use with router.use(). */
|
|
229
|
+
declare function definePlugin<TConfig extends WorkflowConfig, TDeps>(fn: (router: WorkflowRouter<TConfig, TDeps>) => void): Plugin<TConfig, TDeps>;
|
|
230
|
+
/** Checks whether a value is a branded Ryte plugin. */
|
|
231
|
+
declare function isPlugin(value: unknown): value is Plugin<WorkflowConfig, unknown>;
|
|
232
|
+
|
|
233
|
+
export { type CommandNames, type CommandPayload, type Context, type ContextKey, type DispatchResult, DomainErrorSignal, type ErrorCodes, type ErrorData, type EventData, type EventNames, type Handler, type HookEvent, type Middleware, type PipelineError, type Plugin, type ReadonlyContext, type RouterOptions, type StateData, type StateNames, ValidationError, type Workflow, type WorkflowConfig, type WorkflowDefinition, type WorkflowOf, WorkflowRouter, type WorkflowSnapshot, createKey, definePlugin, defineWorkflow, isPlugin };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
// src/types.ts
|
|
2
|
+
var ValidationError = class extends Error {
|
|
3
|
+
constructor(source, issues) {
|
|
4
|
+
super(`Validation failed (${source}): ${issues.map((i) => i.message).join(", ")}`);
|
|
5
|
+
this.source = source;
|
|
6
|
+
this.issues = issues;
|
|
7
|
+
this.name = "ValidationError";
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
var DomainErrorSignal = class extends Error {
|
|
11
|
+
constructor(code, data) {
|
|
12
|
+
super(`Domain error: ${code}`);
|
|
13
|
+
this.code = code;
|
|
14
|
+
this.data = data;
|
|
15
|
+
this.name = "DomainErrorSignal";
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
|
|
1
19
|
// src/definition.ts
|
|
2
20
|
function defineWorkflow(name, config) {
|
|
3
21
|
return {
|
|
@@ -44,6 +62,51 @@ function defineWorkflow(name, config) {
|
|
|
44
62
|
},
|
|
45
63
|
hasState(stateName) {
|
|
46
64
|
return stateName in config.states;
|
|
65
|
+
},
|
|
66
|
+
snapshot(workflow) {
|
|
67
|
+
return {
|
|
68
|
+
id: workflow.id,
|
|
69
|
+
definitionName: name,
|
|
70
|
+
state: workflow.state,
|
|
71
|
+
data: workflow.data,
|
|
72
|
+
createdAt: workflow.createdAt.toISOString(),
|
|
73
|
+
updatedAt: workflow.updatedAt.toISOString(),
|
|
74
|
+
modelVersion: config.modelVersion ?? 1
|
|
75
|
+
};
|
|
76
|
+
},
|
|
77
|
+
restore(snap) {
|
|
78
|
+
const stateSchema = config.states[snap.state];
|
|
79
|
+
if (!stateSchema) {
|
|
80
|
+
return {
|
|
81
|
+
ok: false,
|
|
82
|
+
error: new ValidationError("restore", [
|
|
83
|
+
{
|
|
84
|
+
code: "custom",
|
|
85
|
+
message: `Unknown state: ${snap.state}`,
|
|
86
|
+
input: snap.state,
|
|
87
|
+
path: ["state"]
|
|
88
|
+
}
|
|
89
|
+
])
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
const result = stateSchema.safeParse(snap.data);
|
|
93
|
+
if (!result.success) {
|
|
94
|
+
return {
|
|
95
|
+
ok: false,
|
|
96
|
+
error: new ValidationError("restore", result.error.issues)
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
ok: true,
|
|
101
|
+
workflow: {
|
|
102
|
+
id: snap.id,
|
|
103
|
+
definitionName: snap.definitionName,
|
|
104
|
+
state: snap.state,
|
|
105
|
+
data: result.data,
|
|
106
|
+
createdAt: new Date(snap.createdAt),
|
|
107
|
+
updatedAt: new Date(snap.updatedAt)
|
|
108
|
+
}
|
|
109
|
+
};
|
|
47
110
|
}
|
|
48
111
|
};
|
|
49
112
|
}
|
|
@@ -53,6 +116,17 @@ function createKey(name) {
|
|
|
53
116
|
return { id: Symbol(name) };
|
|
54
117
|
}
|
|
55
118
|
|
|
119
|
+
// src/plugin.ts
|
|
120
|
+
var PLUGIN_SYMBOL = /* @__PURE__ */ Symbol.for("ryte:plugin");
|
|
121
|
+
function definePlugin(fn) {
|
|
122
|
+
const plugin = fn;
|
|
123
|
+
Object.defineProperty(plugin, PLUGIN_SYMBOL, { value: true, writable: false });
|
|
124
|
+
return plugin;
|
|
125
|
+
}
|
|
126
|
+
function isPlugin(value) {
|
|
127
|
+
return typeof value === "function" && PLUGIN_SYMBOL in value;
|
|
128
|
+
}
|
|
129
|
+
|
|
56
130
|
// src/compose.ts
|
|
57
131
|
function compose(middleware) {
|
|
58
132
|
return async (ctx) => {
|
|
@@ -68,24 +142,6 @@ function compose(middleware) {
|
|
|
68
142
|
};
|
|
69
143
|
}
|
|
70
144
|
|
|
71
|
-
// src/types.ts
|
|
72
|
-
var ValidationError = class extends Error {
|
|
73
|
-
constructor(source, issues) {
|
|
74
|
-
super(`Validation failed (${source}): ${issues.map((i) => i.message).join(", ")}`);
|
|
75
|
-
this.source = source;
|
|
76
|
-
this.issues = issues;
|
|
77
|
-
this.name = "ValidationError";
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
var DomainErrorSignal = class extends Error {
|
|
81
|
-
constructor(code, data) {
|
|
82
|
-
super(`Domain error: ${code}`);
|
|
83
|
-
this.code = code;
|
|
84
|
-
this.data = data;
|
|
85
|
-
this.name = "DomainErrorSignal";
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
|
-
|
|
89
145
|
// src/context.ts
|
|
90
146
|
function createContext(definition, originalWorkflow, command, deps) {
|
|
91
147
|
let mutableState = originalWorkflow.state;
|
|
@@ -169,6 +225,46 @@ function createContext(definition, originalWorkflow, command, deps) {
|
|
|
169
225
|
return ctx;
|
|
170
226
|
}
|
|
171
227
|
|
|
228
|
+
// src/hooks.ts
|
|
229
|
+
var HOOK_EVENTS = /* @__PURE__ */ new Set([
|
|
230
|
+
"dispatch:start",
|
|
231
|
+
"dispatch:end",
|
|
232
|
+
"transition",
|
|
233
|
+
"error",
|
|
234
|
+
"event"
|
|
235
|
+
]);
|
|
236
|
+
var HookRegistry = class {
|
|
237
|
+
// biome-ignore lint/complexity/noBannedTypes: callbacks have varying signatures per hook event
|
|
238
|
+
hooks = /* @__PURE__ */ new Map();
|
|
239
|
+
/** Register a callback for a hook event. */
|
|
240
|
+
// biome-ignore lint/complexity/noBannedTypes: callbacks have varying signatures per hook event
|
|
241
|
+
add(event, callback) {
|
|
242
|
+
const existing = this.hooks.get(event) ?? [];
|
|
243
|
+
existing.push(callback);
|
|
244
|
+
this.hooks.set(event, existing);
|
|
245
|
+
}
|
|
246
|
+
/** Emit a hook event, calling all registered callbacks. Errors are caught and forwarded. */
|
|
247
|
+
async emit(event, onError, ...args) {
|
|
248
|
+
const callbacks = this.hooks.get(event);
|
|
249
|
+
if (!callbacks) return;
|
|
250
|
+
for (const cb of callbacks) {
|
|
251
|
+
try {
|
|
252
|
+
await cb(...args);
|
|
253
|
+
} catch (err) {
|
|
254
|
+
onError(err);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
/** Merge another registry's hooks into this one (used by composable routers). */
|
|
259
|
+
merge(other) {
|
|
260
|
+
for (const [event, callbacks] of other.hooks) {
|
|
261
|
+
const existing = this.hooks.get(event) ?? [];
|
|
262
|
+
existing.push(...callbacks);
|
|
263
|
+
this.hooks.set(event, existing);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
|
|
172
268
|
// src/router.ts
|
|
173
269
|
var StateBuilder = class {
|
|
174
270
|
/** @internal */
|
|
@@ -190,20 +286,68 @@ var StateBuilder = class {
|
|
|
190
286
|
return this;
|
|
191
287
|
}
|
|
192
288
|
};
|
|
193
|
-
var WorkflowRouter = class {
|
|
194
|
-
constructor(definition, deps = {}) {
|
|
289
|
+
var WorkflowRouter = class _WorkflowRouter {
|
|
290
|
+
constructor(definition, deps = {}, options = {}) {
|
|
195
291
|
this.definition = definition;
|
|
196
292
|
this.deps = deps;
|
|
293
|
+
this.onHookError = options.onHookError ?? console.error;
|
|
197
294
|
}
|
|
198
295
|
globalMiddleware = [];
|
|
296
|
+
// biome-ignore lint/suspicious/noExplicitAny: type erasure — builders store handlers for different state types
|
|
199
297
|
singleStateBuilders = /* @__PURE__ */ new Map();
|
|
298
|
+
// biome-ignore lint/suspicious/noExplicitAny: type erasure — builders store handlers for different state types
|
|
200
299
|
multiStateBuilders = /* @__PURE__ */ new Map();
|
|
201
300
|
wildcardHandlers = /* @__PURE__ */ new Map();
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
301
|
+
hookRegistry = new HookRegistry();
|
|
302
|
+
onHookError;
|
|
303
|
+
/** Adds global middleware, merges another router, or applies a plugin. */
|
|
304
|
+
use(arg) {
|
|
305
|
+
if (arg instanceof _WorkflowRouter) {
|
|
306
|
+
this.merge(arg);
|
|
307
|
+
} else if (isPlugin(arg)) {
|
|
308
|
+
arg(this);
|
|
309
|
+
} else {
|
|
310
|
+
this.globalMiddleware.push(arg);
|
|
311
|
+
}
|
|
205
312
|
return this;
|
|
206
313
|
}
|
|
314
|
+
merge(child) {
|
|
315
|
+
if (child.definition !== this.definition) {
|
|
316
|
+
throw new Error(
|
|
317
|
+
`Cannot merge router for '${child.definition.name}' into router for '${this.definition.name}': definition mismatch`
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
this.globalMiddleware.push(...child.globalMiddleware);
|
|
321
|
+
this.mergeStateBuilders(this.singleStateBuilders, child.singleStateBuilders);
|
|
322
|
+
this.mergeStateBuilders(this.multiStateBuilders, child.multiStateBuilders);
|
|
323
|
+
for (const [command, entry] of child.wildcardHandlers) {
|
|
324
|
+
if (!this.wildcardHandlers.has(command)) {
|
|
325
|
+
this.wildcardHandlers.set(command, {
|
|
326
|
+
inlineMiddleware: [...entry.inlineMiddleware],
|
|
327
|
+
handler: entry.handler
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
this.hookRegistry.merge(child.hookRegistry);
|
|
332
|
+
}
|
|
333
|
+
mergeStateBuilders(target, source) {
|
|
334
|
+
for (const [stateName, childBuilder] of source) {
|
|
335
|
+
let parentBuilder = target.get(stateName);
|
|
336
|
+
if (!parentBuilder) {
|
|
337
|
+
parentBuilder = new StateBuilder();
|
|
338
|
+
target.set(stateName, parentBuilder);
|
|
339
|
+
}
|
|
340
|
+
for (const [command, entry] of childBuilder.handlers) {
|
|
341
|
+
if (!parentBuilder.handlers.has(command)) {
|
|
342
|
+
parentBuilder.handlers.set(command, {
|
|
343
|
+
inlineMiddleware: [...entry.inlineMiddleware],
|
|
344
|
+
handler: entry.handler
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
parentBuilder.middleware.push(...childBuilder.middleware);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
207
351
|
/** Registers handlers for one or more states. */
|
|
208
352
|
state(name, setup) {
|
|
209
353
|
const names = Array.isArray(name) ? name : [name];
|
|
@@ -219,19 +363,29 @@ var WorkflowRouter = class {
|
|
|
219
363
|
}
|
|
220
364
|
return this;
|
|
221
365
|
}
|
|
222
|
-
|
|
223
|
-
on(
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
handler
|
|
233
|
-
|
|
234
|
-
|
|
366
|
+
// biome-ignore lint/suspicious/noExplicitAny: implementation signature must be loose to handle all overloads
|
|
367
|
+
on(...args) {
|
|
368
|
+
const first = args[0];
|
|
369
|
+
if (HOOK_EVENTS.has(first)) {
|
|
370
|
+
this.hookRegistry.add(first, args[1]);
|
|
371
|
+
return this;
|
|
372
|
+
}
|
|
373
|
+
if (first === "*") {
|
|
374
|
+
const command = args[1];
|
|
375
|
+
const fns = args.slice(2);
|
|
376
|
+
if (fns.length === 0) throw new Error("on() requires at least a handler");
|
|
377
|
+
const handler = fns.pop();
|
|
378
|
+
const inlineMiddleware = fns;
|
|
379
|
+
const wrappedHandler = async (ctx, _next) => {
|
|
380
|
+
await handler(ctx);
|
|
381
|
+
};
|
|
382
|
+
this.wildcardHandlers.set(command, {
|
|
383
|
+
inlineMiddleware,
|
|
384
|
+
handler: wrappedHandler
|
|
385
|
+
});
|
|
386
|
+
return this;
|
|
387
|
+
}
|
|
388
|
+
throw new Error(`Unknown event or state: ${first}`);
|
|
235
389
|
}
|
|
236
390
|
/** Dispatches a command to the appropriate handler and returns the result. */
|
|
237
391
|
async dispatch(workflow, command) {
|
|
@@ -305,17 +459,35 @@ var WorkflowRouter = class {
|
|
|
305
459
|
validatedCommand,
|
|
306
460
|
this.deps
|
|
307
461
|
);
|
|
462
|
+
await this.hookRegistry.emit("dispatch:start", this.onHookError, ctx);
|
|
308
463
|
try {
|
|
309
464
|
const composed = compose(chain);
|
|
310
465
|
await composed(ctx);
|
|
311
|
-
|
|
466
|
+
const result = {
|
|
312
467
|
ok: true,
|
|
313
468
|
workflow: ctx.getWorkflowSnapshot(),
|
|
314
469
|
events: [...ctx.events]
|
|
315
470
|
};
|
|
471
|
+
if (result.ok && result.workflow.state !== workflow.state) {
|
|
472
|
+
await this.hookRegistry.emit(
|
|
473
|
+
"transition",
|
|
474
|
+
this.onHookError,
|
|
475
|
+
workflow.state,
|
|
476
|
+
result.workflow.state,
|
|
477
|
+
result.workflow
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
if (result.ok) {
|
|
481
|
+
for (const event of result.events) {
|
|
482
|
+
await this.hookRegistry.emit("event", this.onHookError, event, result.workflow);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
await this.hookRegistry.emit("dispatch:end", this.onHookError, ctx, result);
|
|
486
|
+
return result;
|
|
316
487
|
} catch (err) {
|
|
488
|
+
let result;
|
|
317
489
|
if (err instanceof DomainErrorSignal) {
|
|
318
|
-
|
|
490
|
+
result = {
|
|
319
491
|
ok: false,
|
|
320
492
|
error: {
|
|
321
493
|
category: "domain",
|
|
@@ -323,9 +495,8 @@ var WorkflowRouter = class {
|
|
|
323
495
|
data: err.data
|
|
324
496
|
}
|
|
325
497
|
};
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
return {
|
|
498
|
+
} else if (err instanceof ValidationError) {
|
|
499
|
+
result = {
|
|
329
500
|
ok: false,
|
|
330
501
|
error: {
|
|
331
502
|
category: "validation",
|
|
@@ -334,8 +505,12 @@ var WorkflowRouter = class {
|
|
|
334
505
|
message: err.message
|
|
335
506
|
}
|
|
336
507
|
};
|
|
508
|
+
} else {
|
|
509
|
+
throw err;
|
|
337
510
|
}
|
|
338
|
-
|
|
511
|
+
await this.hookRegistry.emit("error", this.onHookError, result.error, ctx);
|
|
512
|
+
await this.hookRegistry.emit("dispatch:end", this.onHookError, ctx, result);
|
|
513
|
+
return result;
|
|
339
514
|
}
|
|
340
515
|
}
|
|
341
516
|
};
|
|
@@ -344,6 +519,8 @@ export {
|
|
|
344
519
|
ValidationError,
|
|
345
520
|
WorkflowRouter,
|
|
346
521
|
createKey,
|
|
347
|
-
|
|
522
|
+
definePlugin,
|
|
523
|
+
defineWorkflow,
|
|
524
|
+
isPlugin
|
|
348
525
|
};
|
|
349
526
|
//# sourceMappingURL=index.js.map
|