@workflow/world-local 4.1.1 → 4.1.3
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/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/fs.d.ts +57 -2
- package/dist/fs.d.ts.map +1 -1
- package/dist/fs.js +123 -9
- package/dist/fs.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -3
- package/dist/index.js.map +1 -1
- package/dist/queue.d.ts.map +1 -1
- package/dist/queue.js +31 -1
- package/dist/queue.js.map +1 -1
- package/dist/storage/events-storage.d.ts.map +1 -1
- package/dist/storage/events-storage.js +33 -6
- package/dist/storage/events-storage.js.map +1 -1
- package/dist/storage/hooks-storage.d.ts.map +1 -1
- package/dist/storage/hooks-storage.js +2 -1
- package/dist/storage/hooks-storage.js.map +1 -1
- package/dist/storage/index.d.ts +10 -1
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +8 -10
- package/dist/storage/index.js.map +1 -1
- package/dist/storage/legacy.d.ts.map +1 -1
- package/dist/storage/legacy.js +8 -4
- package/dist/storage/legacy.js.map +1 -1
- package/dist/storage/runs-storage.d.ts +25 -3
- package/dist/storage/runs-storage.d.ts.map +1 -1
- package/dist/storage/runs-storage.js +5 -2
- package/dist/storage/runs-storage.js.map +1 -1
- package/dist/storage/steps-storage.d.ts.map +1 -1
- package/dist/storage/steps-storage.js +6 -1
- package/dist/storage/steps-storage.js.map +1 -1
- package/dist/streamer.d.ts.map +1 -1
- package/dist/streamer.js +44 -31
- package/dist/streamer.js.map +1 -1
- package/package.json +5 -5
|
@@ -2,7 +2,7 @@ import path from 'node:path';
|
|
|
2
2
|
import { HookNotFoundError } from '@workflow/errors';
|
|
3
3
|
import { HookSchema } from '@workflow/world';
|
|
4
4
|
import { DEFAULT_RESOLVE_DATA_OPTION } from '../config.js';
|
|
5
|
-
import { deleteJSON, listJSONFiles, paginatedFileSystemQuery, readJSON, readJSONWithFallback, } from '../fs.js';
|
|
5
|
+
import { assertSafeEntityId, deleteJSON, listJSONFiles, paginatedFileSystemQuery, readJSON, readJSONWithFallback, } from '../fs.js';
|
|
6
6
|
import { filterHookData } from './filters.js';
|
|
7
7
|
import { hashToken } from './helpers.js';
|
|
8
8
|
/**
|
|
@@ -24,6 +24,7 @@ export function createHooksStorage(basedir, tag) {
|
|
|
24
24
|
return null;
|
|
25
25
|
}
|
|
26
26
|
async function get(hookId, params) {
|
|
27
|
+
assertSafeEntityId('hookId', hookId);
|
|
27
28
|
const hook = await readJSONWithFallback(basedir, 'hooks', hookId, HookSchema, tag);
|
|
28
29
|
if (!hook) {
|
|
29
30
|
throw new HookNotFoundError(hookId);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hooks-storage.js","sourceRoot":"","sources":["../../src/storage/hooks-storage.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAQrD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,2BAA2B,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,EACL,UAAU,EACV,aAAa,EACb,wBAAwB,EACxB,QAAQ,EACR,oBAAoB,GACrB,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAChC,OAAe,EACf,GAAY;IAEZ,sEAAsE;IACtE,KAAK,UAAU,eAAe,CAAC,KAAa;QAC1C,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC7C,MAAM,KAAK,GAAG,MAAM,aAAa,CAAC,QAAQ,CAAC,CAAC;QAE5C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,IAAI,OAAO,CAAC,CAAC;YACrD,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;YAClD,IAAI,IAAI,IAAI,IAAI,CAAC,KAAK,KAAK,KAAK,EAAE,CAAC;gBACjC,OAAO,EAAE,GAAG,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,IAAI,IAAI,EAAE,CAAC;YACxD,CAAC;QACH,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK,UAAU,GAAG,CAAC,MAAc,EAAE,MAAsB;QACvD,MAAM,IAAI,GAAG,MAAM,oBAAoB,CACrC,OAAO,EACP,OAAO,EACP,MAAM,EACN,UAAU,EACV,GAAG,CACJ,CAAC;QACF,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,IAAI,iBAAiB,CAAC,MAAM,CAAC,CAAC;QACtC,CAAC;QACD,MAAM,WAAW,GAAG,MAAM,EAAE,WAAW,IAAI,2BAA2B,CAAC;QACvE,OAAO,cAAc,CACnB,EAAE,GAAG,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,IAAI,IAAI,EAAE,EAC9C,WAAW,CACZ,CAAC;IACJ,CAAC;IAED,KAAK,UAAU,UAAU,CAAC,KAAa;QACrC,MAAM,IAAI,GAAG,MAAM,eAAe,CAAC,KAAK,CAAC,CAAC;QAC1C,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,IAAI,iBAAiB,CAAC,KAAK,CAAC,CAAC;QACrC,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK,UAAU,IAAI,CACjB,MAAuB;QAEvB,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC7C,MAAM,WAAW,GAAG,MAAM,CAAC,WAAW,IAAI,2BAA2B,CAAC;QAEtE,MAAM,MAAM,GAAG,MAAM,wBAAwB,CAAC;YAC5C,SAAS,EAAE,QAAQ;YACnB,MAAM,EAAE,UAAU;YAClB,SAAS,EAAE,MAAM,CAAC,UAAU,EAAE,SAAS,IAAI,KAAK;YAChD,KAAK,EAAE,MAAM,CAAC,UAAU,EAAE,KAAK;YAC/B,MAAM,EAAE,MAAM,CAAC,UAAU,EAAE,MAAM;YACjC,UAAU,EAAE,SAAS,EAAE,2DAA2D;YAClF,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;gBACf,8BAA8B;gBAC9B,IAAI,MAAM,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,KAAK,MAAM,CAAC,KAAK,EAAE,CAAC;oBAChD,OAAO,KAAK,CAAC;gBACf,CAAC;gBACD,OAAO,IAAI,CAAC;YACd,CAAC;YACD,YAAY,EAAE,GAAG,EAAE;gBACjB,oEAAoE;gBACpE,kEAAkE;gBAClE,kEAAkE;gBAClE,OAAO,IAAI,CAAC;YACd,CAAC;YACD,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM;SAC7B,CAAC,CAAC;QAEH,sCAAsC;QACtC,OAAO;YACL,GAAG,MAAM;YACT,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,cAAc,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;SACnE,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;AACnC,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,OAAe,EACf,KAAa;IAEb,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC7C,MAAM,KAAK,GAAG,MAAM,aAAa,CAAC,QAAQ,CAAC,CAAC;IAE5C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,IAAI,OAAO,CAAC,CAAC;QACrD,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;QAClD,IAAI,IAAI,IAAI,IAAI,CAAC,KAAK,KAAK,KAAK,EAAE,CAAC;YACjC,wDAAwD;YACxD,MAAM,cAAc,GAAG,IAAI,CAAC,IAAI,CAC9B,QAAQ,EACR,QAAQ,EACR,GAAG,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAChC,CAAC;YACF,MAAM,UAAU,CAAC,cAAc,CAAC,CAAC;YACjC,MAAM,UAAU,CAAC,QAAQ,CAAC,CAAC;QAC7B,CAAC;IACH,CAAC;AACH,CAAC","sourcesContent":["import path from 'node:path';\nimport { HookNotFoundError } from '@workflow/errors';\nimport type {\n GetHookParams,\n Hook,\n ListHooksParams,\n PaginatedResponse,\n Storage,\n} from '@workflow/world';\nimport { HookSchema } from '@workflow/world';\nimport { DEFAULT_RESOLVE_DATA_OPTION } from '../config.js';\nimport {\n deleteJSON,\n listJSONFiles,\n paginatedFileSystemQuery,\n readJSON,\n readJSONWithFallback,\n} from '../fs.js';\nimport { filterHookData } from './filters.js';\nimport { hashToken } from './helpers.js';\n\n/**\n * Creates a hooks storage implementation using the filesystem.\n * Implements the Storage['hooks'] interface with hook CRUD operations.\n */\nexport function createHooksStorage(\n basedir: string,\n tag?: string\n): Storage['hooks'] {\n // Helper function to find a hook by token (shared between getByToken)\n async function findHookByToken(token: string): Promise<Hook | null> {\n const hooksDir = path.join(basedir, 'hooks');\n const files = await listJSONFiles(hooksDir);\n\n for (const file of files) {\n const hookPath = path.join(hooksDir, `${file}.json`);\n const hook = await readJSON(hookPath, HookSchema);\n if (hook && hook.token === token) {\n return { ...hook, isWebhook: hook.isWebhook ?? true };\n }\n }\n\n return null;\n }\n\n async function get(hookId: string, params?: GetHookParams): Promise<Hook> {\n const hook = await readJSONWithFallback(\n basedir,\n 'hooks',\n hookId,\n HookSchema,\n tag\n );\n if (!hook) {\n throw new HookNotFoundError(hookId);\n }\n const resolveData = params?.resolveData || DEFAULT_RESOLVE_DATA_OPTION;\n return filterHookData(\n { ...hook, isWebhook: hook.isWebhook ?? true },\n resolveData\n );\n }\n\n async function getByToken(token: string): Promise<Hook> {\n const hook = await findHookByToken(token);\n if (!hook) {\n throw new HookNotFoundError(token);\n }\n return hook;\n }\n\n async function list(\n params: ListHooksParams\n ): Promise<PaginatedResponse<Hook>> {\n const hooksDir = path.join(basedir, 'hooks');\n const resolveData = params.resolveData || DEFAULT_RESOLVE_DATA_OPTION;\n\n const result = await paginatedFileSystemQuery({\n directory: hooksDir,\n schema: HookSchema,\n sortOrder: params.pagination?.sortOrder ?? 'asc',\n limit: params.pagination?.limit,\n cursor: params.pagination?.cursor,\n filePrefix: undefined, // Hooks don't have ULIDs, so we can't optimize by filename\n filter: (hook) => {\n // Filter by runId if provided\n if (params.runId && hook.runId !== params.runId) {\n return false;\n }\n return true;\n },\n getCreatedAt: () => {\n // Hook files don't have ULID timestamps in filename, so return null\n // to skip the filename-based optimization and defer to JSON-based\n // cursor filtering which uses the actual createdAt from the file.\n return null;\n },\n getId: (hook) => hook.hookId,\n });\n\n // Transform the data after pagination\n return {\n ...result,\n data: result.data.map((hook) => filterHookData(hook, resolveData)),\n };\n }\n\n return { get, getByToken, list };\n}\n\n/**\n * Helper function to delete all hooks associated with a workflow run.\n * Called when a run reaches a terminal state.\n */\nexport async function deleteAllHooksForRun(\n basedir: string,\n runId: string\n): Promise<void> {\n const hooksDir = path.join(basedir, 'hooks');\n const files = await listJSONFiles(hooksDir);\n\n for (const file of files) {\n const hookPath = path.join(hooksDir, `${file}.json`);\n const hook = await readJSON(hookPath, HookSchema);\n if (hook && hook.runId === runId) {\n // Delete the token constraint file to free up the token\n const constraintPath = path.join(\n hooksDir,\n 'tokens',\n `${hashToken(hook.token)}.json`\n );\n await deleteJSON(constraintPath);\n await deleteJSON(hookPath);\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"hooks-storage.js","sourceRoot":"","sources":["../../src/storage/hooks-storage.ts"],"names":[],"mappings":"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","sourcesContent":["import path from 'node:path';\nimport { HookNotFoundError } from '@workflow/errors';\nimport type {\n GetHookParams,\n Hook,\n ListHooksParams,\n PaginatedResponse,\n Storage,\n} from '@workflow/world';\nimport { HookSchema } from '@workflow/world';\nimport { DEFAULT_RESOLVE_DATA_OPTION } from '../config.js';\nimport {\n assertSafeEntityId,\n deleteJSON,\n listJSONFiles,\n paginatedFileSystemQuery,\n readJSON,\n readJSONWithFallback,\n} from '../fs.js';\nimport { filterHookData } from './filters.js';\nimport { hashToken } from './helpers.js';\n\n/**\n * Creates a hooks storage implementation using the filesystem.\n * Implements the Storage['hooks'] interface with hook CRUD operations.\n */\nexport function createHooksStorage(\n basedir: string,\n tag?: string\n): Storage['hooks'] {\n // Helper function to find a hook by token (shared between getByToken)\n async function findHookByToken(token: string): Promise<Hook | null> {\n const hooksDir = path.join(basedir, 'hooks');\n const files = await listJSONFiles(hooksDir);\n\n for (const file of files) {\n const hookPath = path.join(hooksDir, `${file}.json`);\n const hook = await readJSON(hookPath, HookSchema);\n if (hook && hook.token === token) {\n return { ...hook, isWebhook: hook.isWebhook ?? true };\n }\n }\n\n return null;\n }\n\n async function get(hookId: string, params?: GetHookParams): Promise<Hook> {\n assertSafeEntityId('hookId', hookId);\n const hook = await readJSONWithFallback(\n basedir,\n 'hooks',\n hookId,\n HookSchema,\n tag\n );\n if (!hook) {\n throw new HookNotFoundError(hookId);\n }\n const resolveData = params?.resolveData || DEFAULT_RESOLVE_DATA_OPTION;\n return filterHookData(\n { ...hook, isWebhook: hook.isWebhook ?? true },\n resolveData\n );\n }\n\n async function getByToken(token: string): Promise<Hook> {\n const hook = await findHookByToken(token);\n if (!hook) {\n throw new HookNotFoundError(token);\n }\n return hook;\n }\n\n async function list(\n params: ListHooksParams\n ): Promise<PaginatedResponse<Hook>> {\n const hooksDir = path.join(basedir, 'hooks');\n const resolveData = params.resolveData || DEFAULT_RESOLVE_DATA_OPTION;\n\n const result = await paginatedFileSystemQuery({\n directory: hooksDir,\n schema: HookSchema,\n sortOrder: params.pagination?.sortOrder ?? 'asc',\n limit: params.pagination?.limit,\n cursor: params.pagination?.cursor,\n filePrefix: undefined, // Hooks don't have ULIDs, so we can't optimize by filename\n filter: (hook) => {\n // Filter by runId if provided\n if (params.runId && hook.runId !== params.runId) {\n return false;\n }\n return true;\n },\n getCreatedAt: () => {\n // Hook files don't have ULID timestamps in filename, so return null\n // to skip the filename-based optimization and defer to JSON-based\n // cursor filtering which uses the actual createdAt from the file.\n return null;\n },\n getId: (hook) => hook.hookId,\n });\n\n // Transform the data after pagination\n return {\n ...result,\n data: result.data.map((hook) => filterHookData(hook, resolveData)),\n };\n }\n\n return { get, getByToken, list };\n}\n\n/**\n * Helper function to delete all hooks associated with a workflow run.\n * Called when a run reaches a terminal state.\n */\nexport async function deleteAllHooksForRun(\n basedir: string,\n runId: string\n): Promise<void> {\n const hooksDir = path.join(basedir, 'hooks');\n const files = await listJSONFiles(hooksDir);\n\n for (const file of files) {\n const hookPath = path.join(hooksDir, `${file}.json`);\n const hook = await readJSON(hookPath, HookSchema);\n if (hook && hook.runId === runId) {\n // Delete the token constraint file to free up the token\n const constraintPath = path.join(\n hooksDir,\n 'tokens',\n `${hashToken(hook.token)}.json`\n );\n await deleteJSON(constraintPath);\n await deleteJSON(hookPath);\n }\n }\n}\n"]}
|
package/dist/storage/index.d.ts
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
import type { Storage } from '@workflow/world';
|
|
2
|
+
import { type LocalRunsStorage } from './runs-storage.js';
|
|
3
|
+
/**
|
|
4
|
+
* Storage shape used inside world-local: identical to `Storage`, but `runs`
|
|
5
|
+
* exposes the internal `fileIdFilter` option on `list()`. Structurally
|
|
6
|
+
* assignable to `Storage` at public boundaries (e.g., `reenqueueActiveRuns`).
|
|
7
|
+
*/
|
|
8
|
+
export type LocalStorage = Omit<Storage, 'runs'> & {
|
|
9
|
+
runs: LocalRunsStorage;
|
|
10
|
+
};
|
|
2
11
|
/**
|
|
3
12
|
* Creates a complete storage implementation using the filesystem.
|
|
4
13
|
* This is the main entry point that composes all storage implementations.
|
|
@@ -8,5 +17,5 @@ import type { Storage } from '@workflow/world';
|
|
|
8
17
|
* @param basedir - The base directory for storing workflow data
|
|
9
18
|
* @returns A complete Storage implementation with tracing
|
|
10
19
|
*/
|
|
11
|
-
export declare function createStorage(basedir: string, tag?: string):
|
|
20
|
+
export declare function createStorage(basedir: string, tag?: string): LocalStorage;
|
|
12
21
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/storage/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/storage/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAI/C,OAAO,EAAqB,KAAK,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAG7E;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,GAAG;IAAE,IAAI,EAAE,gBAAgB,CAAA;CAAE,CAAC;AAE9E;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,YAAY,CAezE"}
|
package/dist/storage/index.js
CHANGED
|
@@ -14,19 +14,17 @@ import { createStepsStorage } from './steps-storage.js';
|
|
|
14
14
|
*/
|
|
15
15
|
export function createStorage(basedir, tag) {
|
|
16
16
|
// Create raw storage implementations
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
hooks: createHooksStorage(basedir, tag),
|
|
22
|
-
};
|
|
17
|
+
const runs = createRunsStorage(basedir, tag);
|
|
18
|
+
const steps = createStepsStorage(basedir, tag);
|
|
19
|
+
const events = createEventsStorage(basedir, tag);
|
|
20
|
+
const hooks = createHooksStorage(basedir, tag);
|
|
23
21
|
// Instrument all storage methods with tracing
|
|
24
22
|
// NOTE: Span names are lowercase per OTEL semantic conventions
|
|
25
23
|
return {
|
|
26
|
-
runs: instrumentObject('world.runs',
|
|
27
|
-
steps: instrumentObject('world.steps',
|
|
28
|
-
events: instrumentObject('world.events',
|
|
29
|
-
hooks: instrumentObject('world.hooks',
|
|
24
|
+
runs: instrumentObject('world.runs', runs),
|
|
25
|
+
steps: instrumentObject('world.steps', steps),
|
|
26
|
+
events: instrumentObject('world.events', events),
|
|
27
|
+
hooks: instrumentObject('world.hooks', hooks),
|
|
30
28
|
};
|
|
31
29
|
}
|
|
32
30
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/storage/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAE,iBAAiB,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/storage/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAyB,MAAM,mBAAmB,CAAC;AAC7E,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AASxD;;;;;;;;GAQG;AACH,MAAM,UAAU,aAAa,CAAC,OAAe,EAAE,GAAY;IACzD,qCAAqC;IACrC,MAAM,IAAI,GAAG,iBAAiB,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IAC7C,MAAM,KAAK,GAAG,kBAAkB,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IAC/C,MAAM,MAAM,GAAG,mBAAmB,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IACjD,MAAM,KAAK,GAAG,kBAAkB,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IAE/C,8CAA8C;IAC9C,+DAA+D;IAC/D,OAAO;QACL,IAAI,EAAE,gBAAgB,CAAC,YAAY,EAAE,IAAI,CAAC;QAC1C,KAAK,EAAE,gBAAgB,CAAC,aAAa,EAAE,KAAK,CAAC;QAC7C,MAAM,EAAE,gBAAgB,CAAC,cAAc,EAAE,MAAM,CAAC;QAChD,KAAK,EAAE,gBAAgB,CAAC,aAAa,EAAE,KAAK,CAAC;KAC9C,CAAC;AACJ,CAAC","sourcesContent":["import type { Storage } from '@workflow/world';\nimport { instrumentObject } from '../instrumentObject.js';\nimport { createEventsStorage } from './events-storage.js';\nimport { createHooksStorage } from './hooks-storage.js';\nimport { createRunsStorage, type LocalRunsStorage } from './runs-storage.js';\nimport { createStepsStorage } from './steps-storage.js';\n\n/**\n * Storage shape used inside world-local: identical to `Storage`, but `runs`\n * exposes the internal `fileIdFilter` option on `list()`. Structurally\n * assignable to `Storage` at public boundaries (e.g., `reenqueueActiveRuns`).\n */\nexport type LocalStorage = Omit<Storage, 'runs'> & { runs: LocalRunsStorage };\n\n/**\n * Creates a complete storage implementation using the filesystem.\n * This is the main entry point that composes all storage implementations.\n *\n * All storage methods are instrumented with tracing spans for observability.\n *\n * @param basedir - The base directory for storing workflow data\n * @returns A complete Storage implementation with tracing\n */\nexport function createStorage(basedir: string, tag?: string): LocalStorage {\n // Create raw storage implementations\n const runs = createRunsStorage(basedir, tag);\n const steps = createStepsStorage(basedir, tag);\n const events = createEventsStorage(basedir, tag);\n const hooks = createHooksStorage(basedir, tag);\n\n // Instrument all storage methods with tracing\n // NOTE: Span names are lowercase per OTEL semantic conventions\n return {\n runs: instrumentObject('world.runs', runs),\n steps: instrumentObject('world.steps', steps),\n events: instrumentObject('world.events', events),\n hooks: instrumentObject('world.hooks', hooks),\n };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"legacy.d.ts","sourceRoot":"","sources":["../../src/storage/legacy.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"legacy.d.ts","sourceRoot":"","sources":["../../src/storage/legacy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAS,WAAW,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAQvE;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,GAAG,EACT,UAAU,EAAE,WAAW,EACvB,MAAM,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;CAAE,GACxC,OAAO,CAAC,WAAW,CAAC,CAuEtB"}
|
package/dist/storage/legacy.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
1
|
import { SPEC_VERSION_CURRENT } from '@workflow/world';
|
|
3
2
|
import { DEFAULT_RESOLVE_DATA_OPTION } from '../config.js';
|
|
4
|
-
import { writeJSON } from '../fs.js';
|
|
3
|
+
import { assertSafeEntityId, resolveWithinBase, writeJSON } from '../fs.js';
|
|
5
4
|
import { filterRunData, stripEventDataRefs } from './filters.js';
|
|
6
5
|
import { monotonicUlid } from './helpers.js';
|
|
7
6
|
import { deleteAllHooksForRun } from './hooks-storage.js';
|
|
@@ -14,6 +13,11 @@ import { deleteAllHooksForRun } from './hooks-storage.js';
|
|
|
14
13
|
* - Other events: Throw error (not supported for legacy runs)
|
|
15
14
|
*/
|
|
16
15
|
export async function handleLegacyEvent(basedir, runId, data, currentRun, params) {
|
|
16
|
+
// Defense in depth: events.create already validates runId before routing
|
|
17
|
+
// here, but handleLegacyEvent is exported and its signature doesn't
|
|
18
|
+
// document that invariant. Validating locally keeps the guarantee in this
|
|
19
|
+
// file.
|
|
20
|
+
assertSafeEntityId('runId', runId);
|
|
17
21
|
const resolveData = params?.resolveData ?? DEFAULT_RESOLVE_DATA_OPTION;
|
|
18
22
|
switch (data.eventType) {
|
|
19
23
|
case 'run_cancelled': {
|
|
@@ -35,7 +39,7 @@ export async function handleLegacyEvent(basedir, runId, data, currentRun, params
|
|
|
35
39
|
completedAt: now,
|
|
36
40
|
updatedAt: now,
|
|
37
41
|
};
|
|
38
|
-
const runPath =
|
|
42
|
+
const runPath = resolveWithinBase(basedir, 'runs', `${runId}.json`);
|
|
39
43
|
await writeJSON(runPath, run, { overwrite: true });
|
|
40
44
|
await deleteAllHooksForRun(basedir, runId);
|
|
41
45
|
// Return without event (legacy behavior skips event storage)
|
|
@@ -60,7 +64,7 @@ export async function handleLegacyEvent(basedir, runId, data, currentRun, params
|
|
|
60
64
|
specVersion: SPEC_VERSION_CURRENT,
|
|
61
65
|
};
|
|
62
66
|
const compositeKey = `${runId}-${eventId}`;
|
|
63
|
-
const eventPath =
|
|
67
|
+
const eventPath = resolveWithinBase(basedir, 'events', `${compositeKey}.json`);
|
|
64
68
|
await writeJSON(eventPath, event);
|
|
65
69
|
return { event: stripEventDataRefs(event, resolveData) };
|
|
66
70
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"legacy.js","sourceRoot":"","sources":["../../src/storage/legacy.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"legacy.js","sourceRoot":"","sources":["../../src/storage/legacy.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Event, EventResult, WorkflowRun } from '@workflow/world';\nimport { SPEC_VERSION_CURRENT } from '@workflow/world';\nimport { DEFAULT_RESOLVE_DATA_OPTION } from '../config.js';\nimport { assertSafeEntityId, resolveWithinBase, writeJSON } from '../fs.js';\nimport { filterRunData, stripEventDataRefs } from './filters.js';\nimport { monotonicUlid } from './helpers.js';\nimport { deleteAllHooksForRun } from './hooks-storage.js';\n\n/**\n * Handle events for legacy runs (pre-event-sourcing, specVersion < 2).\n * Legacy runs use different behavior:\n * - run_cancelled: Skip event storage, directly update run\n * - wait_completed: Store event only (no entity mutation)\n * - hook_received: Store event only (hooks exist via old system, no entity mutation)\n * - Other events: Throw error (not supported for legacy runs)\n */\nexport async function handleLegacyEvent(\n basedir: string,\n runId: string,\n data: any,\n currentRun: WorkflowRun,\n params?: { resolveData?: 'none' | 'all' }\n): Promise<EventResult> {\n // Defense in depth: events.create already validates runId before routing\n // here, but handleLegacyEvent is exported and its signature doesn't\n // document that invariant. Validating locally keeps the guarantee in this\n // file.\n assertSafeEntityId('runId', runId);\n\n const resolveData = params?.resolveData ?? DEFAULT_RESOLVE_DATA_OPTION;\n\n switch (data.eventType) {\n case 'run_cancelled': {\n // Legacy: Skip event storage, directly update run to cancelled\n const now = new Date();\n const run: WorkflowRun = {\n runId: currentRun.runId,\n deploymentId: currentRun.deploymentId,\n workflowName: currentRun.workflowName,\n specVersion: currentRun.specVersion,\n executionContext: currentRun.executionContext,\n input: currentRun.input,\n createdAt: currentRun.createdAt,\n expiredAt: currentRun.expiredAt,\n startedAt: currentRun.startedAt,\n status: 'cancelled',\n output: undefined,\n error: undefined,\n completedAt: now,\n updatedAt: now,\n };\n const runPath = resolveWithinBase(basedir, 'runs', `${runId}.json`);\n await writeJSON(runPath, run, { overwrite: true });\n await deleteAllHooksForRun(basedir, runId);\n // Return without event (legacy behavior skips event storage)\n // Type assertion: EventResult expects WorkflowRun, filterRunData may return WorkflowRunWithoutData\n return {\n event: undefined,\n run: filterRunData(run, resolveData) as WorkflowRun,\n };\n }\n\n case 'wait_completed':\n case 'hook_received': {\n // Legacy: Store event only (no entity mutation)\n // - wait_completed: for replay purposes\n // - hook_received: hooks exist via old system, just record the event\n const eventId = `evnt_${monotonicUlid()}`;\n const now = new Date();\n const event: Event = {\n ...data,\n runId,\n eventId,\n createdAt: now,\n specVersion: SPEC_VERSION_CURRENT,\n };\n const compositeKey = `${runId}-${eventId}`;\n const eventPath = resolveWithinBase(\n basedir,\n 'events',\n `${compositeKey}.json`\n );\n await writeJSON(eventPath, event);\n return { event: stripEventDataRefs(event, resolveData) };\n }\n\n default:\n throw new Error(\n `Event type '${data.eventType}' not supported for legacy runs ` +\n `(specVersion: ${currentRun.specVersion || 'undefined'}). ` +\n `Please upgrade 'workflow' package.`\n );\n }\n}\n"]}
|
|
@@ -1,7 +1,29 @@
|
|
|
1
|
-
import type { Storage } from '@workflow/world';
|
|
1
|
+
import type { ListWorkflowRunsParams, PaginatedResponse, Storage, WorkflowRun, WorkflowRunWithoutData } from '@workflow/world';
|
|
2
|
+
/**
|
|
3
|
+
* Internal extension of `ListWorkflowRunsParams` that adds a `fileIdFilter`
|
|
4
|
+
* for scoping queries by raw filename (e.g., by tag suffix). Kept out of the
|
|
5
|
+
* public `Storage['runs']['list']` surface — consumers of `@workflow/world`
|
|
6
|
+
* must not see this option.
|
|
7
|
+
*/
|
|
8
|
+
export interface LocalListWorkflowRunsParams extends ListWorkflowRunsParams {
|
|
9
|
+
fileIdFilter?: (fileId: string) => boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface LocalRunsStorage {
|
|
12
|
+
get: Storage['runs']['get'];
|
|
13
|
+
list: {
|
|
14
|
+
(params: LocalListWorkflowRunsParams & {
|
|
15
|
+
resolveData: 'none';
|
|
16
|
+
}): Promise<PaginatedResponse<WorkflowRunWithoutData>>;
|
|
17
|
+
(params?: LocalListWorkflowRunsParams & {
|
|
18
|
+
resolveData?: 'all';
|
|
19
|
+
}): Promise<PaginatedResponse<WorkflowRun>>;
|
|
20
|
+
(params?: LocalListWorkflowRunsParams): Promise<PaginatedResponse<WorkflowRun | WorkflowRunWithoutData>>;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
2
23
|
/**
|
|
3
24
|
* Creates the runs storage implementation using the filesystem.
|
|
4
|
-
* Implements the Storage['runs'] interface with get and list operations
|
|
25
|
+
* Implements the Storage['runs'] interface with get and list operations,
|
|
26
|
+
* plus an internal `fileIdFilter` on `list` for tag-scoped recovery queries.
|
|
5
27
|
*/
|
|
6
|
-
export declare function createRunsStorage(basedir: string, tag?: string):
|
|
28
|
+
export declare function createRunsStorage(basedir: string, tag?: string): LocalRunsStorage;
|
|
7
29
|
//# sourceMappingURL=runs-storage.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runs-storage.d.ts","sourceRoot":"","sources":["../../src/storage/runs-storage.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"runs-storage.d.ts","sourceRoot":"","sources":["../../src/storage/runs-storage.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,sBAAsB,EACtB,iBAAiB,EACjB,OAAO,EACP,WAAW,EACX,sBAAsB,EACvB,MAAM,iBAAiB,CAAC;AAWzB;;;;;GAKG;AACH,MAAM,WAAW,2BAA4B,SAAQ,sBAAsB;IACzE,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC;CAC5C;AAED,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC;IAC5B,IAAI,EAAE;QACJ,CACE,MAAM,EAAE,2BAA2B,GAAG;YAAE,WAAW,EAAE,MAAM,CAAA;SAAE,GAC5D,OAAO,CAAC,iBAAiB,CAAC,sBAAsB,CAAC,CAAC,CAAC;QACtD,CACE,MAAM,CAAC,EAAE,2BAA2B,GAAG;YAAE,WAAW,CAAC,EAAE,KAAK,CAAA;SAAE,GAC7D,OAAO,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAC,CAAC;QAC3C,CACE,MAAM,CAAC,EAAE,2BAA2B,GACnC,OAAO,CAAC,iBAAiB,CAAC,WAAW,GAAG,sBAAsB,CAAC,CAAC,CAAC;KACrE,CAAC;CACH;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,GAAG,CAAC,EAAE,MAAM,GACX,gBAAgB,CA0DlB"}
|
|
@@ -2,16 +2,18 @@ import path from 'node:path';
|
|
|
2
2
|
import { WorkflowRunNotFoundError } from '@workflow/errors';
|
|
3
3
|
import { WorkflowRunSchema } from '@workflow/world';
|
|
4
4
|
import { DEFAULT_RESOLVE_DATA_OPTION } from '../config.js';
|
|
5
|
-
import { paginatedFileSystemQuery, readJSONWithFallback } from '../fs.js';
|
|
5
|
+
import { assertSafeEntityId, paginatedFileSystemQuery, readJSONWithFallback, } from '../fs.js';
|
|
6
6
|
import { filterRunData } from './filters.js';
|
|
7
7
|
import { getObjectCreatedAt } from './helpers.js';
|
|
8
8
|
/**
|
|
9
9
|
* Creates the runs storage implementation using the filesystem.
|
|
10
|
-
* Implements the Storage['runs'] interface with get and list operations
|
|
10
|
+
* Implements the Storage['runs'] interface with get and list operations,
|
|
11
|
+
* plus an internal `fileIdFilter` on `list` for tag-scoped recovery queries.
|
|
11
12
|
*/
|
|
12
13
|
export function createRunsStorage(basedir, tag) {
|
|
13
14
|
return {
|
|
14
15
|
get: (async (id, params) => {
|
|
16
|
+
assertSafeEntityId('runId', id);
|
|
15
17
|
const run = await readJSONWithFallback(basedir, 'runs', id, WorkflowRunSchema, tag);
|
|
16
18
|
if (!run) {
|
|
17
19
|
throw new WorkflowRunNotFoundError(id);
|
|
@@ -24,6 +26,7 @@ export function createRunsStorage(basedir, tag) {
|
|
|
24
26
|
const result = await paginatedFileSystemQuery({
|
|
25
27
|
directory: path.join(basedir, 'runs'),
|
|
26
28
|
schema: WorkflowRunSchema,
|
|
29
|
+
fileIdFilter: params?.fileIdFilter,
|
|
27
30
|
filter: (run) => {
|
|
28
31
|
if (params?.workflowName &&
|
|
29
32
|
run.workflowName !== params.workflowName) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runs-storage.js","sourceRoot":"","sources":["../../src/storage/runs-storage.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"runs-storage.js","sourceRoot":"","sources":["../../src/storage/runs-storage.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAQ5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,2BAA2B,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,EACL,kBAAkB,EAClB,wBAAwB,EACxB,oBAAoB,GACrB,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AA2BlD;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAC/B,OAAe,EACf,GAAY;IAEZ,OAAO;QACL,GAAG,EAAE,CAAC,KAAK,EAAE,EAAU,EAAE,MAAY,EAAE,EAAE;YACvC,kBAAkB,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;YAChC,MAAM,GAAG,GAAG,MAAM,oBAAoB,CACpC,OAAO,EACP,MAAM,EACN,EAAE,EACF,iBAAiB,EACjB,GAAG,CACJ,CAAC;YACF,IAAI,CAAC,GAAG,EAAE,CAAC;gBACT,MAAM,IAAI,wBAAwB,CAAC,EAAE,CAAC,CAAC;YACzC,CAAC;YACD,MAAM,WAAW,GAAG,MAAM,EAAE,WAAW,IAAI,2BAA2B,CAAC;YACvE,OAAO,aAAa,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;QACzC,CAAC,CAA2B;QAE5B,IAAI,EAAE,CAAC,KAAK,EAAE,MAAoC,EAAE,EAAE;YACpD,MAAM,WAAW,GAAG,MAAM,EAAE,WAAW,IAAI,2BAA2B,CAAC;YACvE,MAAM,MAAM,GAAG,MAAM,wBAAwB,CAAC;gBAC5C,SAAS,EAAE,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC;gBACrC,MAAM,EAAE,iBAAiB;gBACzB,YAAY,EAAE,MAAM,EAAE,YAAY;gBAClC,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE;oBACd,IACE,MAAM,EAAE,YAAY;wBACpB,GAAG,CAAC,YAAY,KAAK,MAAM,CAAC,YAAY,EACxC,CAAC;wBACD,OAAO,KAAK,CAAC;oBACf,CAAC;oBACD,IAAI,MAAM,EAAE,MAAM,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,CAAC;wBACnD,OAAO,KAAK,CAAC;oBACf,CAAC;oBACD,OAAO,IAAI,CAAC;gBACd,CAAC;gBACD,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,IAAI,MAAM;gBAClD,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK;gBAChC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM;gBAClC,YAAY,EAAE,kBAAkB,CAAC,MAAM,CAAC;gBACxC,KAAK,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK;aAC1B,CAAC,CAAC;YAEH,gEAAgE;YAChE,IAAI,WAAW,KAAK,MAAM,EAAE,CAAC;gBAC3B,OAAO;oBACL,GAAG,MAAM;oBACT,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;wBAC9B,GAAG,GAAG;wBACN,KAAK,EAAE,SAAS;wBAChB,MAAM,EAAE,SAAS;qBAClB,CAAC,CAA6B;iBAChC,CAAC;YACJ,CAAC;YAED,OAAO,MAAM,CAAC;QAChB,CAAC,CAA6B;KAC/B,CAAC;AACJ,CAAC","sourcesContent":["import path from 'node:path';\nimport { WorkflowRunNotFoundError } from '@workflow/errors';\nimport type {\n ListWorkflowRunsParams,\n PaginatedResponse,\n Storage,\n WorkflowRun,\n WorkflowRunWithoutData,\n} from '@workflow/world';\nimport { WorkflowRunSchema } from '@workflow/world';\nimport { DEFAULT_RESOLVE_DATA_OPTION } from '../config.js';\nimport {\n assertSafeEntityId,\n paginatedFileSystemQuery,\n readJSONWithFallback,\n} from '../fs.js';\nimport { filterRunData } from './filters.js';\nimport { getObjectCreatedAt } from './helpers.js';\n\n/**\n * Internal extension of `ListWorkflowRunsParams` that adds a `fileIdFilter`\n * for scoping queries by raw filename (e.g., by tag suffix). Kept out of the\n * public `Storage['runs']['list']` surface — consumers of `@workflow/world`\n * must not see this option.\n */\nexport interface LocalListWorkflowRunsParams extends ListWorkflowRunsParams {\n fileIdFilter?: (fileId: string) => boolean;\n}\n\nexport interface LocalRunsStorage {\n get: Storage['runs']['get'];\n list: {\n (\n params: LocalListWorkflowRunsParams & { resolveData: 'none' }\n ): Promise<PaginatedResponse<WorkflowRunWithoutData>>;\n (\n params?: LocalListWorkflowRunsParams & { resolveData?: 'all' }\n ): Promise<PaginatedResponse<WorkflowRun>>;\n (\n params?: LocalListWorkflowRunsParams\n ): Promise<PaginatedResponse<WorkflowRun | WorkflowRunWithoutData>>;\n };\n}\n\n/**\n * Creates the runs storage implementation using the filesystem.\n * Implements the Storage['runs'] interface with get and list operations,\n * plus an internal `fileIdFilter` on `list` for tag-scoped recovery queries.\n */\nexport function createRunsStorage(\n basedir: string,\n tag?: string\n): LocalRunsStorage {\n return {\n get: (async (id: string, params?: any) => {\n assertSafeEntityId('runId', id);\n const run = await readJSONWithFallback(\n basedir,\n 'runs',\n id,\n WorkflowRunSchema,\n tag\n );\n if (!run) {\n throw new WorkflowRunNotFoundError(id);\n }\n const resolveData = params?.resolveData ?? DEFAULT_RESOLVE_DATA_OPTION;\n return filterRunData(run, resolveData);\n }) as Storage['runs']['get'],\n\n list: (async (params?: LocalListWorkflowRunsParams) => {\n const resolveData = params?.resolveData ?? DEFAULT_RESOLVE_DATA_OPTION;\n const result = await paginatedFileSystemQuery({\n directory: path.join(basedir, 'runs'),\n schema: WorkflowRunSchema,\n fileIdFilter: params?.fileIdFilter,\n filter: (run) => {\n if (\n params?.workflowName &&\n run.workflowName !== params.workflowName\n ) {\n return false;\n }\n if (params?.status && run.status !== params.status) {\n return false;\n }\n return true;\n },\n sortOrder: params?.pagination?.sortOrder ?? 'desc',\n limit: params?.pagination?.limit,\n cursor: params?.pagination?.cursor,\n getCreatedAt: getObjectCreatedAt('wrun'),\n getId: (run) => run.runId,\n });\n\n // If resolveData is \"none\", replace input/output with undefined\n if (resolveData === 'none') {\n return {\n ...result,\n data: result.data.map((run) => ({\n ...run,\n input: undefined,\n output: undefined,\n })) as WorkflowRunWithoutData[],\n };\n }\n\n return result;\n }) as LocalRunsStorage['list'],\n };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"steps-storage.d.ts","sourceRoot":"","sources":["../../src/storage/steps-storage.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAmB,OAAO,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"steps-storage.d.ts","sourceRoot":"","sources":["../../src/storage/steps-storage.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAmB,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAahE;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,OAAO,CAAC,CA4DlB"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { StepSchema } from '@workflow/world';
|
|
3
3
|
import { DEFAULT_RESOLVE_DATA_OPTION } from '../config.js';
|
|
4
|
-
import { listJSONFiles, paginatedFileSystemQuery, readJSONWithFallback, stripTag, } from '../fs.js';
|
|
4
|
+
import { assertSafeEntityId, listJSONFiles, paginatedFileSystemQuery, readJSONWithFallback, stripTag, } from '../fs.js';
|
|
5
5
|
import { filterStepData } from './filters.js';
|
|
6
6
|
import { getObjectCreatedAt } from './helpers.js';
|
|
7
7
|
/**
|
|
@@ -11,6 +11,7 @@ import { getObjectCreatedAt } from './helpers.js';
|
|
|
11
11
|
export function createStepsStorage(basedir, tag) {
|
|
12
12
|
return {
|
|
13
13
|
get: (async (runId, stepId, params) => {
|
|
14
|
+
assertSafeEntityId('stepId', stepId);
|
|
14
15
|
if (!runId) {
|
|
15
16
|
const fileIds = await listJSONFiles(path.join(basedir, 'steps'));
|
|
16
17
|
const fileId = fileIds.find((fid) => stripTag(fid).endsWith(`-${stepId}`));
|
|
@@ -19,6 +20,9 @@ export function createStepsStorage(basedir, tag) {
|
|
|
19
20
|
}
|
|
20
21
|
runId = stripTag(fileId).split('-')[0];
|
|
21
22
|
}
|
|
23
|
+
else {
|
|
24
|
+
assertSafeEntityId('runId', runId);
|
|
25
|
+
}
|
|
22
26
|
const compositeKey = `${runId}-${stepId}`;
|
|
23
27
|
const step = await readJSONWithFallback(basedir, 'steps', compositeKey, StepSchema, tag);
|
|
24
28
|
if (!step) {
|
|
@@ -28,6 +32,7 @@ export function createStepsStorage(basedir, tag) {
|
|
|
28
32
|
return filterStepData(step, resolveData);
|
|
29
33
|
}),
|
|
30
34
|
list: (async (params) => {
|
|
35
|
+
assertSafeEntityId('runId', params.runId);
|
|
31
36
|
const resolveData = params.resolveData ?? DEFAULT_RESOLVE_DATA_OPTION;
|
|
32
37
|
const result = await paginatedFileSystemQuery({
|
|
33
38
|
directory: path.join(basedir, 'steps'),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"steps-storage.js","sourceRoot":"","sources":["../../src/storage/steps-storage.ts"],"names":[],"mappings":"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","sourcesContent":["import path from 'node:path';\nimport type { StepWithoutData, Storage } from '@workflow/world';\nimport { StepSchema } from '@workflow/world';\nimport { DEFAULT_RESOLVE_DATA_OPTION } from '../config.js';\nimport {\n listJSONFiles,\n paginatedFileSystemQuery,\n readJSONWithFallback,\n stripTag,\n} from '../fs.js';\nimport { filterStepData } from './filters.js';\nimport { getObjectCreatedAt } from './helpers.js';\n\n/**\n * Creates the steps storage implementation using the filesystem.\n * Implements the Storage['steps'] interface with get and list operations.\n */\nexport function createStepsStorage(\n basedir: string,\n tag?: string\n): Storage['steps'] {\n return {\n get: (async (runId: string | undefined, stepId: string, params?: any) => {\n if (!runId) {\n const fileIds = await listJSONFiles(path.join(basedir, 'steps'));\n const fileId = fileIds.find((fid) =>\n stripTag(fid).endsWith(`-${stepId}`)\n );\n if (!fileId) {\n throw new Error(`Step ${stepId} not found`);\n }\n runId = stripTag(fileId).split('-')[0];\n }\n const compositeKey = `${runId}-${stepId}`;\n const step = await readJSONWithFallback(\n basedir,\n 'steps',\n compositeKey,\n StepSchema,\n tag\n );\n if (!step) {\n throw new Error(`Step ${stepId} in run ${runId} not found`);\n }\n const resolveData = params?.resolveData ?? DEFAULT_RESOLVE_DATA_OPTION;\n return filterStepData(step, resolveData);\n }) as Storage['steps']['get'],\n\n list: (async (params: any) => {\n const resolveData = params.resolveData ?? DEFAULT_RESOLVE_DATA_OPTION;\n const result = await paginatedFileSystemQuery({\n directory: path.join(basedir, 'steps'),\n schema: StepSchema,\n filePrefix: `${params.runId}-`,\n sortOrder: params.pagination?.sortOrder ?? 'desc',\n limit: params.pagination?.limit,\n cursor: params.pagination?.cursor,\n getCreatedAt: getObjectCreatedAt('step'),\n getId: (step) => step.stepId,\n });\n\n // If resolveData is \"none\", replace input/output with undefined\n if (resolveData === 'none') {\n return {\n ...result,\n data: result.data.map((step) => ({\n ...step,\n input: undefined,\n output: undefined,\n })) as StepWithoutData[],\n };\n }\n\n return result;\n }) as Storage['steps']['list'],\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"steps-storage.js","sourceRoot":"","sources":["../../src/storage/steps-storage.ts"],"names":[],"mappings":"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","sourcesContent":["import path from 'node:path';\nimport type { StepWithoutData, Storage } from '@workflow/world';\nimport { StepSchema } from '@workflow/world';\nimport { DEFAULT_RESOLVE_DATA_OPTION } from '../config.js';\nimport {\n assertSafeEntityId,\n listJSONFiles,\n paginatedFileSystemQuery,\n readJSONWithFallback,\n stripTag,\n} from '../fs.js';\nimport { filterStepData } from './filters.js';\nimport { getObjectCreatedAt } from './helpers.js';\n\n/**\n * Creates the steps storage implementation using the filesystem.\n * Implements the Storage['steps'] interface with get and list operations.\n */\nexport function createStepsStorage(\n basedir: string,\n tag?: string\n): Storage['steps'] {\n return {\n get: (async (runId: string | undefined, stepId: string, params?: any) => {\n assertSafeEntityId('stepId', stepId);\n if (!runId) {\n const fileIds = await listJSONFiles(path.join(basedir, 'steps'));\n const fileId = fileIds.find((fid) =>\n stripTag(fid).endsWith(`-${stepId}`)\n );\n if (!fileId) {\n throw new Error(`Step ${stepId} not found`);\n }\n runId = stripTag(fileId).split('-')[0];\n } else {\n assertSafeEntityId('runId', runId);\n }\n const compositeKey = `${runId}-${stepId}`;\n const step = await readJSONWithFallback(\n basedir,\n 'steps',\n compositeKey,\n StepSchema,\n tag\n );\n if (!step) {\n throw new Error(`Step ${stepId} in run ${runId} not found`);\n }\n const resolveData = params?.resolveData ?? DEFAULT_RESOLVE_DATA_OPTION;\n return filterStepData(step, resolveData);\n }) as Storage['steps']['get'],\n\n list: (async (params: any) => {\n assertSafeEntityId('runId', params.runId);\n const resolveData = params.resolveData ?? DEFAULT_RESOLVE_DATA_OPTION;\n const result = await paginatedFileSystemQuery({\n directory: path.join(basedir, 'steps'),\n schema: StepSchema,\n filePrefix: `${params.runId}-`,\n sortOrder: params.pagination?.sortOrder ?? 'desc',\n limit: params.pagination?.limit,\n cursor: params.pagination?.cursor,\n getCreatedAt: getObjectCreatedAt('step'),\n getId: (step) => step.stepId,\n });\n\n // If resolveData is \"none\", replace input/output with undefined\n if (resolveData === 'none') {\n return {\n ...result,\n data: result.data.map((step) => ({\n ...step,\n input: undefined,\n output: undefined,\n })) as StepWithoutData[],\n };\n }\n\n return result;\n }) as Storage['steps']['list'],\n };\n}\n"]}
|
package/dist/streamer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"streamer.d.ts","sourceRoot":"","sources":["../src/streamer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"streamer.d.ts","sourceRoot":"","sources":["../src/streamer.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAGV,QAAQ,EAET,MAAM,iBAAiB,CAAC;AAsBzB;;;;;;GAMG;AACH,MAAM,WAAW,KAAK;IACpB,GAAG,EAAE,OAAO,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAQD,wBAAgB,cAAc,CAAC,KAAK,EAAE,KAAK,uBAG1C;AAED,kEAAkE;AAClE,wBAAgB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAEtD;AAED,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM;;;EAKlD;AA4DD,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,QAAQ,CAujBtE"}
|
package/dist/streamer.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { EventEmitter } from 'node:events';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import { monotonicFactory } from 'ulid';
|
|
4
5
|
import { z } from 'zod';
|
|
5
|
-
import {
|
|
6
|
+
import { assertSafeEntityId, readBuffer, readFirstByte, readJSONWithFallback, taggedPath, write, writeJSON, } from './fs.js';
|
|
6
7
|
// Create a monotonic ULID factory that ensures ULIDs are always increasing
|
|
7
8
|
// even when generated within the same millisecond
|
|
8
9
|
const monotonicUlid = monotonicFactory(() => Math.random());
|
|
@@ -10,46 +11,59 @@ const monotonicUlid = monotonicFactory(() => Math.random());
|
|
|
10
11
|
const RunStreamsSchema = z.object({
|
|
11
12
|
streams: z.array(z.string()),
|
|
12
13
|
});
|
|
14
|
+
const EOF_MARKER = 1;
|
|
15
|
+
function isEofByte(byte) {
|
|
16
|
+
return byte === EOF_MARKER;
|
|
17
|
+
}
|
|
13
18
|
export function serializeChunk(chunk) {
|
|
14
|
-
const eofByte = Buffer.from([chunk.eof ?
|
|
19
|
+
const eofByte = Buffer.from([chunk.eof ? EOF_MARKER : 0]);
|
|
15
20
|
return Buffer.concat([eofByte, chunk.chunk]);
|
|
16
21
|
}
|
|
17
22
|
/** Check only the EOF flag byte without copying chunk payload. */
|
|
18
23
|
export function isEofChunk(serialized) {
|
|
19
|
-
return serialized[0]
|
|
24
|
+
return isEofByte(serialized[0]);
|
|
20
25
|
}
|
|
21
26
|
export function deserializeChunk(serialized) {
|
|
22
|
-
const eof = serialized
|
|
27
|
+
const eof = isEofChunk(serialized);
|
|
23
28
|
// Create a copy instead of a view to prevent ArrayBuffer detachment
|
|
24
29
|
const chunk = Buffer.from(serialized.subarray(1));
|
|
25
30
|
return { eof, chunk };
|
|
26
31
|
}
|
|
32
|
+
async function listChunkEntries(chunksDir) {
|
|
33
|
+
try {
|
|
34
|
+
return await fs.readdir(chunksDir);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
if (error.code === 'ENOENT')
|
|
38
|
+
return [];
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function addChunkFilesByExtension(extMap, entries, sourceExtension, fileExtension = sourceExtension, include = () => true) {
|
|
43
|
+
for (const entry of entries) {
|
|
44
|
+
if (!entry.endsWith(sourceExtension))
|
|
45
|
+
continue;
|
|
46
|
+
const file = entry.slice(0, -sourceExtension.length);
|
|
47
|
+
if (include(file))
|
|
48
|
+
extMap.set(file, fileExtension);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
27
51
|
/**
|
|
28
52
|
* List chunk files for a stream, sorted chronologically (ULID order).
|
|
29
53
|
* Returns both the sorted file names and a map of file → extension for
|
|
30
54
|
* resolving the full path. Handles tagged and legacy (.json) formats.
|
|
31
55
|
*/
|
|
32
56
|
async function listChunkFilesForStream(chunksDir, name, tag) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
];
|
|
37
|
-
if (tag) {
|
|
38
|
-
listPromises.push(listFilesByExtension(chunksDir, `.${tag}.bin`));
|
|
39
|
-
}
|
|
40
|
-
const [binFiles, jsonFiles, ...taggedResults] = await Promise.all(listPromises);
|
|
41
|
-
const taggedBinFiles = taggedResults[0] ?? [];
|
|
57
|
+
// Name is used as a filename prefix below; validate it can't escape chunksDir.
|
|
58
|
+
assertSafeEntityId('streamName', name);
|
|
59
|
+
const entries = await listChunkEntries(chunksDir);
|
|
42
60
|
const extMap = new Map();
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
continue;
|
|
49
|
-
extMap.set(f, '.bin');
|
|
61
|
+
addChunkFilesByExtension(extMap, entries, '.json');
|
|
62
|
+
addChunkFilesByExtension(extMap, entries, '.bin', '.bin', tag ? (file) => !file.endsWith(`.${tag}`) : undefined);
|
|
63
|
+
if (tag) {
|
|
64
|
+
const taggedExtension = `.${tag}.bin`;
|
|
65
|
+
addChunkFilesByExtension(extMap, entries, taggedExtension);
|
|
50
66
|
}
|
|
51
|
-
for (const f of taggedBinFiles)
|
|
52
|
-
extMap.set(f, `.${tag}.bin`);
|
|
53
67
|
const files = [...extMap.keys()]
|
|
54
68
|
.filter((file) => file.startsWith(`${name}-`))
|
|
55
69
|
.sort();
|
|
@@ -62,6 +76,8 @@ export function createStreamer(basedir, tag) {
|
|
|
62
76
|
const registeredStreams = new Set();
|
|
63
77
|
// Helper to record the runId <> streamId association
|
|
64
78
|
async function registerStreamForRun(runId, streamName) {
|
|
79
|
+
assertSafeEntityId('runId', runId);
|
|
80
|
+
assertSafeEntityId('streamName', streamName);
|
|
65
81
|
const cacheKey = `${runId}:${streamName}`;
|
|
66
82
|
if (registeredStreams.has(cacheKey)) {
|
|
67
83
|
return; // Already registered in this session
|
|
@@ -165,6 +181,7 @@ export function createStreamer(basedir, tag) {
|
|
|
165
181
|
streamEmitter.emit(`close:${name}`, { streamName: name });
|
|
166
182
|
},
|
|
167
183
|
async listStreamsByRunId(runId) {
|
|
184
|
+
assertSafeEntityId('runId', runId);
|
|
168
185
|
const data = await readJSONWithFallback(basedir, 'streams/runs', runId, RunStreamsSchema, tag);
|
|
169
186
|
return data?.streams ?? [];
|
|
170
187
|
},
|
|
@@ -193,7 +210,7 @@ export function createStreamer(basedir, tag) {
|
|
|
193
210
|
const filePath = path.join(chunksDir, `${file}${ext}`);
|
|
194
211
|
// Before the cursor: only need to check EOF (1 byte), skip content
|
|
195
212
|
if (dataIndex < startIndex) {
|
|
196
|
-
if (
|
|
213
|
+
if (isEofByte(await readFirstByte(filePath))) {
|
|
197
214
|
streamDone = true;
|
|
198
215
|
break;
|
|
199
216
|
}
|
|
@@ -202,7 +219,7 @@ export function createStreamer(basedir, tag) {
|
|
|
202
219
|
}
|
|
203
220
|
// Collected enough data chunks — peek at the next file for EOF/hasMore
|
|
204
221
|
if (resultChunks.length >= limit) {
|
|
205
|
-
if (
|
|
222
|
+
if (isEofByte(await readFirstByte(filePath))) {
|
|
206
223
|
streamDone = true;
|
|
207
224
|
}
|
|
208
225
|
else {
|
|
@@ -239,13 +256,12 @@ export function createStreamer(basedir, tag) {
|
|
|
239
256
|
async getStreamInfo(name, _runId) {
|
|
240
257
|
const chunksDir = path.join(basedir, 'streams', 'chunks');
|
|
241
258
|
const { files: chunkFiles, extMap: fileExtMap } = await listChunkFilesForStream(chunksDir, name, tag);
|
|
242
|
-
//
|
|
243
|
-
// deserialization needed since we just need a count.
|
|
259
|
+
// Read only the EOF marker byte because metadata never needs payloads.
|
|
244
260
|
let streamDone = false;
|
|
245
261
|
let dataCount = 0;
|
|
246
262
|
for (const file of chunkFiles) {
|
|
247
263
|
const ext = fileExtMap.get(file) ?? '.bin';
|
|
248
|
-
if (
|
|
264
|
+
if (isEofByte(await readFirstByte(path.join(chunksDir, `${file}${ext}`)))) {
|
|
249
265
|
streamDone = true;
|
|
250
266
|
break;
|
|
251
267
|
}
|
|
@@ -328,10 +344,7 @@ export function createStreamer(basedir, tag) {
|
|
|
328
344
|
chunkFiles.length > 0) {
|
|
329
345
|
const lastFile = chunkFiles[chunkFiles.length - 1];
|
|
330
346
|
const lastExt = fileExtMap.get(lastFile) ?? '.bin';
|
|
331
|
-
|
|
332
|
-
// Acceptable since negative startIndex is not a hot path.
|
|
333
|
-
const lastChunk = deserializeChunk(await readBuffer(path.join(chunksDir, `${lastFile}${lastExt}`)));
|
|
334
|
-
if (lastChunk?.eof === true) {
|
|
347
|
+
if (isEofByte(await readFirstByte(path.join(chunksDir, `${lastFile}${lastExt}`)))) {
|
|
335
348
|
dataChunkCount--;
|
|
336
349
|
}
|
|
337
350
|
}
|