@rytejs/core 0.5.0 → 0.7.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 CHANGED
@@ -1,9 +1,15 @@
1
- # @rytejs/core
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/helico-tech/rytejs/master/docs/public/logo.svg" width="120" alt="Ryte" />
3
+ </p>
2
4
 
3
- Type-safe workflow engine with Zod validation and middleware pipelines.
5
+ <h1 align="center">@rytejs/core</h1>
4
6
 
5
- [![CI](https://github.com/helico-tech/rytejs/actions/workflows/ci.yml/badge.svg)](https://github.com/helico-tech/rytejs/actions/workflows/ci.yml)
6
- [![npm](https://img.shields.io/npm/v/@rytejs/core)](https://www.npmjs.com/package/@rytejs/core)
7
+ <p align="center">Type-safe workflow engine with Zod validation and middleware pipelines.</p>
8
+
9
+ <p align="center">
10
+ <a href="https://github.com/helico-tech/rytejs/actions/workflows/ci.yml"><img src="https://github.com/helico-tech/rytejs/actions/workflows/ci.yml/badge.svg" alt="CI" /></a>
11
+ <a href="https://www.npmjs.com/package/@rytejs/core"><img src="https://img.shields.io/npm/v/@rytejs/core" alt="npm" /></a>
12
+ </p>
7
13
 
8
14
  ## Why Ryte?
9
15
 
@@ -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":[]}