@rytejs/core 0.6.0 → 0.7.1

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.
@@ -0,0 +1,19 @@
1
+ // src/compose.ts
2
+ function compose(middleware) {
3
+ return async (ctx) => {
4
+ let index = -1;
5
+ async function dispatch(i) {
6
+ if (i <= index) throw new Error("next() called multiple times");
7
+ index = i;
8
+ const fn = middleware[i];
9
+ if (!fn) return;
10
+ await fn(ctx, () => dispatch(i + 1));
11
+ }
12
+ await dispatch(0);
13
+ };
14
+ }
15
+
16
+ export {
17
+ compose
18
+ };
19
+ //# sourceMappingURL=chunk-YTJGSTKG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/compose.ts"],"sourcesContent":["type Middleware<TCtx> = (ctx: TCtx, next: () => Promise<void>) => Promise<void>;\n\n/** Composes an array of middleware into a single function (Koa-style onion model). */\nexport function compose<TCtx>(middleware: Middleware<TCtx>[]): (ctx: TCtx) => Promise<void> {\n\treturn async (ctx: TCtx) => {\n\t\tlet index = -1;\n\t\tasync function dispatch(i: number): Promise<void> {\n\t\t\tif (i <= index) throw new Error(\"next() called multiple times\");\n\t\t\tindex = i;\n\t\t\tconst fn = middleware[i];\n\t\t\tif (!fn) return;\n\t\t\tawait fn(ctx, () => dispatch(i + 1));\n\t\t}\n\t\tawait dispatch(0);\n\t};\n}\n"],"mappings":";AAGO,SAAS,QAAc,YAA8D;AAC3F,SAAO,OAAO,QAAc;AAC3B,QAAI,QAAQ;AACZ,mBAAe,SAAS,GAA0B;AACjD,UAAI,KAAK,MAAO,OAAM,IAAI,MAAM,8BAA8B;AAC9D,cAAQ;AACR,YAAM,KAAK,WAAW,CAAC;AACvB,UAAI,CAAC,GAAI;AACT,YAAM,GAAG,KAAK,MAAM,SAAS,IAAI,CAAC,CAAC;AAAA,IACpC;AACA,UAAM,SAAS,CAAC;AAAA,EACjB;AACD;","names":[]}
@@ -0,0 +1,17 @@
1
+ // src/store/errors.ts
2
+ var ConcurrencyConflictError = class extends Error {
3
+ constructor(workflowId, expectedVersion, actualVersion) {
4
+ super(
5
+ `Concurrency conflict for workflow "${workflowId}": expected version ${expectedVersion}, actual ${actualVersion}`
6
+ );
7
+ this.workflowId = workflowId;
8
+ this.expectedVersion = expectedVersion;
9
+ this.actualVersion = actualVersion;
10
+ }
11
+ name = "ConcurrencyConflictError";
12
+ };
13
+
14
+ export {
15
+ ConcurrencyConflictError
16
+ };
17
+ //# sourceMappingURL=chunk-ZFRIE42B.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/store/errors.ts"],"sourcesContent":["export class ConcurrencyConflictError extends Error {\n\treadonly name = \"ConcurrencyConflictError\";\n\n\tconstructor(\n\t\treadonly workflowId: string,\n\t\treadonly expectedVersion: number,\n\t\treadonly actualVersion: number,\n\t) {\n\t\tsuper(\n\t\t\t`Concurrency conflict for workflow \"${workflowId}\": expected version ${expectedVersion}, actual ${actualVersion}`,\n\t\t);\n\t}\n}\n"],"mappings":";AAAO,IAAM,2BAAN,cAAuC,MAAM;AAAA,EAGnD,YACU,YACA,iBACA,eACR;AACD;AAAA,MACC,sCAAsC,UAAU,uBAAuB,eAAe,YAAY,aAAa;AAAA,IAChH;AANS;AACA;AACA;AAAA,EAKV;AAAA,EAVS,OAAO;AAWjB;","names":[]}
@@ -0,0 +1,185 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/executor/index.ts
21
+ var executor_exports = {};
22
+ __export(executor_exports, {
23
+ WorkflowExecutor: () => WorkflowExecutor
24
+ });
25
+ module.exports = __toCommonJS(executor_exports);
26
+
27
+ // src/compose.ts
28
+ function compose(middleware) {
29
+ return async (ctx) => {
30
+ let index = -1;
31
+ async function dispatch(i) {
32
+ if (i <= index) throw new Error("next() called multiple times");
33
+ index = i;
34
+ const fn = middleware[i];
35
+ if (!fn) return;
36
+ await fn(ctx, () => dispatch(i + 1));
37
+ }
38
+ await dispatch(0);
39
+ };
40
+ }
41
+
42
+ // src/store/errors.ts
43
+ var ConcurrencyConflictError = class extends Error {
44
+ constructor(workflowId, expectedVersion, actualVersion) {
45
+ super(
46
+ `Concurrency conflict for workflow "${workflowId}": expected version ${expectedVersion}, actual ${actualVersion}`
47
+ );
48
+ this.workflowId = workflowId;
49
+ this.expectedVersion = expectedVersion;
50
+ this.actualVersion = actualVersion;
51
+ }
52
+ name = "ConcurrencyConflictError";
53
+ };
54
+
55
+ // src/executor/executor.ts
56
+ var WorkflowExecutor = class {
57
+ constructor(router, store) {
58
+ this.router = router;
59
+ this.store = store;
60
+ }
61
+ middleware = [];
62
+ use(middleware) {
63
+ this.middleware.push(middleware);
64
+ return this;
65
+ }
66
+ async execute(id, command, options) {
67
+ const stored = await this.store.load(id);
68
+ if (!stored) {
69
+ return { ok: false, error: { category: "not_found", id } };
70
+ }
71
+ if (options?.expectedVersion !== void 0 && options.expectedVersion !== stored.version) {
72
+ return {
73
+ ok: false,
74
+ error: {
75
+ category: "conflict",
76
+ id,
77
+ expectedVersion: options.expectedVersion,
78
+ actualVersion: stored.version
79
+ }
80
+ };
81
+ }
82
+ const ctx = {
83
+ id,
84
+ command,
85
+ stored,
86
+ result: null,
87
+ snapshot: null,
88
+ events: []
89
+ };
90
+ try {
91
+ const chain = [...this.middleware, this.dispatchHandler()];
92
+ await compose(chain)(ctx);
93
+ } catch (err) {
94
+ return {
95
+ ok: false,
96
+ error: {
97
+ category: "unexpected",
98
+ error: err,
99
+ message: err instanceof Error ? err.message : String(err)
100
+ }
101
+ };
102
+ }
103
+ if (ctx.snapshot) {
104
+ const newVersion = stored.version + 1;
105
+ const savedSnapshot = { ...ctx.snapshot, version: newVersion };
106
+ try {
107
+ await this.store.save({
108
+ id,
109
+ snapshot: ctx.snapshot,
110
+ expectedVersion: stored.version,
111
+ events: ctx.events
112
+ });
113
+ } catch (err) {
114
+ if (err instanceof ConcurrencyConflictError) {
115
+ return {
116
+ ok: false,
117
+ error: {
118
+ category: "conflict",
119
+ id,
120
+ expectedVersion: stored.version,
121
+ actualVersion: err.actualVersion
122
+ }
123
+ };
124
+ }
125
+ return {
126
+ ok: false,
127
+ error: {
128
+ category: "unexpected",
129
+ error: err,
130
+ message: err instanceof Error ? err.message : String(err)
131
+ }
132
+ };
133
+ }
134
+ return {
135
+ ok: true,
136
+ snapshot: savedSnapshot,
137
+ version: newVersion,
138
+ events: ctx.events
139
+ };
140
+ }
141
+ if (ctx.result && !ctx.result.ok) {
142
+ return { ok: false, error: ctx.result.error };
143
+ }
144
+ return {
145
+ ok: false,
146
+ error: {
147
+ category: "unexpected",
148
+ error: new Error("Pipeline completed without setting snapshot or error"),
149
+ message: "Pipeline completed without setting snapshot or error"
150
+ }
151
+ };
152
+ }
153
+ dispatchHandler() {
154
+ const definition = this.router.definition;
155
+ const router = this.router;
156
+ return async (ctx, _next) => {
157
+ const restoreResult = definition.deserialize(ctx.stored.snapshot);
158
+ if (!restoreResult.ok) {
159
+ ctx.result = {
160
+ ok: false,
161
+ error: {
162
+ category: "restore",
163
+ id: ctx.id,
164
+ issues: restoreResult.error.issues
165
+ }
166
+ };
167
+ return;
168
+ }
169
+ const dispatchResult = await router.dispatch(restoreResult.workflow, ctx.command);
170
+ ctx.result = dispatchResult;
171
+ if (dispatchResult.ok) {
172
+ ctx.snapshot = definition.serialize(dispatchResult.workflow);
173
+ ctx.events = dispatchResult.events.map((e) => ({
174
+ type: e.type,
175
+ data: e.data
176
+ }));
177
+ }
178
+ };
179
+ }
180
+ };
181
+ // Annotate the CommonJS export names for ESM import in node:
182
+ 0 && (module.exports = {
183
+ WorkflowExecutor
184
+ });
185
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/executor/index.ts","../../src/compose.ts","../../src/store/errors.ts","../../src/executor/executor.ts"],"sourcesContent":["export { WorkflowExecutor } from \"./executor.js\";\nexport type {\n\tExecutionResult,\n\tExecutorContext,\n\tExecutorError,\n\tExecutorMiddleware,\n} from \"./types.js\";\n","type Middleware<TCtx> = (ctx: TCtx, next: () => Promise<void>) => Promise<void>;\n\n/** Composes an array of middleware into a single function (Koa-style onion model). */\nexport function compose<TCtx>(middleware: Middleware<TCtx>[]): (ctx: TCtx) => Promise<void> {\n\treturn async (ctx: TCtx) => {\n\t\tlet index = -1;\n\t\tasync function dispatch(i: number): Promise<void> {\n\t\t\tif (i <= index) throw new Error(\"next() called multiple times\");\n\t\t\tindex = i;\n\t\t\tconst fn = middleware[i];\n\t\t\tif (!fn) return;\n\t\t\tawait fn(ctx, () => dispatch(i + 1));\n\t\t}\n\t\tawait dispatch(0);\n\t};\n}\n","export class ConcurrencyConflictError extends Error {\n\treadonly name = \"ConcurrencyConflictError\";\n\n\tconstructor(\n\t\treadonly workflowId: string,\n\t\treadonly expectedVersion: number,\n\t\treadonly actualVersion: number,\n\t) {\n\t\tsuper(\n\t\t\t`Concurrency conflict for workflow \"${workflowId}\": expected version ${expectedVersion}, actual ${actualVersion}`,\n\t\t);\n\t}\n}\n","import { compose } from \"../compose.js\";\nimport type { WorkflowRouter } from \"../router.js\";\nimport type { WorkflowSnapshot } from \"../snapshot.js\";\nimport { ConcurrencyConflictError } from \"../store/errors.js\";\nimport type { StoreAdapter } from \"../store/types.js\";\nimport type { WorkflowConfig } from \"../types.js\";\nimport type { ExecutionResult, ExecutorContext, ExecutorMiddleware } from \"./types.js\";\n\nexport class WorkflowExecutor<TConfig extends WorkflowConfig> {\n\tprivate readonly middleware: ExecutorMiddleware[] = [];\n\n\tconstructor(\n\t\tpublic readonly router: WorkflowRouter<TConfig>,\n\t\tprivate readonly store: StoreAdapter,\n\t) {}\n\n\tuse(middleware: ExecutorMiddleware): this {\n\t\tthis.middleware.push(middleware);\n\t\treturn this;\n\t}\n\n\tasync execute(\n\t\tid: string,\n\t\tcommand: { type: string; payload: unknown },\n\t\toptions?: { expectedVersion?: number },\n\t): Promise<ExecutionResult> {\n\t\t// 1. Load\n\t\tconst stored = await this.store.load(id);\n\t\tif (!stored) {\n\t\t\treturn { ok: false, error: { category: \"not_found\", id } };\n\t\t}\n\n\t\t// 2. Optimistic version check\n\t\tif (options?.expectedVersion !== undefined && options.expectedVersion !== stored.version) {\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\terror: {\n\t\t\t\t\tcategory: \"conflict\",\n\t\t\t\t\tid,\n\t\t\t\t\texpectedVersion: options.expectedVersion,\n\t\t\t\t\tactualVersion: stored.version,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// 3. Build context\n\t\tconst ctx: ExecutorContext = {\n\t\t\tid,\n\t\t\tcommand,\n\t\t\tstored,\n\t\t\tresult: null,\n\t\t\tsnapshot: null,\n\t\t\tevents: [],\n\t\t};\n\n\t\t// 4. Run pipeline\n\t\ttry {\n\t\t\tconst chain = [...this.middleware, this.dispatchHandler()];\n\t\t\tawait compose(chain)(ctx);\n\t\t} catch (err) {\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\terror: {\n\t\t\t\t\tcategory: \"unexpected\",\n\t\t\t\t\terror: err,\n\t\t\t\t\tmessage: err instanceof Error ? err.message : String(err),\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// 5. Save if dispatch succeeded\n\t\tif (ctx.snapshot) {\n\t\t\tconst newVersion = stored.version + 1;\n\t\t\tconst savedSnapshot = { ...ctx.snapshot, version: newVersion };\n\n\t\t\ttry {\n\t\t\t\tawait this.store.save({\n\t\t\t\t\tid,\n\t\t\t\t\tsnapshot: ctx.snapshot,\n\t\t\t\t\texpectedVersion: stored.version,\n\t\t\t\t\tevents: ctx.events,\n\t\t\t\t});\n\t\t\t} catch (err) {\n\t\t\t\tif (err instanceof ConcurrencyConflictError) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tok: false,\n\t\t\t\t\t\terror: {\n\t\t\t\t\t\t\tcategory: \"conflict\",\n\t\t\t\t\t\t\tid,\n\t\t\t\t\t\t\texpectedVersion: stored.version,\n\t\t\t\t\t\t\tactualVersion: err.actualVersion,\n\t\t\t\t\t\t},\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\tok: false,\n\t\t\t\t\terror: {\n\t\t\t\t\t\tcategory: \"unexpected\",\n\t\t\t\t\t\terror: err,\n\t\t\t\t\t\tmessage: err instanceof Error ? err.message : String(err),\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tok: true,\n\t\t\t\tsnapshot: savedSnapshot,\n\t\t\t\tversion: newVersion,\n\t\t\t\tevents: ctx.events,\n\t\t\t};\n\t\t}\n\n\t\t// 6. Dispatch failed — return the error\n\t\tif (ctx.result && !ctx.result.ok) {\n\t\t\treturn { ok: false, error: ctx.result.error };\n\t\t}\n\n\t\treturn {\n\t\t\tok: false,\n\t\t\terror: {\n\t\t\t\tcategory: \"unexpected\",\n\t\t\t\terror: new Error(\"Pipeline completed without setting snapshot or error\"),\n\t\t\t\tmessage: \"Pipeline completed without setting snapshot or error\",\n\t\t\t},\n\t\t};\n\t}\n\n\tprivate dispatchHandler(): ExecutorMiddleware {\n\t\tconst definition = this.router.definition;\n\t\tconst router = this.router;\n\n\t\treturn async (ctx, _next) => {\n\t\t\tconst restoreResult = definition.deserialize(ctx.stored.snapshot);\n\t\t\tif (!restoreResult.ok) {\n\t\t\t\tctx.result = {\n\t\t\t\t\tok: false as const,\n\t\t\t\t\terror: {\n\t\t\t\t\t\tcategory: \"restore\" as const,\n\t\t\t\t\t\tid: ctx.id,\n\t\t\t\t\t\tissues: restoreResult.error.issues,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// as never: type erasure — executor holds WorkflowConfig base type,\n\t\t\t// but dispatch validates commands against Zod schemas at runtime\n\t\t\tconst dispatchResult = await router.dispatch(restoreResult.workflow, ctx.command as never);\n\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: type erasure — DispatchResult<TConfig> assigned to DispatchResult<WorkflowConfig>\n\t\t\tctx.result = dispatchResult as any;\n\n\t\t\tif (dispatchResult.ok) {\n\t\t\t\t// biome-ignore lint/suspicious/noExplicitAny: type erasure — TConfig narrows WorkflowSnapshot but ctx.snapshot is unparameterized\n\t\t\t\tctx.snapshot = definition.serialize(dispatchResult.workflow) as any as WorkflowSnapshot;\n\t\t\t\tctx.events = (dispatchResult.events as Array<{ type: string; data: unknown }>).map((e) => ({\n\t\t\t\t\ttype: e.type,\n\t\t\t\t\tdata: e.data,\n\t\t\t\t}));\n\t\t\t}\n\t\t};\n\t}\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,69 @@
1
+ import { c as WorkflowRouter } from '../plugin-DHS8yUmS.cjs';
2
+ import { S as StoredWorkflow, a as StoreAdapter } from '../types-BtMTMoOZ.cjs';
3
+ import { a as WorkflowSnapshot, P as PipelineError, W as WorkflowConfig, D as DispatchResult } from '../snapshot-D5iZubCz.cjs';
4
+ import 'zod';
5
+
6
+ interface ExecutorContext {
7
+ readonly id: string;
8
+ readonly command: {
9
+ type: string;
10
+ payload: unknown;
11
+ };
12
+ readonly stored: StoredWorkflow;
13
+ result: DispatchResult<WorkflowConfig> | {
14
+ ok: false;
15
+ error: ExecutorError;
16
+ } | null;
17
+ snapshot: WorkflowSnapshot | null;
18
+ events: Array<{
19
+ type: string;
20
+ data: unknown;
21
+ }>;
22
+ }
23
+ type ExecutorMiddleware = (ctx: ExecutorContext, next: () => Promise<void>) => Promise<void>;
24
+ type ExecutorError = {
25
+ category: "not_found";
26
+ id: string;
27
+ } | {
28
+ category: "conflict";
29
+ id: string;
30
+ expectedVersion: number;
31
+ actualVersion: number;
32
+ } | {
33
+ category: "restore";
34
+ id: string;
35
+ issues: unknown[];
36
+ } | {
37
+ category: "unexpected";
38
+ error: unknown;
39
+ message: string;
40
+ };
41
+ type ExecutionResult = {
42
+ ok: true;
43
+ snapshot: WorkflowSnapshot;
44
+ version: number;
45
+ events: Array<{
46
+ type: string;
47
+ data: unknown;
48
+ }>;
49
+ } | {
50
+ ok: false;
51
+ error: PipelineError<WorkflowConfig> | ExecutorError;
52
+ };
53
+
54
+ declare class WorkflowExecutor<TConfig extends WorkflowConfig> {
55
+ readonly router: WorkflowRouter<TConfig>;
56
+ private readonly store;
57
+ private readonly middleware;
58
+ constructor(router: WorkflowRouter<TConfig>, store: StoreAdapter);
59
+ use(middleware: ExecutorMiddleware): this;
60
+ execute(id: string, command: {
61
+ type: string;
62
+ payload: unknown;
63
+ }, options?: {
64
+ expectedVersion?: number;
65
+ }): Promise<ExecutionResult>;
66
+ private dispatchHandler;
67
+ }
68
+
69
+ export { type ExecutionResult, type ExecutorContext, type ExecutorError, type ExecutorMiddleware, WorkflowExecutor };
@@ -0,0 +1,69 @@
1
+ import { c as WorkflowRouter } from '../plugin-DHN3Pk52.js';
2
+ import { S as StoredWorkflow, a as StoreAdapter } from '../types-C0nlrs5c.js';
3
+ import { a as WorkflowSnapshot, P as PipelineError, W as WorkflowConfig, D as DispatchResult } from '../snapshot-D5iZubCz.js';
4
+ import 'zod';
5
+
6
+ interface ExecutorContext {
7
+ readonly id: string;
8
+ readonly command: {
9
+ type: string;
10
+ payload: unknown;
11
+ };
12
+ readonly stored: StoredWorkflow;
13
+ result: DispatchResult<WorkflowConfig> | {
14
+ ok: false;
15
+ error: ExecutorError;
16
+ } | null;
17
+ snapshot: WorkflowSnapshot | null;
18
+ events: Array<{
19
+ type: string;
20
+ data: unknown;
21
+ }>;
22
+ }
23
+ type ExecutorMiddleware = (ctx: ExecutorContext, next: () => Promise<void>) => Promise<void>;
24
+ type ExecutorError = {
25
+ category: "not_found";
26
+ id: string;
27
+ } | {
28
+ category: "conflict";
29
+ id: string;
30
+ expectedVersion: number;
31
+ actualVersion: number;
32
+ } | {
33
+ category: "restore";
34
+ id: string;
35
+ issues: unknown[];
36
+ } | {
37
+ category: "unexpected";
38
+ error: unknown;
39
+ message: string;
40
+ };
41
+ type ExecutionResult = {
42
+ ok: true;
43
+ snapshot: WorkflowSnapshot;
44
+ version: number;
45
+ events: Array<{
46
+ type: string;
47
+ data: unknown;
48
+ }>;
49
+ } | {
50
+ ok: false;
51
+ error: PipelineError<WorkflowConfig> | ExecutorError;
52
+ };
53
+
54
+ declare class WorkflowExecutor<TConfig extends WorkflowConfig> {
55
+ readonly router: WorkflowRouter<TConfig>;
56
+ private readonly store;
57
+ private readonly middleware;
58
+ constructor(router: WorkflowRouter<TConfig>, store: StoreAdapter);
59
+ use(middleware: ExecutorMiddleware): this;
60
+ execute(id: string, command: {
61
+ type: string;
62
+ payload: unknown;
63
+ }, options?: {
64
+ expectedVersion?: number;
65
+ }): Promise<ExecutionResult>;
66
+ private dispatchHandler;
67
+ }
68
+
69
+ export { type ExecutionResult, type ExecutorContext, type ExecutorError, type ExecutorMiddleware, WorkflowExecutor };
@@ -0,0 +1,137 @@
1
+ import {
2
+ compose
3
+ } from "../chunk-YTJGSTKG.js";
4
+ import {
5
+ ConcurrencyConflictError
6
+ } from "../chunk-ZFRIE42B.js";
7
+
8
+ // src/executor/executor.ts
9
+ var WorkflowExecutor = class {
10
+ constructor(router, store) {
11
+ this.router = router;
12
+ this.store = store;
13
+ }
14
+ middleware = [];
15
+ use(middleware) {
16
+ this.middleware.push(middleware);
17
+ return this;
18
+ }
19
+ async execute(id, command, options) {
20
+ const stored = await this.store.load(id);
21
+ if (!stored) {
22
+ return { ok: false, error: { category: "not_found", id } };
23
+ }
24
+ if (options?.expectedVersion !== void 0 && options.expectedVersion !== stored.version) {
25
+ return {
26
+ ok: false,
27
+ error: {
28
+ category: "conflict",
29
+ id,
30
+ expectedVersion: options.expectedVersion,
31
+ actualVersion: stored.version
32
+ }
33
+ };
34
+ }
35
+ const ctx = {
36
+ id,
37
+ command,
38
+ stored,
39
+ result: null,
40
+ snapshot: null,
41
+ events: []
42
+ };
43
+ try {
44
+ const chain = [...this.middleware, this.dispatchHandler()];
45
+ await compose(chain)(ctx);
46
+ } catch (err) {
47
+ return {
48
+ ok: false,
49
+ error: {
50
+ category: "unexpected",
51
+ error: err,
52
+ message: err instanceof Error ? err.message : String(err)
53
+ }
54
+ };
55
+ }
56
+ if (ctx.snapshot) {
57
+ const newVersion = stored.version + 1;
58
+ const savedSnapshot = { ...ctx.snapshot, version: newVersion };
59
+ try {
60
+ await this.store.save({
61
+ id,
62
+ snapshot: ctx.snapshot,
63
+ expectedVersion: stored.version,
64
+ events: ctx.events
65
+ });
66
+ } catch (err) {
67
+ if (err instanceof ConcurrencyConflictError) {
68
+ return {
69
+ ok: false,
70
+ error: {
71
+ category: "conflict",
72
+ id,
73
+ expectedVersion: stored.version,
74
+ actualVersion: err.actualVersion
75
+ }
76
+ };
77
+ }
78
+ return {
79
+ ok: false,
80
+ error: {
81
+ category: "unexpected",
82
+ error: err,
83
+ message: err instanceof Error ? err.message : String(err)
84
+ }
85
+ };
86
+ }
87
+ return {
88
+ ok: true,
89
+ snapshot: savedSnapshot,
90
+ version: newVersion,
91
+ events: ctx.events
92
+ };
93
+ }
94
+ if (ctx.result && !ctx.result.ok) {
95
+ return { ok: false, error: ctx.result.error };
96
+ }
97
+ return {
98
+ ok: false,
99
+ error: {
100
+ category: "unexpected",
101
+ error: new Error("Pipeline completed without setting snapshot or error"),
102
+ message: "Pipeline completed without setting snapshot or error"
103
+ }
104
+ };
105
+ }
106
+ dispatchHandler() {
107
+ const definition = this.router.definition;
108
+ const router = this.router;
109
+ return async (ctx, _next) => {
110
+ const restoreResult = definition.deserialize(ctx.stored.snapshot);
111
+ if (!restoreResult.ok) {
112
+ ctx.result = {
113
+ ok: false,
114
+ error: {
115
+ category: "restore",
116
+ id: ctx.id,
117
+ issues: restoreResult.error.issues
118
+ }
119
+ };
120
+ return;
121
+ }
122
+ const dispatchResult = await router.dispatch(restoreResult.workflow, ctx.command);
123
+ ctx.result = dispatchResult;
124
+ if (dispatchResult.ok) {
125
+ ctx.snapshot = definition.serialize(dispatchResult.workflow);
126
+ ctx.events = dispatchResult.events.map((e) => ({
127
+ type: e.type,
128
+ data: e.data
129
+ }));
130
+ }
131
+ };
132
+ }
133
+ };
134
+ export {
135
+ WorkflowExecutor
136
+ };
137
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/executor/executor.ts"],"sourcesContent":["import { compose } from \"../compose.js\";\nimport type { WorkflowRouter } from \"../router.js\";\nimport type { WorkflowSnapshot } from \"../snapshot.js\";\nimport { ConcurrencyConflictError } from \"../store/errors.js\";\nimport type { StoreAdapter } from \"../store/types.js\";\nimport type { WorkflowConfig } from \"../types.js\";\nimport type { ExecutionResult, ExecutorContext, ExecutorMiddleware } from \"./types.js\";\n\nexport class WorkflowExecutor<TConfig extends WorkflowConfig> {\n\tprivate readonly middleware: ExecutorMiddleware[] = [];\n\n\tconstructor(\n\t\tpublic readonly router: WorkflowRouter<TConfig>,\n\t\tprivate readonly store: StoreAdapter,\n\t) {}\n\n\tuse(middleware: ExecutorMiddleware): this {\n\t\tthis.middleware.push(middleware);\n\t\treturn this;\n\t}\n\n\tasync execute(\n\t\tid: string,\n\t\tcommand: { type: string; payload: unknown },\n\t\toptions?: { expectedVersion?: number },\n\t): Promise<ExecutionResult> {\n\t\t// 1. Load\n\t\tconst stored = await this.store.load(id);\n\t\tif (!stored) {\n\t\t\treturn { ok: false, error: { category: \"not_found\", id } };\n\t\t}\n\n\t\t// 2. Optimistic version check\n\t\tif (options?.expectedVersion !== undefined && options.expectedVersion !== stored.version) {\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\terror: {\n\t\t\t\t\tcategory: \"conflict\",\n\t\t\t\t\tid,\n\t\t\t\t\texpectedVersion: options.expectedVersion,\n\t\t\t\t\tactualVersion: stored.version,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// 3. Build context\n\t\tconst ctx: ExecutorContext = {\n\t\t\tid,\n\t\t\tcommand,\n\t\t\tstored,\n\t\t\tresult: null,\n\t\t\tsnapshot: null,\n\t\t\tevents: [],\n\t\t};\n\n\t\t// 4. Run pipeline\n\t\ttry {\n\t\t\tconst chain = [...this.middleware, this.dispatchHandler()];\n\t\t\tawait compose(chain)(ctx);\n\t\t} catch (err) {\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\terror: {\n\t\t\t\t\tcategory: \"unexpected\",\n\t\t\t\t\terror: err,\n\t\t\t\t\tmessage: err instanceof Error ? err.message : String(err),\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// 5. Save if dispatch succeeded\n\t\tif (ctx.snapshot) {\n\t\t\tconst newVersion = stored.version + 1;\n\t\t\tconst savedSnapshot = { ...ctx.snapshot, version: newVersion };\n\n\t\t\ttry {\n\t\t\t\tawait this.store.save({\n\t\t\t\t\tid,\n\t\t\t\t\tsnapshot: ctx.snapshot,\n\t\t\t\t\texpectedVersion: stored.version,\n\t\t\t\t\tevents: ctx.events,\n\t\t\t\t});\n\t\t\t} catch (err) {\n\t\t\t\tif (err instanceof ConcurrencyConflictError) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tok: false,\n\t\t\t\t\t\terror: {\n\t\t\t\t\t\t\tcategory: \"conflict\",\n\t\t\t\t\t\t\tid,\n\t\t\t\t\t\t\texpectedVersion: stored.version,\n\t\t\t\t\t\t\tactualVersion: err.actualVersion,\n\t\t\t\t\t\t},\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\tok: false,\n\t\t\t\t\terror: {\n\t\t\t\t\t\tcategory: \"unexpected\",\n\t\t\t\t\t\terror: err,\n\t\t\t\t\t\tmessage: err instanceof Error ? err.message : String(err),\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tok: true,\n\t\t\t\tsnapshot: savedSnapshot,\n\t\t\t\tversion: newVersion,\n\t\t\t\tevents: ctx.events,\n\t\t\t};\n\t\t}\n\n\t\t// 6. Dispatch failed — return the error\n\t\tif (ctx.result && !ctx.result.ok) {\n\t\t\treturn { ok: false, error: ctx.result.error };\n\t\t}\n\n\t\treturn {\n\t\t\tok: false,\n\t\t\terror: {\n\t\t\t\tcategory: \"unexpected\",\n\t\t\t\terror: new Error(\"Pipeline completed without setting snapshot or error\"),\n\t\t\t\tmessage: \"Pipeline completed without setting snapshot or error\",\n\t\t\t},\n\t\t};\n\t}\n\n\tprivate dispatchHandler(): ExecutorMiddleware {\n\t\tconst definition = this.router.definition;\n\t\tconst router = this.router;\n\n\t\treturn async (ctx, _next) => {\n\t\t\tconst restoreResult = definition.deserialize(ctx.stored.snapshot);\n\t\t\tif (!restoreResult.ok) {\n\t\t\t\tctx.result = {\n\t\t\t\t\tok: false as const,\n\t\t\t\t\terror: {\n\t\t\t\t\t\tcategory: \"restore\" as const,\n\t\t\t\t\t\tid: ctx.id,\n\t\t\t\t\t\tissues: restoreResult.error.issues,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// as never: type erasure — executor holds WorkflowConfig base type,\n\t\t\t// but dispatch validates commands against Zod schemas at runtime\n\t\t\tconst dispatchResult = await router.dispatch(restoreResult.workflow, ctx.command as never);\n\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: type erasure — DispatchResult<TConfig> assigned to DispatchResult<WorkflowConfig>\n\t\t\tctx.result = dispatchResult as any;\n\n\t\t\tif (dispatchResult.ok) {\n\t\t\t\t// biome-ignore lint/suspicious/noExplicitAny: type erasure — TConfig narrows WorkflowSnapshot but ctx.snapshot is unparameterized\n\t\t\t\tctx.snapshot = definition.serialize(dispatchResult.workflow) as any as WorkflowSnapshot;\n\t\t\t\tctx.events = (dispatchResult.events as Array<{ type: string; data: unknown }>).map((e) => ({\n\t\t\t\t\ttype: e.type,\n\t\t\t\t\tdata: e.data,\n\t\t\t\t}));\n\t\t\t}\n\t\t};\n\t}\n}\n"],"mappings":"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","names":[]}
package/dist/index.cjs CHANGED
@@ -25,6 +25,7 @@ __export(index_exports, {
25
25
  ValidationError: () => ValidationError,
26
26
  WorkflowRouter: () => WorkflowRouter,
27
27
  createKey: () => createKey,
28
+ defineGenericPlugin: () => defineGenericPlugin,
28
29
  defineMigrations: () => defineMigrations,
29
30
  definePlugin: () => definePlugin,
30
31
  defineWorkflow: () => defineWorkflow,
@@ -108,7 +109,13 @@ function defineWorkflow(name, config) {
108
109
  hasState(stateName) {
109
110
  return stateName in config.states;
110
111
  },
111
- snapshot(workflow) {
112
+ hasCommand(commandName) {
113
+ return commandName in config.commands;
114
+ },
115
+ hasEvent(eventName) {
116
+ return eventName in config.events;
117
+ },
118
+ serialize(workflow) {
112
119
  return {
113
120
  id: workflow.id,
114
121
  definitionName: name,
@@ -116,10 +123,11 @@ function defineWorkflow(name, config) {
116
123
  data: workflow.data,
117
124
  createdAt: workflow.createdAt.toISOString(),
118
125
  updatedAt: workflow.updatedAt.toISOString(),
119
- modelVersion: config.modelVersion ?? 1
126
+ modelVersion: config.modelVersion ?? 1,
127
+ version: workflow.version ?? 1
120
128
  };
121
129
  },
122
- restore(snap) {
130
+ deserialize(snap) {
123
131
  const stateSchema = config.states[snap.state];
124
132
  if (!stateSchema) {
125
133
  return {
@@ -283,6 +291,9 @@ function definePlugin(fn) {
283
291
  Object.defineProperty(plugin, PLUGIN_SYMBOL, { value: true, writable: false });
284
292
  return plugin;
285
293
  }
294
+ function defineGenericPlugin(fn) {
295
+ return definePlugin(fn);
296
+ }
286
297
  function isPlugin(value) {
287
298
  return typeof value === "function" && PLUGIN_SYMBOL in value;
288
299
  }
@@ -456,6 +467,8 @@ function createContext(definition, originalWorkflow, command, deps, options) {
456
467
  var HOOK_EVENTS = /* @__PURE__ */ new Set([
457
468
  "dispatch:start",
458
469
  "dispatch:end",
470
+ "pipeline:start",
471
+ "pipeline:end",
459
472
  "transition",
460
473
  "error",
461
474
  "event"
@@ -640,6 +653,25 @@ var WorkflowRouter = class _WorkflowRouter {
640
653
  * @returns A {@link DispatchResult} indicating success or failure with the updated workflow and events
641
654
  */
642
655
  async dispatch(workflow, command) {
656
+ await this.hookRegistry.emit("dispatch:start", this.onHookError, workflow, command);
657
+ let result;
658
+ try {
659
+ result = await this.executePipeline(workflow, command);
660
+ } catch (err) {
661
+ result = {
662
+ ok: false,
663
+ error: {
664
+ category: "unexpected",
665
+ error: err,
666
+ message: err instanceof Error ? err.message : String(err)
667
+ }
668
+ };
669
+ } finally {
670
+ await this.hookRegistry.emit("dispatch:end", this.onHookError, workflow, command, result);
671
+ }
672
+ return result;
673
+ }
674
+ async executePipeline(workflow, command) {
643
675
  if (!this.definition.hasState(workflow.state)) {
644
676
  return {
645
677
  ok: false,
@@ -711,7 +743,7 @@ var WorkflowRouter = class _WorkflowRouter {
711
743
  this.deps,
712
744
  { wrapDeps: this.wrapDeps }
713
745
  );
714
- await this.hookRegistry.emit("dispatch:start", this.onHookError, ctx);
746
+ await this.hookRegistry.emit("pipeline:start", this.onHookError, ctx);
715
747
  try {
716
748
  const composed = compose(chain);
717
749
  await composed(ctx);
@@ -734,7 +766,7 @@ var WorkflowRouter = class _WorkflowRouter {
734
766
  await this.hookRegistry.emit("event", this.onHookError, event, result.workflow);
735
767
  }
736
768
  }
737
- await this.hookRegistry.emit("dispatch:end", this.onHookError, ctx, result);
769
+ await this.hookRegistry.emit("pipeline:end", this.onHookError, ctx, result);
738
770
  return result;
739
771
  } catch (err) {
740
772
  let result;
@@ -778,7 +810,7 @@ var WorkflowRouter = class _WorkflowRouter {
778
810
  };
779
811
  }
780
812
  await this.hookRegistry.emit("error", this.onHookError, result.error, ctx);
781
- await this.hookRegistry.emit("dispatch:end", this.onHookError, ctx, result);
813
+ await this.hookRegistry.emit("pipeline:end", this.onHookError, ctx, result);
782
814
  return result;
783
815
  }
784
816
  }
@@ -790,6 +822,7 @@ var WorkflowRouter = class _WorkflowRouter {
790
822
  ValidationError,
791
823
  WorkflowRouter,
792
824
  createKey,
825
+ defineGenericPlugin,
793
826
  defineMigrations,
794
827
  definePlugin,
795
828
  defineWorkflow,