resumable-workflow 1.4.0 → 1.4.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.
- package/dist/index.d.mts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +29 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +29 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -68,6 +68,7 @@ declare class FileStorage implements StorageProvider {
|
|
|
68
68
|
private readonly baseDir;
|
|
69
69
|
constructor(baseDir?: string);
|
|
70
70
|
private ensureDir;
|
|
71
|
+
private validateRunId;
|
|
71
72
|
private getFilePath;
|
|
72
73
|
saveRun(state: WorkflowRunState<unknown, Record<string, unknown>>): Promise<void>;
|
|
73
74
|
getRun(runId: string): Promise<WorkflowRunState<unknown, Record<string, unknown>> | null>;
|
package/dist/index.d.ts
CHANGED
|
@@ -68,6 +68,7 @@ declare class FileStorage implements StorageProvider {
|
|
|
68
68
|
private readonly baseDir;
|
|
69
69
|
constructor(baseDir?: string);
|
|
70
70
|
private ensureDir;
|
|
71
|
+
private validateRunId;
|
|
71
72
|
private getFilePath;
|
|
72
73
|
saveRun(state: WorkflowRunState<unknown, Record<string, unknown>>): Promise<void>;
|
|
73
74
|
getRun(runId: string): Promise<WorkflowRunState<unknown, Record<string, unknown>> | null>;
|
package/dist/index.js
CHANGED
|
@@ -49,8 +49,26 @@ var FileStorage = class {
|
|
|
49
49
|
async ensureDir() {
|
|
50
50
|
await import_promises.default.mkdir(this.baseDir, { recursive: true });
|
|
51
51
|
}
|
|
52
|
+
validateRunId(runId) {
|
|
53
|
+
if (!runId || typeof runId !== "string") {
|
|
54
|
+
throw new Error(`Invalid runId: ${runId}`);
|
|
55
|
+
}
|
|
56
|
+
if (runId.includes("../") || runId.includes("..\\") || runId.startsWith("..")) {
|
|
57
|
+
throw new Error(`Invalid runId: Path traversal detected in ${runId}`);
|
|
58
|
+
}
|
|
59
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(runId)) {
|
|
60
|
+
throw new Error(`Invalid runId: Only alphanumeric characters, hyphens, and underscores are allowed in ${runId}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
52
63
|
getFilePath(runId) {
|
|
53
|
-
|
|
64
|
+
this.validateRunId(runId);
|
|
65
|
+
const filePath = import_node_path.default.join(this.baseDir, `${runId}.json`);
|
|
66
|
+
const resolvedPath = import_node_path.default.resolve(filePath);
|
|
67
|
+
const resolvedBaseDir = import_node_path.default.resolve(this.baseDir);
|
|
68
|
+
if (!resolvedPath.startsWith(resolvedBaseDir)) {
|
|
69
|
+
throw new Error(`Invalid runId: Path traversal detected`);
|
|
70
|
+
}
|
|
71
|
+
return resolvedPath;
|
|
54
72
|
}
|
|
55
73
|
async saveRun(state) {
|
|
56
74
|
await this.ensureDir();
|
|
@@ -60,10 +78,14 @@ var FileStorage = class {
|
|
|
60
78
|
);
|
|
61
79
|
}
|
|
62
80
|
async getRun(runId) {
|
|
81
|
+
this.validateRunId(runId);
|
|
63
82
|
try {
|
|
64
83
|
const data = await import_promises.default.readFile(this.getFilePath(runId), "utf-8");
|
|
65
84
|
return JSON.parse(data);
|
|
66
|
-
} catch (
|
|
85
|
+
} catch (e) {
|
|
86
|
+
if (e.message.includes("Invalid runId") || e.message.includes("Path traversal")) {
|
|
87
|
+
throw e;
|
|
88
|
+
}
|
|
67
89
|
return null;
|
|
68
90
|
}
|
|
69
91
|
}
|
|
@@ -99,9 +121,13 @@ var FileStorage = class {
|
|
|
99
121
|
}
|
|
100
122
|
}
|
|
101
123
|
async deleteRun(runId) {
|
|
124
|
+
this.validateRunId(runId);
|
|
102
125
|
try {
|
|
103
126
|
await import_promises.default.unlink(this.getFilePath(runId));
|
|
104
|
-
} catch (
|
|
127
|
+
} catch (e) {
|
|
128
|
+
if (e.message.includes("Invalid runId") || e.message.includes("Path traversal")) {
|
|
129
|
+
throw e;
|
|
130
|
+
}
|
|
105
131
|
}
|
|
106
132
|
}
|
|
107
133
|
};
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/core/engine.ts","../src/storage/file-storage.ts"],"sourcesContent":["export { createWorkflow, Workflow } from './core/engine';\nexport { FileStorage } from './storage/file-storage';\nexport type {\n Step,\n StorageProvider,\n WorkflowConfig,\n WorkflowResult,\n WorkflowRunState,\n WorkflowStatus,\n} from './types';\n","import { randomUUID } from 'node:crypto';\nimport { FileStorage } from '../storage/file-storage';\nimport type {\n StorageProvider,\n WorkflowConfig,\n WorkflowResult,\n WorkflowRunState,\n WorkflowStatus,\n} from '../types';\n\ntype Mutable<T> = {\n -readonly [P in keyof T]: T[P];\n};\n\nexport class Workflow<\n TInput = unknown,\n TState extends Record<string, unknown> = Record<string, unknown>,\n> {\n private readonly storage: StorageProvider;\n private readonly workflowId: string;\n private readonly config: WorkflowConfig<TInput, TState>;\n\n constructor(config: WorkflowConfig<TInput, TState>) {\n this.config = config;\n this.storage = config.storage || new FileStorage();\n this.workflowId = config.id;\n\n if (config.autoResume) {\n this.resumeAllIncomplete().catch((err) => {\n console.error(\n `[ResumableWorkflow: ${this.workflowId}] Auto-resume failed:`,\n err\n );\n });\n }\n }\n\n private async executeStep(\n currentState: Mutable<WorkflowRunState<TInput, TState>>,\n stepIndex: number\n ): Promise<void> {\n const step = this.config.steps[stepIndex];\n if (!step) {\n return;\n }\n\n try {\n const result = await step.run({\n input: currentState.input,\n state: currentState.state,\n });\n\n if (\n result !== null &&\n typeof result === 'object' &&\n !Array.isArray(result)\n ) {\n currentState.state = {\n ...currentState.state,\n ...(result as Partial<TState>),\n };\n } else if (result !== undefined) {\n (currentState.state as Record<string, unknown>)[\n step.name || `step_${stepIndex}`\n ] = result;\n }\n\n currentState.currentStepIndex = stepIndex + 1;\n\n if (currentState.currentStepIndex === this.config.steps.length) {\n currentState.status = 'completed';\n }\n\n await this.storage.saveRun(currentState);\n } catch (error) {\n currentState.status = 'failed';\n currentState.error =\n error instanceof Error ? error.message : String(error);\n await this.storage.saveRun(currentState);\n throw error;\n }\n }\n\n private async run(\n input: TInput,\n existingRun?: WorkflowRunState<TInput, TState>\n ): Promise<WorkflowResult<TState>> {\n const runId = existingRun?.runId || randomUUID();\n const currentState: Mutable<WorkflowRunState<TInput, TState>> =\n (existingRun as Mutable<WorkflowRunState<TInput, TState>>) || {\n workflowId: this.workflowId,\n runId,\n status: 'pending' as WorkflowStatus,\n currentStepIndex: 0,\n input,\n state: {} as TState,\n };\n\n if (!existingRun) {\n await this.storage.saveRun(currentState);\n }\n\n try {\n for (\n let i = currentState.currentStepIndex;\n i < this.config.steps.length;\n i++\n ) {\n await this.executeStep(currentState, i);\n }\n } catch (_error) {\n return {\n success: false,\n error: currentState.error || 'Unknown error',\n runId: currentState.runId,\n stepName:\n this.config.steps[currentState.currentStepIndex]?.name ||\n `step_${currentState.currentStepIndex}`,\n };\n }\n\n // Auto-cleanup if enabled and successful\n if (this.config.autoCleanup) {\n await this.storage.deleteRun(currentState.runId);\n }\n\n return {\n success: true,\n result: currentState.state,\n runId: currentState.runId,\n };\n }\n\n start(input: TInput): Promise<WorkflowResult<TState>> {\n return this.run(input);\n }\n\n async resume(runId: string): Promise<WorkflowResult<TState>> {\n const runState = await this.storage.getRun(runId);\n if (!runState) {\n return {\n success: false,\n error: `Run ${runId} not found`,\n runId,\n stepName: 'unknown',\n };\n }\n if (runState.status === 'completed') {\n return {\n success: true,\n result: runState.state as TState,\n runId: runState.runId,\n };\n }\n return this.run(\n runState.input as TInput,\n runState as WorkflowRunState<TInput, TState>\n );\n }\n\n listIncomplete(): Promise<\n WorkflowRunState<unknown, Record<string, unknown>>[]\n > {\n return this.storage.listIncompleteRuns(this.workflowId);\n }\n\n async resumeAllIncomplete(): Promise<WorkflowResult<TState>[]> {\n const incomplete = await this.storage.listIncompleteRuns(this.workflowId);\n return Promise.all(\n incomplete.map((runState) =>\n this.run(\n runState.input as TInput,\n runState as WorkflowRunState<TInput, TState>\n )\n )\n );\n }\n\n /**\n * Manually clears all completed workflow runs for this workflow ID from storage.\n */\n async clearCompleted(): Promise<void> {\n const completed = await this.storage.listCompletedRuns(this.workflowId);\n await Promise.all(completed.map((run) => this.storage.deleteRun(run.runId)));\n }\n}\n\n/**\n * Factory function for backward compatibility and ease of use\n */\nexport function createWorkflow<\n TInput = unknown,\n TState extends Record<string, unknown> = Record<string, unknown>,\n>(config: WorkflowConfig<TInput, TState>) {\n return new Workflow(config);\n}","import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport type { StorageProvider, WorkflowRunState } from '../types';\n\nexport class FileStorage implements StorageProvider {\n private readonly baseDir: string;\n\n constructor(baseDir = '.resumable-workflow') {\n this.baseDir = path.resolve(baseDir);\n }\n\n private async ensureDir(): Promise<void> {\n await fs.mkdir(this.baseDir, { recursive: true });\n }\n\n private getFilePath(runId: string): string {\n return path.join(this.baseDir, `${runId}.json`);\n }\n\n async saveRun(\n state: WorkflowRunState<unknown, Record<string, unknown>>\n ): Promise<void> {\n await this.ensureDir();\n await fs.writeFile(\n this.getFilePath(state.runId),\n JSON.stringify(state, null, 2)\n );\n }\n\n async getRun(\n runId: string\n ): Promise<WorkflowRunState<unknown, Record<string, unknown>> | null> {\n try {\n const data = await fs.readFile(this.getFilePath(runId), 'utf-8');\n return JSON.parse(data) as WorkflowRunState<\n unknown,\n Record<string, unknown>\n >;\n } catch (_e) {\n // Return null if file doesn't exist or is invalid\n return null;\n }\n }\n\n listIncompleteRuns(\n workflowId: string\n ): Promise<WorkflowRunState<unknown, Record<string, unknown>>[]> {\n return this.listRuns(workflowId, 'pending');\n }\n\n listCompletedRuns(\n workflowId: string\n ): Promise<WorkflowRunState<unknown, Record<string, unknown>>[]> {\n return this.listRuns(workflowId, 'completed');\n }\n\n private async listRuns(\n workflowId: string,\n status: 'pending' | 'completed'\n ): Promise<WorkflowRunState<unknown, Record<string, unknown>>[]> {\n try {\n await this.ensureDir();\n const files = await fs.readdir(this.baseDir);\n const runs: WorkflowRunState<unknown, Record<string, unknown>>[] = [];\n\n for (const file of files) {\n if (file.endsWith('.json')) {\n try {\n const data = await fs.readFile(\n path.join(this.baseDir, file),\n 'utf-8'\n );\n const run = JSON.parse(data) as WorkflowRunState<\n unknown,\n Record<string, unknown>\n >;\n if (run.workflowId === workflowId && run.status === status) {\n runs.push(run);\n }\n } catch (_e) {\n // Ignore corrupted files\n }\n }\n }\n return runs;\n } catch (_e) {\n return [];\n }\n }\n\n async deleteRun(runId: string): Promise<void> {\n try {\n await fs.unlink(this.getFilePath(runId));\n } catch (_e) {\n // Ignore if file doesn't exist\n }\n }\n}\n"],"mappings":"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","names":["path","fs"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/core/engine.ts","../src/storage/file-storage.ts"],"sourcesContent":["export { createWorkflow, Workflow } from './core/engine';\nexport { FileStorage } from './storage/file-storage';\nexport type {\n Step,\n StorageProvider,\n WorkflowConfig,\n WorkflowResult,\n WorkflowRunState,\n WorkflowStatus,\n} from './types';\n","import { randomUUID } from 'node:crypto';\nimport { FileStorage } from '../storage/file-storage';\nimport type {\n StorageProvider,\n WorkflowConfig,\n WorkflowResult,\n WorkflowRunState,\n WorkflowStatus,\n} from '../types';\n\ntype Mutable<T> = {\n -readonly [P in keyof T]: T[P];\n};\n\nexport class Workflow<\n TInput = unknown,\n TState extends Record<string, unknown> = Record<string, unknown>,\n> {\n private readonly storage: StorageProvider;\n private readonly workflowId: string;\n private readonly config: WorkflowConfig<TInput, TState>;\n\n constructor(config: WorkflowConfig<TInput, TState>) {\n this.config = config;\n this.storage = config.storage || new FileStorage();\n this.workflowId = config.id;\n\n if (config.autoResume) {\n this.resumeAllIncomplete().catch((err) => {\n console.error(\n `[ResumableWorkflow: ${this.workflowId}] Auto-resume failed:`,\n err\n );\n });\n }\n }\n\n private async executeStep(\n currentState: Mutable<WorkflowRunState<TInput, TState>>,\n stepIndex: number\n ): Promise<void> {\n const step = this.config.steps[stepIndex];\n if (!step) {\n return;\n }\n\n try {\n const result = await step.run({\n input: currentState.input,\n state: currentState.state,\n });\n\n if (\n result !== null &&\n typeof result === 'object' &&\n !Array.isArray(result)\n ) {\n currentState.state = {\n ...currentState.state,\n ...(result as Partial<TState>),\n };\n } else if (result !== undefined) {\n (currentState.state as Record<string, unknown>)[\n step.name || `step_${stepIndex}`\n ] = result;\n }\n\n currentState.currentStepIndex = stepIndex + 1;\n\n if (currentState.currentStepIndex === this.config.steps.length) {\n currentState.status = 'completed';\n }\n\n await this.storage.saveRun(currentState);\n } catch (error) {\n currentState.status = 'failed';\n currentState.error =\n error instanceof Error ? error.message : String(error);\n await this.storage.saveRun(currentState);\n throw error;\n }\n }\n\n private async run(\n input: TInput,\n existingRun?: WorkflowRunState<TInput, TState>\n ): Promise<WorkflowResult<TState>> {\n const runId = existingRun?.runId || randomUUID();\n const currentState: Mutable<WorkflowRunState<TInput, TState>> =\n (existingRun as Mutable<WorkflowRunState<TInput, TState>>) || {\n workflowId: this.workflowId,\n runId,\n status: 'pending' as WorkflowStatus,\n currentStepIndex: 0,\n input,\n state: {} as TState,\n };\n\n if (!existingRun) {\n await this.storage.saveRun(currentState);\n }\n\n try {\n for (\n let i = currentState.currentStepIndex;\n i < this.config.steps.length;\n i++\n ) {\n await this.executeStep(currentState, i);\n }\n } catch (_error) {\n return {\n success: false,\n error: currentState.error || 'Unknown error',\n runId: currentState.runId,\n stepName:\n this.config.steps[currentState.currentStepIndex]?.name ||\n `step_${currentState.currentStepIndex}`,\n };\n }\n\n // Auto-cleanup if enabled and successful\n if (this.config.autoCleanup) {\n await this.storage.deleteRun(currentState.runId);\n }\n\n return {\n success: true,\n result: currentState.state,\n runId: currentState.runId,\n };\n }\n\n start(input: TInput): Promise<WorkflowResult<TState>> {\n return this.run(input);\n }\n\n async resume(runId: string): Promise<WorkflowResult<TState>> {\n const runState = await this.storage.getRun(runId);\n if (!runState) {\n return {\n success: false,\n error: `Run ${runId} not found`,\n runId,\n stepName: 'unknown',\n };\n }\n if (runState.status === 'completed') {\n return {\n success: true,\n result: runState.state as TState,\n runId: runState.runId,\n };\n }\n return this.run(\n runState.input as TInput,\n runState as WorkflowRunState<TInput, TState>\n );\n }\n\n listIncomplete(): Promise<\n WorkflowRunState<unknown, Record<string, unknown>>[]\n > {\n return this.storage.listIncompleteRuns(this.workflowId);\n }\n\n async resumeAllIncomplete(): Promise<WorkflowResult<TState>[]> {\n const incomplete = await this.storage.listIncompleteRuns(this.workflowId);\n return Promise.all(\n incomplete.map((runState) =>\n this.run(\n runState.input as TInput,\n runState as WorkflowRunState<TInput, TState>\n )\n )\n );\n }\n\n /**\n * Manually clears all completed workflow runs for this workflow ID from storage.\n */\n async clearCompleted(): Promise<void> {\n const completed = await this.storage.listCompletedRuns(this.workflowId);\n await Promise.all(completed.map((run) => this.storage.deleteRun(run.runId)));\n }\n}\n\n/**\n * Factory function for backward compatibility and ease of use\n */\nexport function createWorkflow<\n TInput = unknown,\n TState extends Record<string, unknown> = Record<string, unknown>,\n>(config: WorkflowConfig<TInput, TState>) {\n return new Workflow(config);\n}","import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport type { StorageProvider, WorkflowRunState } from '../types';\n\nexport class FileStorage implements StorageProvider {\n private readonly baseDir: string;\n\n constructor(baseDir = '.resumable-workflow') {\n this.baseDir = path.resolve(baseDir);\n }\n\n private async ensureDir(): Promise<void> {\n await fs.mkdir(this.baseDir, { recursive: true });\n }\n\n private validateRunId(runId: string): void {\n if (!runId || typeof runId !== 'string') {\n throw new Error(`Invalid runId: ${runId}`);\n }\n \n // Check for path traversal attempts\n if (runId.includes('../') || runId.includes('..\\\\') || runId.startsWith('..')) {\n throw new Error(`Invalid runId: Path traversal detected in ${runId}`);\n }\n \n // Additional validation: only allow alphanumeric, hyphens, and underscores\n if (!/^[a-zA-Z0-9_-]+$/.test(runId)) {\n throw new Error(`Invalid runId: Only alphanumeric characters, hyphens, and underscores are allowed in ${runId}`);\n }\n }\n\n private getFilePath(runId: string): string {\n this.validateRunId(runId);\n const filePath = path.join(this.baseDir, `${runId}.json`);\n \n // Additional security check: ensure the resolved path is within the base directory\n const resolvedPath = path.resolve(filePath);\n const resolvedBaseDir = path.resolve(this.baseDir);\n \n if (!resolvedPath.startsWith(resolvedBaseDir)) {\n throw new Error(`Invalid runId: Path traversal detected`);\n }\n \n return resolvedPath;\n }\n\n async saveRun(\n state: WorkflowRunState<unknown, Record<string, unknown>>\n ): Promise<void> {\n await this.ensureDir();\n await fs.writeFile(\n this.getFilePath(state.runId),\n JSON.stringify(state, null, 2)\n );\n }\n\n async getRun(\n runId: string\n ): Promise<WorkflowRunState<unknown, Record<string, unknown>> | null> {\n // Validate runId first before attempting file operations\n this.validateRunId(runId);\n \n try {\n const data = await fs.readFile(this.getFilePath(runId), 'utf-8');\n return JSON.parse(data) as WorkflowRunState<\n unknown,\n Record<string, unknown>\n >;\n } catch (e) {\n // Only return null for file system errors, not validation errors\n // If it's a validation error, it should have been caught by validateRunId\n if ((e as Error).message.includes('Invalid runId') || (e as Error).message.includes('Path traversal')) {\n throw e; // Re-throw validation errors\n }\n // Return null if file doesn't exist or is invalid\n return null;\n }\n }\n\n listIncompleteRuns(\n workflowId: string\n ): Promise<WorkflowRunState<unknown, Record<string, unknown>>[]> {\n return this.listRuns(workflowId, 'pending');\n }\n\n listCompletedRuns(\n workflowId: string\n ): Promise<WorkflowRunState<unknown, Record<string, unknown>>[]> {\n return this.listRuns(workflowId, 'completed');\n }\n\n private async listRuns(\n workflowId: string,\n status: 'pending' | 'completed'\n ): Promise<WorkflowRunState<unknown, Record<string, unknown>>[]> {\n try {\n await this.ensureDir();\n const files = await fs.readdir(this.baseDir);\n const runs: WorkflowRunState<unknown, Record<string, unknown>>[] = [];\n\n for (const file of files) {\n if (file.endsWith('.json')) {\n try {\n const data = await fs.readFile(\n path.join(this.baseDir, file),\n 'utf-8'\n );\n const run = JSON.parse(data) as WorkflowRunState<\n unknown,\n Record<string, unknown>\n >;\n if (run.workflowId === workflowId && run.status === status) {\n runs.push(run);\n }\n } catch (_e) {\n // Ignore corrupted files\n }\n }\n }\n return runs;\n } catch (_e) {\n return [];\n }\n }\n\n async deleteRun(runId: string): Promise<void> {\n // Validate runId first before attempting file operations\n this.validateRunId(runId);\n \n try {\n await fs.unlink(this.getFilePath(runId));\n } catch (e) {\n // Only ignore file system errors, not validation errors\n if ((e as Error).message.includes('Invalid runId') || (e as Error).message.includes('Path traversal')) {\n throw e; // Re-throw validation errors\n }\n // Ignore if file doesn't exist\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,yBAA2B;;;ACA3B,sBAAe;AACf,uBAAiB;AAGV,IAAM,cAAN,MAA6C;AAAA,EAGlD,YAAY,UAAU,uBAAuB;AAC3C,SAAK,UAAU,iBAAAA,QAAK,QAAQ,OAAO;AAAA,EACrC;AAAA,EAEA,MAAc,YAA2B;AACvC,UAAM,gBAAAC,QAAG,MAAM,KAAK,SAAS,EAAE,WAAW,KAAK,CAAC;AAAA,EAClD;AAAA,EAEQ,cAAc,OAAqB;AACzC,QAAI,CAAC,SAAS,OAAO,UAAU,UAAU;AACvC,YAAM,IAAI,MAAM,kBAAkB,KAAK,EAAE;AAAA,IAC3C;AAGA,QAAI,MAAM,SAAS,KAAK,KAAK,MAAM,SAAS,MAAM,KAAK,MAAM,WAAW,IAAI,GAAG;AAC7E,YAAM,IAAI,MAAM,6CAA6C,KAAK,EAAE;AAAA,IACtE;AAGA,QAAI,CAAC,mBAAmB,KAAK,KAAK,GAAG;AACnC,YAAM,IAAI,MAAM,wFAAwF,KAAK,EAAE;AAAA,IACjH;AAAA,EACF;AAAA,EAEQ,YAAY,OAAuB;AACzC,SAAK,cAAc,KAAK;AACxB,UAAM,WAAW,iBAAAD,QAAK,KAAK,KAAK,SAAS,GAAG,KAAK,OAAO;AAGxD,UAAM,eAAe,iBAAAA,QAAK,QAAQ,QAAQ;AAC1C,UAAM,kBAAkB,iBAAAA,QAAK,QAAQ,KAAK,OAAO;AAEjD,QAAI,CAAC,aAAa,WAAW,eAAe,GAAG;AAC7C,YAAM,IAAI,MAAM,wCAAwC;AAAA,IAC1D;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,QACJ,OACe;AACf,UAAM,KAAK,UAAU;AACrB,UAAM,gBAAAC,QAAG;AAAA,MACP,KAAK,YAAY,MAAM,KAAK;AAAA,MAC5B,KAAK,UAAU,OAAO,MAAM,CAAC;AAAA,IAC/B;AAAA,EACF;AAAA,EAEA,MAAM,OACJ,OACoE;AAEpE,SAAK,cAAc,KAAK;AAExB,QAAI;AACF,YAAM,OAAO,MAAM,gBAAAA,QAAG,SAAS,KAAK,YAAY,KAAK,GAAG,OAAO;AAC/D,aAAO,KAAK,MAAM,IAAI;AAAA,IAIxB,SAAS,GAAG;AAGV,UAAK,EAAY,QAAQ,SAAS,eAAe,KAAM,EAAY,QAAQ,SAAS,gBAAgB,GAAG;AACrG,cAAM;AAAA,MACR;AAEA,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,mBACE,YAC+D;AAC/D,WAAO,KAAK,SAAS,YAAY,SAAS;AAAA,EAC5C;AAAA,EAEA,kBACE,YAC+D;AAC/D,WAAO,KAAK,SAAS,YAAY,WAAW;AAAA,EAC9C;AAAA,EAEA,MAAc,SACZ,YACA,QAC+D;AAC/D,QAAI;AACF,YAAM,KAAK,UAAU;AACrB,YAAM,QAAQ,MAAM,gBAAAA,QAAG,QAAQ,KAAK,OAAO;AAC3C,YAAM,OAA6D,CAAC;AAEpE,iBAAW,QAAQ,OAAO;AACxB,YAAI,KAAK,SAAS,OAAO,GAAG;AAC1B,cAAI;AACF,kBAAM,OAAO,MAAM,gBAAAA,QAAG;AAAA,cACpB,iBAAAD,QAAK,KAAK,KAAK,SAAS,IAAI;AAAA,cAC5B;AAAA,YACF;AACA,kBAAM,MAAM,KAAK,MAAM,IAAI;AAI3B,gBAAI,IAAI,eAAe,cAAc,IAAI,WAAW,QAAQ;AAC1D,mBAAK,KAAK,GAAG;AAAA,YACf;AAAA,UACF,SAAS,IAAI;AAAA,UAEb;AAAA,QACF;AAAA,MACF;AACA,aAAO;AAAA,IACT,SAAS,IAAI;AACX,aAAO,CAAC;AAAA,IACV;AAAA,EACF;AAAA,EAEA,MAAM,UAAU,OAA8B;AAE5C,SAAK,cAAc,KAAK;AAExB,QAAI;AACF,YAAM,gBAAAC,QAAG,OAAO,KAAK,YAAY,KAAK,CAAC;AAAA,IACzC,SAAS,GAAG;AAEV,UAAK,EAAY,QAAQ,SAAS,eAAe,KAAM,EAAY,QAAQ,SAAS,gBAAgB,GAAG;AACrG,cAAM;AAAA,MACR;AAAA,IAEF;AAAA,EACF;AACF;;;AD7HO,IAAM,WAAN,MAGL;AAAA,EAKA,YAAY,QAAwC;AAClD,SAAK,SAAS;AACd,SAAK,UAAU,OAAO,WAAW,IAAI,YAAY;AACjD,SAAK,aAAa,OAAO;AAEzB,QAAI,OAAO,YAAY;AACrB,WAAK,oBAAoB,EAAE,MAAM,CAAC,QAAQ;AACxC,gBAAQ;AAAA,UACN,uBAAuB,KAAK,UAAU;AAAA,UACtC;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEA,MAAc,YACZ,cACA,WACe;AACf,UAAM,OAAO,KAAK,OAAO,MAAM,SAAS;AACxC,QAAI,CAAC,MAAM;AACT;AAAA,IACF;AAEA,QAAI;AACF,YAAM,SAAS,MAAM,KAAK,IAAI;AAAA,QAC5B,OAAO,aAAa;AAAA,QACpB,OAAO,aAAa;AAAA,MACtB,CAAC;AAED,UACE,WAAW,QACX,OAAO,WAAW,YAClB,CAAC,MAAM,QAAQ,MAAM,GACrB;AACA,qBAAa,QAAQ;AAAA,UACnB,GAAG,aAAa;AAAA,UAChB,GAAI;AAAA,QACN;AAAA,MACF,WAAW,WAAW,QAAW;AAC/B,QAAC,aAAa,MACZ,KAAK,QAAQ,QAAQ,SAAS,EAChC,IAAI;AAAA,MACN;AAEA,mBAAa,mBAAmB,YAAY;AAE5C,UAAI,aAAa,qBAAqB,KAAK,OAAO,MAAM,QAAQ;AAC9D,qBAAa,SAAS;AAAA,MACxB;AAEA,YAAM,KAAK,QAAQ,QAAQ,YAAY;AAAA,IACzC,SAAS,OAAO;AACd,mBAAa,SAAS;AACtB,mBAAa,QACX,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACvD,YAAM,KAAK,QAAQ,QAAQ,YAAY;AACvC,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAc,IACZ,OACA,aACiC;AACjC,UAAM,QAAQ,aAAa,aAAS,+BAAW;AAC/C,UAAM,eACH,eAA6D;AAAA,MAC5D,YAAY,KAAK;AAAA,MACjB;AAAA,MACA,QAAQ;AAAA,MACR,kBAAkB;AAAA,MAClB;AAAA,MACA,OAAO,CAAC;AAAA,IACV;AAEF,QAAI,CAAC,aAAa;AAChB,YAAM,KAAK,QAAQ,QAAQ,YAAY;AAAA,IACzC;AAEA,QAAI;AACF,eACM,IAAI,aAAa,kBACrB,IAAI,KAAK,OAAO,MAAM,QACtB,KACA;AACA,cAAM,KAAK,YAAY,cAAc,CAAC;AAAA,MACxC;AAAA,IACF,SAAS,QAAQ;AACf,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO,aAAa,SAAS;AAAA,QAC7B,OAAO,aAAa;AAAA,QACpB,UACE,KAAK,OAAO,MAAM,aAAa,gBAAgB,GAAG,QAClD,QAAQ,aAAa,gBAAgB;AAAA,MACzC;AAAA,IACF;AAGA,QAAI,KAAK,OAAO,aAAa;AAC3B,YAAM,KAAK,QAAQ,UAAU,aAAa,KAAK;AAAA,IACjD;AAEA,WAAO;AAAA,MACL,SAAS;AAAA,MACT,QAAQ,aAAa;AAAA,MACrB,OAAO,aAAa;AAAA,IACtB;AAAA,EACF;AAAA,EAEA,MAAM,OAAgD;AACpD,WAAO,KAAK,IAAI,KAAK;AAAA,EACvB;AAAA,EAEA,MAAM,OAAO,OAAgD;AAC3D,UAAM,WAAW,MAAM,KAAK,QAAQ,OAAO,KAAK;AAChD,QAAI,CAAC,UAAU;AACb,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO,OAAO,KAAK;AAAA,QACnB;AAAA,QACA,UAAU;AAAA,MACZ;AAAA,IACF;AACA,QAAI,SAAS,WAAW,aAAa;AACnC,aAAO;AAAA,QACL,SAAS;AAAA,QACT,QAAQ,SAAS;AAAA,QACjB,OAAO,SAAS;AAAA,MAClB;AAAA,IACF;AACA,WAAO,KAAK;AAAA,MACV,SAAS;AAAA,MACT;AAAA,IACF;AAAA,EACF;AAAA,EAEA,iBAEE;AACA,WAAO,KAAK,QAAQ,mBAAmB,KAAK,UAAU;AAAA,EACxD;AAAA,EAEA,MAAM,sBAAyD;AAC7D,UAAM,aAAa,MAAM,KAAK,QAAQ,mBAAmB,KAAK,UAAU;AACxE,WAAO,QAAQ;AAAA,MACb,WAAW;AAAA,QAAI,CAAC,aACd,KAAK;AAAA,UACH,SAAS;AAAA,UACT;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,iBAAgC;AACpC,UAAM,YAAY,MAAM,KAAK,QAAQ,kBAAkB,KAAK,UAAU;AACtE,UAAM,QAAQ,IAAI,UAAU,IAAI,CAAC,QAAQ,KAAK,QAAQ,UAAU,IAAI,KAAK,CAAC,CAAC;AAAA,EAC7E;AACF;AAKO,SAAS,eAGd,QAAwC;AACxC,SAAO,IAAI,SAAS,MAAM;AAC5B;","names":["path","fs"]}
|
package/dist/index.mjs
CHANGED
|
@@ -11,8 +11,26 @@ var FileStorage = class {
|
|
|
11
11
|
async ensureDir() {
|
|
12
12
|
await fs.mkdir(this.baseDir, { recursive: true });
|
|
13
13
|
}
|
|
14
|
+
validateRunId(runId) {
|
|
15
|
+
if (!runId || typeof runId !== "string") {
|
|
16
|
+
throw new Error(`Invalid runId: ${runId}`);
|
|
17
|
+
}
|
|
18
|
+
if (runId.includes("../") || runId.includes("..\\") || runId.startsWith("..")) {
|
|
19
|
+
throw new Error(`Invalid runId: Path traversal detected in ${runId}`);
|
|
20
|
+
}
|
|
21
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(runId)) {
|
|
22
|
+
throw new Error(`Invalid runId: Only alphanumeric characters, hyphens, and underscores are allowed in ${runId}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
14
25
|
getFilePath(runId) {
|
|
15
|
-
|
|
26
|
+
this.validateRunId(runId);
|
|
27
|
+
const filePath = path.join(this.baseDir, `${runId}.json`);
|
|
28
|
+
const resolvedPath = path.resolve(filePath);
|
|
29
|
+
const resolvedBaseDir = path.resolve(this.baseDir);
|
|
30
|
+
if (!resolvedPath.startsWith(resolvedBaseDir)) {
|
|
31
|
+
throw new Error(`Invalid runId: Path traversal detected`);
|
|
32
|
+
}
|
|
33
|
+
return resolvedPath;
|
|
16
34
|
}
|
|
17
35
|
async saveRun(state) {
|
|
18
36
|
await this.ensureDir();
|
|
@@ -22,10 +40,14 @@ var FileStorage = class {
|
|
|
22
40
|
);
|
|
23
41
|
}
|
|
24
42
|
async getRun(runId) {
|
|
43
|
+
this.validateRunId(runId);
|
|
25
44
|
try {
|
|
26
45
|
const data = await fs.readFile(this.getFilePath(runId), "utf-8");
|
|
27
46
|
return JSON.parse(data);
|
|
28
|
-
} catch (
|
|
47
|
+
} catch (e) {
|
|
48
|
+
if (e.message.includes("Invalid runId") || e.message.includes("Path traversal")) {
|
|
49
|
+
throw e;
|
|
50
|
+
}
|
|
29
51
|
return null;
|
|
30
52
|
}
|
|
31
53
|
}
|
|
@@ -61,9 +83,13 @@ var FileStorage = class {
|
|
|
61
83
|
}
|
|
62
84
|
}
|
|
63
85
|
async deleteRun(runId) {
|
|
86
|
+
this.validateRunId(runId);
|
|
64
87
|
try {
|
|
65
88
|
await fs.unlink(this.getFilePath(runId));
|
|
66
|
-
} catch (
|
|
89
|
+
} catch (e) {
|
|
90
|
+
if (e.message.includes("Invalid runId") || e.message.includes("Path traversal")) {
|
|
91
|
+
throw e;
|
|
92
|
+
}
|
|
67
93
|
}
|
|
68
94
|
}
|
|
69
95
|
};
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/engine.ts","../src/storage/file-storage.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { FileStorage } from '../storage/file-storage';\nimport type {\n StorageProvider,\n WorkflowConfig,\n WorkflowResult,\n WorkflowRunState,\n WorkflowStatus,\n} from '../types';\n\ntype Mutable<T> = {\n -readonly [P in keyof T]: T[P];\n};\n\nexport class Workflow<\n TInput = unknown,\n TState extends Record<string, unknown> = Record<string, unknown>,\n> {\n private readonly storage: StorageProvider;\n private readonly workflowId: string;\n private readonly config: WorkflowConfig<TInput, TState>;\n\n constructor(config: WorkflowConfig<TInput, TState>) {\n this.config = config;\n this.storage = config.storage || new FileStorage();\n this.workflowId = config.id;\n\n if (config.autoResume) {\n this.resumeAllIncomplete().catch((err) => {\n console.error(\n `[ResumableWorkflow: ${this.workflowId}] Auto-resume failed:`,\n err\n );\n });\n }\n }\n\n private async executeStep(\n currentState: Mutable<WorkflowRunState<TInput, TState>>,\n stepIndex: number\n ): Promise<void> {\n const step = this.config.steps[stepIndex];\n if (!step) {\n return;\n }\n\n try {\n const result = await step.run({\n input: currentState.input,\n state: currentState.state,\n });\n\n if (\n result !== null &&\n typeof result === 'object' &&\n !Array.isArray(result)\n ) {\n currentState.state = {\n ...currentState.state,\n ...(result as Partial<TState>),\n };\n } else if (result !== undefined) {\n (currentState.state as Record<string, unknown>)[\n step.name || `step_${stepIndex}`\n ] = result;\n }\n\n currentState.currentStepIndex = stepIndex + 1;\n\n if (currentState.currentStepIndex === this.config.steps.length) {\n currentState.status = 'completed';\n }\n\n await this.storage.saveRun(currentState);\n } catch (error) {\n currentState.status = 'failed';\n currentState.error =\n error instanceof Error ? error.message : String(error);\n await this.storage.saveRun(currentState);\n throw error;\n }\n }\n\n private async run(\n input: TInput,\n existingRun?: WorkflowRunState<TInput, TState>\n ): Promise<WorkflowResult<TState>> {\n const runId = existingRun?.runId || randomUUID();\n const currentState: Mutable<WorkflowRunState<TInput, TState>> =\n (existingRun as Mutable<WorkflowRunState<TInput, TState>>) || {\n workflowId: this.workflowId,\n runId,\n status: 'pending' as WorkflowStatus,\n currentStepIndex: 0,\n input,\n state: {} as TState,\n };\n\n if (!existingRun) {\n await this.storage.saveRun(currentState);\n }\n\n try {\n for (\n let i = currentState.currentStepIndex;\n i < this.config.steps.length;\n i++\n ) {\n await this.executeStep(currentState, i);\n }\n } catch (_error) {\n return {\n success: false,\n error: currentState.error || 'Unknown error',\n runId: currentState.runId,\n stepName:\n this.config.steps[currentState.currentStepIndex]?.name ||\n `step_${currentState.currentStepIndex}`,\n };\n }\n\n // Auto-cleanup if enabled and successful\n if (this.config.autoCleanup) {\n await this.storage.deleteRun(currentState.runId);\n }\n\n return {\n success: true,\n result: currentState.state,\n runId: currentState.runId,\n };\n }\n\n start(input: TInput): Promise<WorkflowResult<TState>> {\n return this.run(input);\n }\n\n async resume(runId: string): Promise<WorkflowResult<TState>> {\n const runState = await this.storage.getRun(runId);\n if (!runState) {\n return {\n success: false,\n error: `Run ${runId} not found`,\n runId,\n stepName: 'unknown',\n };\n }\n if (runState.status === 'completed') {\n return {\n success: true,\n result: runState.state as TState,\n runId: runState.runId,\n };\n }\n return this.run(\n runState.input as TInput,\n runState as WorkflowRunState<TInput, TState>\n );\n }\n\n listIncomplete(): Promise<\n WorkflowRunState<unknown, Record<string, unknown>>[]\n > {\n return this.storage.listIncompleteRuns(this.workflowId);\n }\n\n async resumeAllIncomplete(): Promise<WorkflowResult<TState>[]> {\n const incomplete = await this.storage.listIncompleteRuns(this.workflowId);\n return Promise.all(\n incomplete.map((runState) =>\n this.run(\n runState.input as TInput,\n runState as WorkflowRunState<TInput, TState>\n )\n )\n );\n }\n\n /**\n * Manually clears all completed workflow runs for this workflow ID from storage.\n */\n async clearCompleted(): Promise<void> {\n const completed = await this.storage.listCompletedRuns(this.workflowId);\n await Promise.all(completed.map((run) => this.storage.deleteRun(run.runId)));\n }\n}\n\n/**\n * Factory function for backward compatibility and ease of use\n */\nexport function createWorkflow<\n TInput = unknown,\n TState extends Record<string, unknown> = Record<string, unknown>,\n>(config: WorkflowConfig<TInput, TState>) {\n return new Workflow(config);\n}","import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport type { StorageProvider, WorkflowRunState } from '../types';\n\nexport class FileStorage implements StorageProvider {\n private readonly baseDir: string;\n\n constructor(baseDir = '.resumable-workflow') {\n this.baseDir = path.resolve(baseDir);\n }\n\n private async ensureDir(): Promise<void> {\n await fs.mkdir(this.baseDir, { recursive: true });\n }\n\n private getFilePath(runId: string): string {\n return path.join(this.baseDir, `${runId}.json`);\n }\n\n async saveRun(\n state: WorkflowRunState<unknown, Record<string, unknown>>\n ): Promise<void> {\n await this.ensureDir();\n await fs.writeFile(\n this.getFilePath(state.runId),\n JSON.stringify(state, null, 2)\n );\n }\n\n async getRun(\n runId: string\n ): Promise<WorkflowRunState<unknown, Record<string, unknown>> | null> {\n try {\n const data = await fs.readFile(this.getFilePath(runId), 'utf-8');\n return JSON.parse(data) as WorkflowRunState<\n unknown,\n Record<string, unknown>\n >;\n } catch (_e) {\n // Return null if file doesn't exist or is invalid\n return null;\n }\n }\n\n listIncompleteRuns(\n workflowId: string\n ): Promise<WorkflowRunState<unknown, Record<string, unknown>>[]> {\n return this.listRuns(workflowId, 'pending');\n }\n\n listCompletedRuns(\n workflowId: string\n ): Promise<WorkflowRunState<unknown, Record<string, unknown>>[]> {\n return this.listRuns(workflowId, 'completed');\n }\n\n private async listRuns(\n workflowId: string,\n status: 'pending' | 'completed'\n ): Promise<WorkflowRunState<unknown, Record<string, unknown>>[]> {\n try {\n await this.ensureDir();\n const files = await fs.readdir(this.baseDir);\n const runs: WorkflowRunState<unknown, Record<string, unknown>>[] = [];\n\n for (const file of files) {\n if (file.endsWith('.json')) {\n try {\n const data = await fs.readFile(\n path.join(this.baseDir, file),\n 'utf-8'\n );\n const run = JSON.parse(data) as WorkflowRunState<\n unknown,\n Record<string, unknown>\n >;\n if (run.workflowId === workflowId && run.status === status) {\n runs.push(run);\n }\n } catch (_e) {\n // Ignore corrupted files\n }\n }\n }\n return runs;\n } catch (_e) {\n return [];\n }\n }\n\n async deleteRun(runId: string): Promise<void> {\n try {\n await fs.unlink(this.getFilePath(runId));\n } catch (_e) {\n // Ignore if file doesn't exist\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/core/engine.ts","../src/storage/file-storage.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { FileStorage } from '../storage/file-storage';\nimport type {\n StorageProvider,\n WorkflowConfig,\n WorkflowResult,\n WorkflowRunState,\n WorkflowStatus,\n} from '../types';\n\ntype Mutable<T> = {\n -readonly [P in keyof T]: T[P];\n};\n\nexport class Workflow<\n TInput = unknown,\n TState extends Record<string, unknown> = Record<string, unknown>,\n> {\n private readonly storage: StorageProvider;\n private readonly workflowId: string;\n private readonly config: WorkflowConfig<TInput, TState>;\n\n constructor(config: WorkflowConfig<TInput, TState>) {\n this.config = config;\n this.storage = config.storage || new FileStorage();\n this.workflowId = config.id;\n\n if (config.autoResume) {\n this.resumeAllIncomplete().catch((err) => {\n console.error(\n `[ResumableWorkflow: ${this.workflowId}] Auto-resume failed:`,\n err\n );\n });\n }\n }\n\n private async executeStep(\n currentState: Mutable<WorkflowRunState<TInput, TState>>,\n stepIndex: number\n ): Promise<void> {\n const step = this.config.steps[stepIndex];\n if (!step) {\n return;\n }\n\n try {\n const result = await step.run({\n input: currentState.input,\n state: currentState.state,\n });\n\n if (\n result !== null &&\n typeof result === 'object' &&\n !Array.isArray(result)\n ) {\n currentState.state = {\n ...currentState.state,\n ...(result as Partial<TState>),\n };\n } else if (result !== undefined) {\n (currentState.state as Record<string, unknown>)[\n step.name || `step_${stepIndex}`\n ] = result;\n }\n\n currentState.currentStepIndex = stepIndex + 1;\n\n if (currentState.currentStepIndex === this.config.steps.length) {\n currentState.status = 'completed';\n }\n\n await this.storage.saveRun(currentState);\n } catch (error) {\n currentState.status = 'failed';\n currentState.error =\n error instanceof Error ? error.message : String(error);\n await this.storage.saveRun(currentState);\n throw error;\n }\n }\n\n private async run(\n input: TInput,\n existingRun?: WorkflowRunState<TInput, TState>\n ): Promise<WorkflowResult<TState>> {\n const runId = existingRun?.runId || randomUUID();\n const currentState: Mutable<WorkflowRunState<TInput, TState>> =\n (existingRun as Mutable<WorkflowRunState<TInput, TState>>) || {\n workflowId: this.workflowId,\n runId,\n status: 'pending' as WorkflowStatus,\n currentStepIndex: 0,\n input,\n state: {} as TState,\n };\n\n if (!existingRun) {\n await this.storage.saveRun(currentState);\n }\n\n try {\n for (\n let i = currentState.currentStepIndex;\n i < this.config.steps.length;\n i++\n ) {\n await this.executeStep(currentState, i);\n }\n } catch (_error) {\n return {\n success: false,\n error: currentState.error || 'Unknown error',\n runId: currentState.runId,\n stepName:\n this.config.steps[currentState.currentStepIndex]?.name ||\n `step_${currentState.currentStepIndex}`,\n };\n }\n\n // Auto-cleanup if enabled and successful\n if (this.config.autoCleanup) {\n await this.storage.deleteRun(currentState.runId);\n }\n\n return {\n success: true,\n result: currentState.state,\n runId: currentState.runId,\n };\n }\n\n start(input: TInput): Promise<WorkflowResult<TState>> {\n return this.run(input);\n }\n\n async resume(runId: string): Promise<WorkflowResult<TState>> {\n const runState = await this.storage.getRun(runId);\n if (!runState) {\n return {\n success: false,\n error: `Run ${runId} not found`,\n runId,\n stepName: 'unknown',\n };\n }\n if (runState.status === 'completed') {\n return {\n success: true,\n result: runState.state as TState,\n runId: runState.runId,\n };\n }\n return this.run(\n runState.input as TInput,\n runState as WorkflowRunState<TInput, TState>\n );\n }\n\n listIncomplete(): Promise<\n WorkflowRunState<unknown, Record<string, unknown>>[]\n > {\n return this.storage.listIncompleteRuns(this.workflowId);\n }\n\n async resumeAllIncomplete(): Promise<WorkflowResult<TState>[]> {\n const incomplete = await this.storage.listIncompleteRuns(this.workflowId);\n return Promise.all(\n incomplete.map((runState) =>\n this.run(\n runState.input as TInput,\n runState as WorkflowRunState<TInput, TState>\n )\n )\n );\n }\n\n /**\n * Manually clears all completed workflow runs for this workflow ID from storage.\n */\n async clearCompleted(): Promise<void> {\n const completed = await this.storage.listCompletedRuns(this.workflowId);\n await Promise.all(completed.map((run) => this.storage.deleteRun(run.runId)));\n }\n}\n\n/**\n * Factory function for backward compatibility and ease of use\n */\nexport function createWorkflow<\n TInput = unknown,\n TState extends Record<string, unknown> = Record<string, unknown>,\n>(config: WorkflowConfig<TInput, TState>) {\n return new Workflow(config);\n}","import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport type { StorageProvider, WorkflowRunState } from '../types';\n\nexport class FileStorage implements StorageProvider {\n private readonly baseDir: string;\n\n constructor(baseDir = '.resumable-workflow') {\n this.baseDir = path.resolve(baseDir);\n }\n\n private async ensureDir(): Promise<void> {\n await fs.mkdir(this.baseDir, { recursive: true });\n }\n\n private validateRunId(runId: string): void {\n if (!runId || typeof runId !== 'string') {\n throw new Error(`Invalid runId: ${runId}`);\n }\n \n // Check for path traversal attempts\n if (runId.includes('../') || runId.includes('..\\\\') || runId.startsWith('..')) {\n throw new Error(`Invalid runId: Path traversal detected in ${runId}`);\n }\n \n // Additional validation: only allow alphanumeric, hyphens, and underscores\n if (!/^[a-zA-Z0-9_-]+$/.test(runId)) {\n throw new Error(`Invalid runId: Only alphanumeric characters, hyphens, and underscores are allowed in ${runId}`);\n }\n }\n\n private getFilePath(runId: string): string {\n this.validateRunId(runId);\n const filePath = path.join(this.baseDir, `${runId}.json`);\n \n // Additional security check: ensure the resolved path is within the base directory\n const resolvedPath = path.resolve(filePath);\n const resolvedBaseDir = path.resolve(this.baseDir);\n \n if (!resolvedPath.startsWith(resolvedBaseDir)) {\n throw new Error(`Invalid runId: Path traversal detected`);\n }\n \n return resolvedPath;\n }\n\n async saveRun(\n state: WorkflowRunState<unknown, Record<string, unknown>>\n ): Promise<void> {\n await this.ensureDir();\n await fs.writeFile(\n this.getFilePath(state.runId),\n JSON.stringify(state, null, 2)\n );\n }\n\n async getRun(\n runId: string\n ): Promise<WorkflowRunState<unknown, Record<string, unknown>> | null> {\n // Validate runId first before attempting file operations\n this.validateRunId(runId);\n \n try {\n const data = await fs.readFile(this.getFilePath(runId), 'utf-8');\n return JSON.parse(data) as WorkflowRunState<\n unknown,\n Record<string, unknown>\n >;\n } catch (e) {\n // Only return null for file system errors, not validation errors\n // If it's a validation error, it should have been caught by validateRunId\n if ((e as Error).message.includes('Invalid runId') || (e as Error).message.includes('Path traversal')) {\n throw e; // Re-throw validation errors\n }\n // Return null if file doesn't exist or is invalid\n return null;\n }\n }\n\n listIncompleteRuns(\n workflowId: string\n ): Promise<WorkflowRunState<unknown, Record<string, unknown>>[]> {\n return this.listRuns(workflowId, 'pending');\n }\n\n listCompletedRuns(\n workflowId: string\n ): Promise<WorkflowRunState<unknown, Record<string, unknown>>[]> {\n return this.listRuns(workflowId, 'completed');\n }\n\n private async listRuns(\n workflowId: string,\n status: 'pending' | 'completed'\n ): Promise<WorkflowRunState<unknown, Record<string, unknown>>[]> {\n try {\n await this.ensureDir();\n const files = await fs.readdir(this.baseDir);\n const runs: WorkflowRunState<unknown, Record<string, unknown>>[] = [];\n\n for (const file of files) {\n if (file.endsWith('.json')) {\n try {\n const data = await fs.readFile(\n path.join(this.baseDir, file),\n 'utf-8'\n );\n const run = JSON.parse(data) as WorkflowRunState<\n unknown,\n Record<string, unknown>\n >;\n if (run.workflowId === workflowId && run.status === status) {\n runs.push(run);\n }\n } catch (_e) {\n // Ignore corrupted files\n }\n }\n }\n return runs;\n } catch (_e) {\n return [];\n }\n }\n\n async deleteRun(runId: string): Promise<void> {\n // Validate runId first before attempting file operations\n this.validateRunId(runId);\n \n try {\n await fs.unlink(this.getFilePath(runId));\n } catch (e) {\n // Only ignore file system errors, not validation errors\n if ((e as Error).message.includes('Invalid runId') || (e as Error).message.includes('Path traversal')) {\n throw e; // Re-throw validation errors\n }\n // Ignore if file doesn't exist\n }\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "resumable-workflow",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "A lightweight, persistent workflow engine for Node.js. Ensure multi-step processes survive crashes and resume automatically.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|