@workflow/world-local 5.0.0-beta.2 → 5.0.0-beta.4
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 +53 -2
- package/dist/fs.d.ts.map +1 -1
- package/dist/fs.js +108 -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/storage/events-storage.d.ts.map +1 -1
- package/dist/storage/events-storage.js +694 -600
- 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 +4 -1
- package/dist/storage/steps-storage.js.map +1 -1
- package/dist/streamer.d.ts.map +1 -1
- package/dist/streamer.js +6 -1
- package/dist/streamer.js.map +1 -1
- package/package.json +4 -4
|
@@ -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;AAWhE;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,OAAO,CAAC,CAiDlB"}
|
|
@@ -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 { paginatedFileSystemQuery, readJSONWithFallback } from '../fs.js';
|
|
4
|
+
import { assertSafeEntityId, paginatedFileSystemQuery, readJSONWithFallback, } from '../fs.js';
|
|
5
5
|
import { filterStepData } from './filters.js';
|
|
6
6
|
import { getObjectCreatedAt } from './helpers.js';
|
|
7
7
|
/**
|
|
@@ -11,6 +11,8 @@ import { getObjectCreatedAt } from './helpers.js';
|
|
|
11
11
|
export function createStepsStorage(basedir, tag) {
|
|
12
12
|
return {
|
|
13
13
|
get: (async (runId, stepId, params) => {
|
|
14
|
+
assertSafeEntityId('runId', runId);
|
|
15
|
+
assertSafeEntityId('stepId', stepId);
|
|
14
16
|
const compositeKey = `${runId}-${stepId}`;
|
|
15
17
|
const step = await readJSONWithFallback(basedir, 'steps', compositeKey, StepSchema, tag);
|
|
16
18
|
if (!step) {
|
|
@@ -20,6 +22,7 @@ export function createStepsStorage(basedir, tag) {
|
|
|
20
22
|
return filterStepData(step, resolveData);
|
|
21
23
|
}),
|
|
22
24
|
list: (async (params) => {
|
|
25
|
+
assertSafeEntityId('runId', params.runId);
|
|
23
26
|
const resolveData = params.resolveData ?? DEFAULT_RESOLVE_DATA_OPTION;
|
|
24
27
|
const result = await paginatedFileSystemQuery({
|
|
25
28
|
directory: path.join(basedir, 'steps'),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"steps-storage.js","sourceRoot":"","sources":["../../src/storage/steps-storage.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,2BAA2B,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,
|
|
1
|
+
{"version":3,"file":"steps-storage.js","sourceRoot":"","sources":["../../src/storage/steps-storage.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,2BAA2B,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,EACL,kBAAkB,EAClB,wBAAwB,EACxB,oBAAoB,GACrB,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAElD;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAChC,OAAe,EACf,GAAY;IAEZ,OAAO;QACL,GAAG,EAAE,CAAC,KAAK,EAAE,KAAa,EAAE,MAAc,EAAE,MAAY,EAAE,EAAE;YAC1D,kBAAkB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;YACnC,kBAAkB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;YACrC,MAAM,YAAY,GAAG,GAAG,KAAK,IAAI,MAAM,EAAE,CAAC;YAC1C,MAAM,IAAI,GAAG,MAAM,oBAAoB,CACrC,OAAO,EACP,OAAO,EACP,YAAY,EACZ,UAAU,EACV,GAAG,CACJ,CAAC;YACF,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,MAAM,IAAI,KAAK,CAAC,QAAQ,MAAM,WAAW,KAAK,YAAY,CAAC,CAAC;YAC9D,CAAC;YACD,MAAM,WAAW,GAAG,MAAM,EAAE,WAAW,IAAI,2BAA2B,CAAC;YACvE,OAAO,cAAc,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;QAC3C,CAAC,CAA4B;QAE7B,IAAI,EAAE,CAAC,KAAK,EAAE,MAAW,EAAE,EAAE;YAC3B,kBAAkB,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;YAC1C,MAAM,WAAW,GAAG,MAAM,CAAC,WAAW,IAAI,2BAA2B,CAAC;YACtE,MAAM,MAAM,GAAG,MAAM,wBAAwB,CAAC;gBAC5C,SAAS,EAAE,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC;gBACtC,MAAM,EAAE,UAAU;gBAClB,UAAU,EAAE,GAAG,MAAM,CAAC,KAAK,GAAG;gBAC9B,SAAS,EAAE,MAAM,CAAC,UAAU,EAAE,SAAS,IAAI,MAAM;gBACjD,KAAK,EAAE,MAAM,CAAC,UAAU,EAAE,KAAK;gBAC/B,MAAM,EAAE,MAAM,CAAC,UAAU,EAAE,MAAM;gBACjC,YAAY,EAAE,kBAAkB,CAAC,MAAM,CAAC;gBACxC,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM;aAC7B,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,IAAI,EAAE,EAAE,CAAC,CAAC;wBAC/B,GAAG,IAAI;wBACP,KAAK,EAAE,SAAS;wBAChB,MAAM,EAAE,SAAS;qBAClB,CAAC,CAAsB;iBACzB,CAAC;YACJ,CAAC;YAED,OAAO,MAAM,CAAC;QAChB,CAAC,CAA6B;KAC/B,CAAC;AACJ,CAAC","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 paginatedFileSystemQuery,\n readJSONWithFallback,\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, stepId: string, params?: any) => {\n assertSafeEntityId('runId', runId);\n assertSafeEntityId('stepId', stepId);\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":"AAEA,OAAO,KAAK,EAGV,QAAQ,EAET,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"streamer.d.ts","sourceRoot":"","sources":["../src/streamer.ts"],"names":[],"mappings":"AAEA,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;AAED,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;AAyCD,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,QAAQ,CAsjBtE"}
|
package/dist/streamer.js
CHANGED
|
@@ -2,7 +2,7 @@ import { EventEmitter } from 'node:events';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { monotonicFactory } from 'ulid';
|
|
4
4
|
import { z } from 'zod';
|
|
5
|
-
import { listFilesByExtension, readBuffer, readJSONWithFallback, taggedPath, write, writeJSON, } from './fs.js';
|
|
5
|
+
import { assertSafeEntityId, listFilesByExtension, readBuffer, readJSONWithFallback, taggedPath, write, writeJSON, } from './fs.js';
|
|
6
6
|
// Create a monotonic ULID factory that ensures ULIDs are always increasing
|
|
7
7
|
// even when generated within the same millisecond
|
|
8
8
|
const monotonicUlid = monotonicFactory(() => Math.random());
|
|
@@ -30,6 +30,8 @@ export function deserializeChunk(serialized) {
|
|
|
30
30
|
* resolving the full path. Handles tagged and legacy (.json) formats.
|
|
31
31
|
*/
|
|
32
32
|
async function listChunkFilesForStream(chunksDir, name, tag) {
|
|
33
|
+
// Name is used as a filename prefix below; validate it can't escape chunksDir.
|
|
34
|
+
assertSafeEntityId('streamName', name);
|
|
33
35
|
const listPromises = [
|
|
34
36
|
listFilesByExtension(chunksDir, '.bin'),
|
|
35
37
|
listFilesByExtension(chunksDir, '.json'),
|
|
@@ -62,6 +64,8 @@ export function createStreamer(basedir, tag) {
|
|
|
62
64
|
const registeredStreams = new Set();
|
|
63
65
|
// Helper to record the runId <> streamId association
|
|
64
66
|
async function registerStreamForRun(runId, streamName) {
|
|
67
|
+
assertSafeEntityId('runId', runId);
|
|
68
|
+
assertSafeEntityId('streamName', streamName);
|
|
65
69
|
const cacheKey = `${runId}:${streamName}`;
|
|
66
70
|
if (registeredStreams.has(cacheKey)) {
|
|
67
71
|
return; // Already registered in this session
|
|
@@ -166,6 +170,7 @@ export function createStreamer(basedir, tag) {
|
|
|
166
170
|
streamEmitter.emit(`close:${name}`, { streamName: name });
|
|
167
171
|
},
|
|
168
172
|
async list(runId) {
|
|
173
|
+
assertSafeEntityId('runId', runId);
|
|
169
174
|
const data = await readJSONWithFallback(basedir, 'streams/runs', runId, RunStreamsSchema, tag);
|
|
170
175
|
return data?.streams ?? [];
|
|
171
176
|
},
|
package/dist/streamer.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"streamer.js","sourceRoot":"","sources":["../src/streamer.ts"],"names":[],"mappings":"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","sourcesContent":["import { EventEmitter } from 'node:events';\nimport path from 'node:path';\nimport type {\n GetChunksOptions,\n StreamChunksResponse,\n Streamer,\n StreamInfoResponse,\n} from '@workflow/world';\nimport { monotonicFactory } from 'ulid';\nimport { z } from 'zod';\nimport {\n listFilesByExtension,\n readBuffer,\n readJSONWithFallback,\n taggedPath,\n write,\n writeJSON,\n} from './fs.js';\n\n// Create a monotonic ULID factory that ensures ULIDs are always increasing\n// even when generated within the same millisecond\nconst monotonicUlid = monotonicFactory(() => Math.random());\n\n// Schema for the run-to-streams mapping file\nconst RunStreamsSchema = z.object({\n streams: z.array(z.string()),\n});\n\n/**\n * A chunk consists of a boolean `eof` indicating if it's the last chunk,\n * and a `chunk` which is a Buffer of data.\n * The serialized format is:\n * - 1 byte for `eof` (0 or 1)\n * - and the rest is the chunk data.\n */\nexport interface Chunk {\n eof: boolean;\n chunk: Buffer;\n}\n\nexport function serializeChunk(chunk: Chunk) {\n const eofByte = Buffer.from([chunk.eof ? 1 : 0]);\n return Buffer.concat([eofByte, chunk.chunk]);\n}\n\n/** Check only the EOF flag byte without copying chunk payload. */\nexport function isEofChunk(serialized: Buffer): boolean {\n return serialized[0] === 1;\n}\n\nexport function deserializeChunk(serialized: Buffer) {\n const eof = serialized[0] === 1;\n // Create a copy instead of a view to prevent ArrayBuffer detachment\n const chunk = Buffer.from(serialized.subarray(1));\n return { eof, chunk };\n}\n\n/**\n * List chunk files for a stream, sorted chronologically (ULID order).\n * Returns both the sorted file names and a map of file → extension for\n * resolving the full path. Handles tagged and legacy (.json) formats.\n */\nasync function listChunkFilesForStream(\n chunksDir: string,\n name: string,\n tag?: string\n): Promise<{ files: string[]; extMap: Map<string, string> }> {\n const listPromises: Promise<string[]>[] = [\n listFilesByExtension(chunksDir, '.bin'),\n listFilesByExtension(chunksDir, '.json'),\n ];\n if (tag) {\n listPromises.push(listFilesByExtension(chunksDir, `.${tag}.bin`));\n }\n const [binFiles, jsonFiles, ...taggedResults] =\n await Promise.all(listPromises);\n const taggedBinFiles = taggedResults[0] ?? [];\n\n const extMap = new Map<string, string>();\n for (const f of jsonFiles) extMap.set(f, '.json');\n const tagSfx = tag ? `.${tag}` : '';\n for (const f of binFiles) {\n if (tag && f.endsWith(tagSfx)) continue;\n extMap.set(f, '.bin');\n }\n for (const f of taggedBinFiles) extMap.set(f, `.${tag}.bin`);\n\n const files = [...extMap.keys()]\n .filter((file) => file.startsWith(`${name}-`))\n .sort();\n\n return { files, extMap };\n}\n\nexport function createStreamer(basedir: string, tag?: string): Streamer {\n const tagSuffix = tag ? `.${tag}` : '';\n const streamEmitter = new EventEmitter<{\n [key: `chunk:${string}`]: [\n {\n streamName: string;\n chunkData: Uint8Array;\n chunkId: string;\n },\n ];\n [key: `close:${string}`]: [\n {\n streamName: string;\n },\n ];\n }>();\n\n // Track which streams have already been registered for a run (in-memory cache)\n const registeredStreams = new Set<string>();\n\n // Helper to record the runId <> streamId association\n async function registerStreamForRun(\n runId: string,\n streamName: string\n ): Promise<void> {\n const cacheKey = `${runId}:${streamName}`;\n if (registeredStreams.has(cacheKey)) {\n return; // Already registered in this session\n }\n\n const runStreamsPath = taggedPath(basedir, 'streams/runs', runId, tag);\n\n // Read existing streams for this run (try tagged first, fall back to untagged)\n const existing = await readJSONWithFallback(\n basedir,\n 'streams/runs',\n runId,\n RunStreamsSchema,\n tag\n );\n const streams = existing?.streams ?? [];\n\n // Add stream if not already present\n if (!streams.includes(streamName)) {\n streams.push(streamName);\n await writeJSON(runStreamsPath, { streams }, { overwrite: true });\n }\n\n registeredStreams.add(cacheKey);\n }\n\n // Helper to convert a chunk to a Buffer\n function toBuffer(chunk: string | Uint8Array): Buffer {\n if (typeof chunk === 'string') {\n return Buffer.from(new TextEncoder().encode(chunk));\n } else if (chunk instanceof Buffer) {\n return chunk;\n } else {\n return Buffer.from(chunk);\n }\n }\n\n return {\n streams: {\n async write(\n _runId: string | Promise<string>,\n name: string,\n chunk: string | Uint8Array\n ) {\n // Generate ULID synchronously BEFORE any await to preserve call order.\n // This ensures that chunks written in sequence maintain their order even\n // when runId is a promise that multiple writes are waiting on.\n const chunkId = `chnk_${monotonicUlid()}`;\n\n // Await runId if it's a promise to ensure proper flushing\n const runId = await _runId;\n\n // Register this stream for the run\n await registerStreamForRun(runId, name);\n\n // Convert chunk to buffer for serialization\n const chunkBuffer = toBuffer(chunk);\n\n const serialized = serializeChunk({\n chunk: chunkBuffer,\n eof: false,\n });\n\n const chunkPath = path.join(\n basedir,\n 'streams',\n 'chunks',\n `${name}-${chunkId}${tagSuffix}.bin`\n );\n\n await write(chunkPath, serialized);\n\n // Emit real-time event with Uint8Array (create copy to prevent ArrayBuffer detachment)\n const chunkData = Uint8Array.from(chunkBuffer);\n\n streamEmitter.emit(`chunk:${name}` as const, {\n streamName: name,\n chunkData,\n chunkId,\n });\n },\n\n async writeMulti(\n _runId: string | Promise<string>,\n name: string,\n chunks: (string | Uint8Array)[]\n ) {\n if (chunks.length === 0) return;\n\n // Generate all ULIDs synchronously BEFORE any await to preserve call order.\n // This ensures that chunks maintain their order even when runId is a promise.\n const chunkIds = chunks.map(() => `chnk_${monotonicUlid()}`);\n\n // Await runId if it's a promise\n const runId = await _runId;\n\n // Register this stream for the run\n await registerStreamForRun(runId, name);\n\n // Prepare chunk data for parallel writes\n const chunkBuffers = chunks.map((chunk) => toBuffer(chunk));\n\n // Write all chunks in parallel for efficiency, but track individual completion\n const writePromises = chunkBuffers.map(async (chunkBuffer, i) => {\n const chunkId = chunkIds[i];\n\n const serialized = serializeChunk({\n chunk: chunkBuffer,\n eof: false,\n });\n\n const chunkPath = path.join(\n basedir,\n 'streams',\n 'chunks',\n `${name}-${chunkId}${tagSuffix}.bin`\n );\n\n await write(chunkPath, serialized);\n\n // Return data needed for event emission\n return {\n chunkId,\n chunkData: Uint8Array.from(chunkBuffer),\n };\n });\n\n // Emit events in order, waiting for each chunk's write to complete\n // This ensures events are emitted in order while writes happen in parallel\n for (const writePromise of writePromises) {\n const { chunkId, chunkData } = await writePromise;\n\n streamEmitter.emit(`chunk:${name}` as const, {\n streamName: name,\n chunkData,\n chunkId,\n });\n }\n },\n\n async close(_runId: string | Promise<string>, name: string) {\n // Generate ULID synchronously BEFORE any await to preserve call order.\n const chunkId = `chnk_${monotonicUlid()}`;\n\n // Await runId if it's a promise to ensure proper flushing\n const runId = await _runId;\n\n // Register this stream for the run (in case write wasn't called)\n await registerStreamForRun(runId, name);\n const chunkPath = path.join(\n basedir,\n 'streams',\n 'chunks',\n `${name}-${chunkId}${tagSuffix}.bin`\n );\n\n await write(\n chunkPath,\n serializeChunk({ chunk: Buffer.from([]), eof: true })\n );\n\n streamEmitter.emit(`close:${name}` as const, { streamName: name });\n },\n\n async list(runId: string) {\n const data = await readJSONWithFallback(\n basedir,\n 'streams/runs',\n runId,\n RunStreamsSchema,\n tag\n );\n return data?.streams ?? [];\n },\n\n async getChunks(\n _runId: string,\n name: string,\n options?: GetChunksOptions\n ): Promise<StreamChunksResponse> {\n const limit = options?.limit ?? 100;\n const chunksDir = path.join(basedir, 'streams', 'chunks');\n const { files: chunkFiles, extMap: fileExtMap } =\n await listChunkFilesForStream(chunksDir, name, tag);\n\n // Decode cursor\n let startIndex = 0;\n if (options?.cursor) {\n try {\n const decoded = JSON.parse(\n Buffer.from(options.cursor, 'base64').toString('utf-8')\n );\n startIndex = decoded.i;\n } catch {\n startIndex = 0;\n }\n }\n\n // Walk from startIndex, reading only the files we need.\n // Files before the cursor are skipped entirely.\n let streamDone = false;\n const resultChunks: { index: number; data: Uint8Array }[] = [];\n let dataIndex = 0; // running count of data (non-EOF) files seen\n\n for (const file of chunkFiles) {\n const ext = fileExtMap.get(file) ?? '.bin';\n const filePath = path.join(chunksDir, `${file}${ext}`);\n\n // Before the cursor: only need to check EOF (1 byte), skip content\n if (dataIndex < startIndex) {\n if (isEofChunk(await readBuffer(filePath))) {\n streamDone = true;\n break;\n }\n dataIndex++;\n continue;\n }\n\n // Collected enough data chunks — peek at the next file for EOF/hasMore\n if (resultChunks.length >= limit) {\n if (isEofChunk(await readBuffer(filePath))) {\n streamDone = true;\n } else {\n // More data files exist beyond this page\n dataIndex++;\n }\n break;\n }\n\n // In the page window: deserialize fully\n const chunk = deserializeChunk(await readBuffer(filePath));\n if (chunk.eof) {\n streamDone = true;\n break;\n }\n resultChunks.push({\n index: dataIndex,\n data: Uint8Array.from(chunk.chunk),\n });\n dataIndex++;\n }\n\n // hasMore = we know there are data files beyond this page\n const hasMore =\n !streamDone && dataIndex > startIndex + resultChunks.length;\n const nextIndex = startIndex + resultChunks.length;\n const nextCursor = hasMore\n ? Buffer.from(JSON.stringify({ i: nextIndex })).toString('base64')\n : null;\n\n return {\n data: resultChunks,\n cursor: nextCursor,\n hasMore,\n done: streamDone,\n };\n },\n\n async getInfo(_runId: string, name: string): Promise<StreamInfoResponse> {\n const chunksDir = path.join(basedir, 'streams', 'chunks');\n const { files: chunkFiles, extMap: fileExtMap } =\n await listChunkFilesForStream(chunksDir, name, tag);\n\n // Only read the first byte of each file to check EOF — no full\n // deserialization needed since we just need a count.\n let streamDone = false;\n let dataCount = 0;\n for (const file of chunkFiles) {\n const ext = fileExtMap.get(file) ?? '.bin';\n if (\n isEofChunk(await readBuffer(path.join(chunksDir, `${file}${ext}`)))\n ) {\n streamDone = true;\n break;\n }\n dataCount++;\n }\n\n return { tailIndex: dataCount - 1, done: streamDone };\n },\n\n async get(_runId: string, name: string, startIndex = 0) {\n const chunksDir = path.join(basedir, 'streams', 'chunks');\n let removeListeners = () => {};\n let pollInterval: ReturnType<typeof setInterval> | null = null;\n\n return new ReadableStream<Uint8Array>({\n async start(controller) {\n // Track chunks delivered via events to prevent duplicates and maintain order.\n const deliveredChunkIds = new Set<string>();\n // Buffer for chunks that arrive via events during disk reading\n const bufferedEventChunks: Array<{\n chunkId: string;\n chunkData: Uint8Array;\n }> = [];\n let isReadingFromDisk = true;\n // Buffer close event if it arrives during disk reading\n let pendingClose = false;\n // Set when the controller is closed; guards against enqueue-after-close\n // in the polling callback when closeListener fires mid-iteration.\n let streamClosed = false;\n\n const chunkListener = (event: {\n streamName: string;\n chunkData: Uint8Array;\n chunkId: string;\n }) => {\n // Skip empty chunks to maintain consistency with disk reading behavior\n if (event.chunkData.byteLength === 0) {\n deliveredChunkIds.add(event.chunkId);\n return;\n }\n\n if (isReadingFromDisk) {\n deliveredChunkIds.add(event.chunkId);\n // Buffer chunks that arrive during disk reading to maintain order\n // Create a copy to prevent ArrayBuffer detachment when enqueued later\n bufferedEventChunks.push({\n chunkId: event.chunkId,\n chunkData: Uint8Array.from(event.chunkData),\n });\n } else if (!deliveredChunkIds.has(event.chunkId)) {\n // Guard against duplicates: polling may have already claimed this\n // chunk between its has() check and readBuffer() yield.\n deliveredChunkIds.add(event.chunkId);\n // After disk reading is complete, deliver chunks immediately\n // Create a copy to prevent ArrayBuffer detachment\n controller.enqueue(Uint8Array.from(event.chunkData));\n }\n };\n\n const closeListener = () => {\n // Buffer close event if disk reading is still in progress\n if (isReadingFromDisk) {\n pendingClose = true;\n return;\n }\n // Remove listeners before closing\n streamClosed = true;\n streamEmitter.off(`chunk:${name}` as const, chunkListener);\n streamEmitter.off(`close:${name}` as const, closeListener);\n if (pollInterval) {\n clearInterval(pollInterval);\n pollInterval = null;\n }\n try {\n controller.close();\n } catch {\n // Ignore if controller is already closed (e.g., from cancel() or EOF)\n }\n };\n removeListeners = closeListener;\n\n // Set up listeners FIRST to avoid missing events\n streamEmitter.on(`chunk:${name}` as const, chunkListener);\n streamEmitter.on(`close:${name}` as const, closeListener);\n\n // Now load existing chunks from disk.\n const { files: chunkFiles, extMap: fileExtMap } =\n await listChunkFilesForStream(chunksDir, name, tag);\n\n // Resolve negative startIndex relative to the number of data chunks\n // (excluding the trailing EOF marker chunk, if present).\n let dataChunkCount = chunkFiles.length;\n if (\n typeof startIndex === 'number' &&\n startIndex < 0 &&\n chunkFiles.length > 0\n ) {\n const lastFile = chunkFiles[chunkFiles.length - 1];\n const lastExt = fileExtMap.get(lastFile) ?? '.bin';\n // Note: this incurs an extra disk read to check the EOF marker.\n // Acceptable since negative startIndex is not a hot path.\n const lastChunk = deserializeChunk(\n await readBuffer(path.join(chunksDir, `${lastFile}${lastExt}`))\n );\n if (lastChunk?.eof === true) {\n dataChunkCount--;\n }\n }\n const resolvedStartIndex =\n typeof startIndex === 'number' && startIndex < 0\n ? Math.max(0, dataChunkCount + startIndex)\n : startIndex;\n\n // Process existing chunks, skipping any already delivered via events\n let isComplete = false;\n for (let i = resolvedStartIndex; i < chunkFiles.length; i++) {\n const file = chunkFiles[i];\n // Extract chunk ID from filename: \"streamName-chunkId\" or \"streamName-chunkId.tag\"\n const rawChunkId = file.substring(name.length + 1);\n // Strip tag suffix (e.g., \"chnk_ULID.vitest-0\" → \"chnk_ULID\")\n const chunkId = tag\n ? rawChunkId.replace(`.${tag}`, '')\n : rawChunkId;\n\n // Skip if already delivered via event\n if (deliveredChunkIds.has(chunkId)) {\n continue;\n }\n\n const ext = fileExtMap.get(file) ?? '.bin';\n const chunk = deserializeChunk(\n await readBuffer(path.join(chunksDir, `${file}${ext}`))\n );\n if (chunk?.eof === true) {\n isComplete = true;\n break;\n }\n // Track as handled so polling doesn't re-deliver\n deliveredChunkIds.add(chunkId);\n if (chunk.chunk.byteLength) {\n // Create a copy to prevent ArrayBuffer detachment\n controller.enqueue(Uint8Array.from(chunk.chunk));\n }\n }\n\n // Finished reading from disk - now deliver buffered event chunks in chronological order\n isReadingFromDisk = false;\n\n // Sort buffered chunks by ULID (chronological order)\n bufferedEventChunks.sort((a, b) =>\n a.chunkId.localeCompare(b.chunkId)\n );\n for (const buffered of bufferedEventChunks) {\n // Create a copy for defense in depth (already copied at storage, but be extra safe)\n controller.enqueue(Uint8Array.from(buffered.chunkData));\n }\n\n if (isComplete) {\n removeListeners();\n try {\n controller.close();\n } catch {\n // Ignore if controller is already closed (e.g., from closeListener event)\n }\n return;\n }\n\n // Process any pending close event that arrived during disk reading\n if (pendingClose) {\n streamEmitter.off(`chunk:${name}` as const, chunkListener);\n streamEmitter.off(`close:${name}` as const, closeListener);\n try {\n controller.close();\n } catch {\n // Ignore if controller is already closed\n }\n return;\n }\n\n // Track pre-startIndex chunks so polling doesn't re-deliver them\n for (\n let i = 0;\n i < resolvedStartIndex && i < chunkFiles.length;\n i++\n ) {\n const file = chunkFiles[i];\n const rawChunkId = file.substring(name.length + 1);\n const chunkId = tag\n ? rawChunkId.replace(`.${tag}`, '')\n : rawChunkId;\n deliveredChunkIds.add(chunkId);\n }\n\n // Start filesystem polling for cross-process streaming support.\n // The EventEmitter only works in-process; when the writer is in a\n // separate process (e.g. e2e test runner ↔ workbench app), polling\n // the shared filesystem is the fallback delivery mechanism.\n let isPolling = false;\n pollInterval = setInterval(async () => {\n if (isPolling) return;\n isPolling = true;\n try {\n const { files: currentFiles, extMap: currentExtMap } =\n await listChunkFilesForStream(chunksDir, name, tag);\n\n for (const file of currentFiles) {\n const rawChunkId = file.substring(name.length + 1);\n const chunkId = tag\n ? rawChunkId.replace(`.${tag}`, '')\n : rawChunkId;\n\n if (deliveredChunkIds.has(chunkId)) continue;\n deliveredChunkIds.add(chunkId);\n\n const ext = currentExtMap.get(file) ?? '.bin';\n const chunk = deserializeChunk(\n await readBuffer(path.join(chunksDir, `${file}${ext}`))\n );\n\n if (chunk?.eof === true) {\n streamClosed = true;\n if (pollInterval) {\n clearInterval(pollInterval);\n pollInterval = null;\n }\n streamEmitter.off(`chunk:${name}` as const, chunkListener);\n streamEmitter.off(`close:${name}` as const, closeListener);\n try {\n controller.close();\n } catch {\n // Ignore if controller is already closed\n }\n return;\n }\n\n // Guard against enqueue-after-close: closeListener may have\n // fired between our readBuffer() yield and this point.\n if (streamClosed) return;\n\n if (chunk.chunk.byteLength) {\n controller.enqueue(Uint8Array.from(chunk.chunk));\n }\n }\n } catch (err: unknown) {\n // Silently ignore transient filesystem errors (ENOENT, EACCES, etc.)\n // Surface unexpected errors so bugs aren't hidden\n if (!(err instanceof Error && 'code' in err)) {\n console.error('[world-local] Unexpected polling error:', err);\n }\n } finally {\n isPolling = false;\n }\n }, 100);\n },\n\n cancel() {\n removeListeners();\n if (pollInterval) {\n clearInterval(pollInterval);\n pollInterval = null;\n }\n },\n });\n },\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"streamer.js","sourceRoot":"","sources":["../src/streamer.ts"],"names":[],"mappings":"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","sourcesContent":["import { EventEmitter } from 'node:events';\nimport path from 'node:path';\nimport type {\n GetChunksOptions,\n StreamChunksResponse,\n Streamer,\n StreamInfoResponse,\n} from '@workflow/world';\nimport { monotonicFactory } from 'ulid';\nimport { z } from 'zod';\nimport {\n assertSafeEntityId,\n listFilesByExtension,\n readBuffer,\n readJSONWithFallback,\n taggedPath,\n write,\n writeJSON,\n} from './fs.js';\n\n// Create a monotonic ULID factory that ensures ULIDs are always increasing\n// even when generated within the same millisecond\nconst monotonicUlid = monotonicFactory(() => Math.random());\n\n// Schema for the run-to-streams mapping file\nconst RunStreamsSchema = z.object({\n streams: z.array(z.string()),\n});\n\n/**\n * A chunk consists of a boolean `eof` indicating if it's the last chunk,\n * and a `chunk` which is a Buffer of data.\n * The serialized format is:\n * - 1 byte for `eof` (0 or 1)\n * - and the rest is the chunk data.\n */\nexport interface Chunk {\n eof: boolean;\n chunk: Buffer;\n}\n\nexport function serializeChunk(chunk: Chunk) {\n const eofByte = Buffer.from([chunk.eof ? 1 : 0]);\n return Buffer.concat([eofByte, chunk.chunk]);\n}\n\n/** Check only the EOF flag byte without copying chunk payload. */\nexport function isEofChunk(serialized: Buffer): boolean {\n return serialized[0] === 1;\n}\n\nexport function deserializeChunk(serialized: Buffer) {\n const eof = serialized[0] === 1;\n // Create a copy instead of a view to prevent ArrayBuffer detachment\n const chunk = Buffer.from(serialized.subarray(1));\n return { eof, chunk };\n}\n\n/**\n * List chunk files for a stream, sorted chronologically (ULID order).\n * Returns both the sorted file names and a map of file → extension for\n * resolving the full path. Handles tagged and legacy (.json) formats.\n */\nasync function listChunkFilesForStream(\n chunksDir: string,\n name: string,\n tag?: string\n): Promise<{ files: string[]; extMap: Map<string, string> }> {\n // Name is used as a filename prefix below; validate it can't escape chunksDir.\n assertSafeEntityId('streamName', name);\n const listPromises: Promise<string[]>[] = [\n listFilesByExtension(chunksDir, '.bin'),\n listFilesByExtension(chunksDir, '.json'),\n ];\n if (tag) {\n listPromises.push(listFilesByExtension(chunksDir, `.${tag}.bin`));\n }\n const [binFiles, jsonFiles, ...taggedResults] =\n await Promise.all(listPromises);\n const taggedBinFiles = taggedResults[0] ?? [];\n\n const extMap = new Map<string, string>();\n for (const f of jsonFiles) extMap.set(f, '.json');\n const tagSfx = tag ? `.${tag}` : '';\n for (const f of binFiles) {\n if (tag && f.endsWith(tagSfx)) continue;\n extMap.set(f, '.bin');\n }\n for (const f of taggedBinFiles) extMap.set(f, `.${tag}.bin`);\n\n const files = [...extMap.keys()]\n .filter((file) => file.startsWith(`${name}-`))\n .sort();\n\n return { files, extMap };\n}\n\nexport function createStreamer(basedir: string, tag?: string): Streamer {\n const tagSuffix = tag ? `.${tag}` : '';\n const streamEmitter = new EventEmitter<{\n [key: `chunk:${string}`]: [\n {\n streamName: string;\n chunkData: Uint8Array;\n chunkId: string;\n },\n ];\n [key: `close:${string}`]: [\n {\n streamName: string;\n },\n ];\n }>();\n\n // Track which streams have already been registered for a run (in-memory cache)\n const registeredStreams = new Set<string>();\n\n // Helper to record the runId <> streamId association\n async function registerStreamForRun(\n runId: string,\n streamName: string\n ): Promise<void> {\n assertSafeEntityId('runId', runId);\n assertSafeEntityId('streamName', streamName);\n const cacheKey = `${runId}:${streamName}`;\n if (registeredStreams.has(cacheKey)) {\n return; // Already registered in this session\n }\n\n const runStreamsPath = taggedPath(basedir, 'streams/runs', runId, tag);\n\n // Read existing streams for this run (try tagged first, fall back to untagged)\n const existing = await readJSONWithFallback(\n basedir,\n 'streams/runs',\n runId,\n RunStreamsSchema,\n tag\n );\n const streams = existing?.streams ?? [];\n\n // Add stream if not already present\n if (!streams.includes(streamName)) {\n streams.push(streamName);\n await writeJSON(runStreamsPath, { streams }, { overwrite: true });\n }\n\n registeredStreams.add(cacheKey);\n }\n\n // Helper to convert a chunk to a Buffer\n function toBuffer(chunk: string | Uint8Array): Buffer {\n if (typeof chunk === 'string') {\n return Buffer.from(new TextEncoder().encode(chunk));\n } else if (chunk instanceof Buffer) {\n return chunk;\n } else {\n return Buffer.from(chunk);\n }\n }\n\n return {\n streams: {\n async write(\n _runId: string | Promise<string>,\n name: string,\n chunk: string | Uint8Array\n ) {\n // Generate ULID synchronously BEFORE any await to preserve call order.\n // This ensures that chunks written in sequence maintain their order even\n // when runId is a promise that multiple writes are waiting on.\n const chunkId = `chnk_${monotonicUlid()}`;\n\n // Await runId if it's a promise to ensure proper flushing\n const runId = await _runId;\n\n // Register this stream for the run\n await registerStreamForRun(runId, name);\n\n // Convert chunk to buffer for serialization\n const chunkBuffer = toBuffer(chunk);\n\n const serialized = serializeChunk({\n chunk: chunkBuffer,\n eof: false,\n });\n\n const chunkPath = path.join(\n basedir,\n 'streams',\n 'chunks',\n `${name}-${chunkId}${tagSuffix}.bin`\n );\n\n await write(chunkPath, serialized);\n\n // Emit real-time event with Uint8Array (create copy to prevent ArrayBuffer detachment)\n const chunkData = Uint8Array.from(chunkBuffer);\n\n streamEmitter.emit(`chunk:${name}` as const, {\n streamName: name,\n chunkData,\n chunkId,\n });\n },\n\n async writeMulti(\n _runId: string | Promise<string>,\n name: string,\n chunks: (string | Uint8Array)[]\n ) {\n if (chunks.length === 0) return;\n\n // Generate all ULIDs synchronously BEFORE any await to preserve call order.\n // This ensures that chunks maintain their order even when runId is a promise.\n const chunkIds = chunks.map(() => `chnk_${monotonicUlid()}`);\n\n // Await runId if it's a promise\n const runId = await _runId;\n\n // Register this stream for the run\n await registerStreamForRun(runId, name);\n\n // Prepare chunk data for parallel writes\n const chunkBuffers = chunks.map((chunk) => toBuffer(chunk));\n\n // Write all chunks in parallel for efficiency, but track individual completion\n const writePromises = chunkBuffers.map(async (chunkBuffer, i) => {\n const chunkId = chunkIds[i];\n\n const serialized = serializeChunk({\n chunk: chunkBuffer,\n eof: false,\n });\n\n const chunkPath = path.join(\n basedir,\n 'streams',\n 'chunks',\n `${name}-${chunkId}${tagSuffix}.bin`\n );\n\n await write(chunkPath, serialized);\n\n // Return data needed for event emission\n return {\n chunkId,\n chunkData: Uint8Array.from(chunkBuffer),\n };\n });\n\n // Emit events in order, waiting for each chunk's write to complete\n // This ensures events are emitted in order while writes happen in parallel\n for (const writePromise of writePromises) {\n const { chunkId, chunkData } = await writePromise;\n\n streamEmitter.emit(`chunk:${name}` as const, {\n streamName: name,\n chunkData,\n chunkId,\n });\n }\n },\n\n async close(_runId: string | Promise<string>, name: string) {\n // Generate ULID synchronously BEFORE any await to preserve call order.\n const chunkId = `chnk_${monotonicUlid()}`;\n\n // Await runId if it's a promise to ensure proper flushing\n const runId = await _runId;\n\n // Register this stream for the run (in case write wasn't called)\n await registerStreamForRun(runId, name);\n const chunkPath = path.join(\n basedir,\n 'streams',\n 'chunks',\n `${name}-${chunkId}${tagSuffix}.bin`\n );\n\n await write(\n chunkPath,\n serializeChunk({ chunk: Buffer.from([]), eof: true })\n );\n\n streamEmitter.emit(`close:${name}` as const, { streamName: name });\n },\n\n async list(runId: string) {\n assertSafeEntityId('runId', runId);\n const data = await readJSONWithFallback(\n basedir,\n 'streams/runs',\n runId,\n RunStreamsSchema,\n tag\n );\n return data?.streams ?? [];\n },\n\n async getChunks(\n _runId: string,\n name: string,\n options?: GetChunksOptions\n ): Promise<StreamChunksResponse> {\n const limit = options?.limit ?? 100;\n const chunksDir = path.join(basedir, 'streams', 'chunks');\n const { files: chunkFiles, extMap: fileExtMap } =\n await listChunkFilesForStream(chunksDir, name, tag);\n\n // Decode cursor\n let startIndex = 0;\n if (options?.cursor) {\n try {\n const decoded = JSON.parse(\n Buffer.from(options.cursor, 'base64').toString('utf-8')\n );\n startIndex = decoded.i;\n } catch {\n startIndex = 0;\n }\n }\n\n // Walk from startIndex, reading only the files we need.\n // Files before the cursor are skipped entirely.\n let streamDone = false;\n const resultChunks: { index: number; data: Uint8Array }[] = [];\n let dataIndex = 0; // running count of data (non-EOF) files seen\n\n for (const file of chunkFiles) {\n const ext = fileExtMap.get(file) ?? '.bin';\n const filePath = path.join(chunksDir, `${file}${ext}`);\n\n // Before the cursor: only need to check EOF (1 byte), skip content\n if (dataIndex < startIndex) {\n if (isEofChunk(await readBuffer(filePath))) {\n streamDone = true;\n break;\n }\n dataIndex++;\n continue;\n }\n\n // Collected enough data chunks — peek at the next file for EOF/hasMore\n if (resultChunks.length >= limit) {\n if (isEofChunk(await readBuffer(filePath))) {\n streamDone = true;\n } else {\n // More data files exist beyond this page\n dataIndex++;\n }\n break;\n }\n\n // In the page window: deserialize fully\n const chunk = deserializeChunk(await readBuffer(filePath));\n if (chunk.eof) {\n streamDone = true;\n break;\n }\n resultChunks.push({\n index: dataIndex,\n data: Uint8Array.from(chunk.chunk),\n });\n dataIndex++;\n }\n\n // hasMore = we know there are data files beyond this page\n const hasMore =\n !streamDone && dataIndex > startIndex + resultChunks.length;\n const nextIndex = startIndex + resultChunks.length;\n const nextCursor = hasMore\n ? Buffer.from(JSON.stringify({ i: nextIndex })).toString('base64')\n : null;\n\n return {\n data: resultChunks,\n cursor: nextCursor,\n hasMore,\n done: streamDone,\n };\n },\n\n async getInfo(_runId: string, name: string): Promise<StreamInfoResponse> {\n const chunksDir = path.join(basedir, 'streams', 'chunks');\n const { files: chunkFiles, extMap: fileExtMap } =\n await listChunkFilesForStream(chunksDir, name, tag);\n\n // Only read the first byte of each file to check EOF — no full\n // deserialization needed since we just need a count.\n let streamDone = false;\n let dataCount = 0;\n for (const file of chunkFiles) {\n const ext = fileExtMap.get(file) ?? '.bin';\n if (\n isEofChunk(await readBuffer(path.join(chunksDir, `${file}${ext}`)))\n ) {\n streamDone = true;\n break;\n }\n dataCount++;\n }\n\n return { tailIndex: dataCount - 1, done: streamDone };\n },\n\n async get(_runId: string, name: string, startIndex = 0) {\n const chunksDir = path.join(basedir, 'streams', 'chunks');\n let removeListeners = () => {};\n let pollInterval: ReturnType<typeof setInterval> | null = null;\n\n return new ReadableStream<Uint8Array>({\n async start(controller) {\n // Track chunks delivered via events to prevent duplicates and maintain order.\n const deliveredChunkIds = new Set<string>();\n // Buffer for chunks that arrive via events during disk reading\n const bufferedEventChunks: Array<{\n chunkId: string;\n chunkData: Uint8Array;\n }> = [];\n let isReadingFromDisk = true;\n // Buffer close event if it arrives during disk reading\n let pendingClose = false;\n // Set when the controller is closed; guards against enqueue-after-close\n // in the polling callback when closeListener fires mid-iteration.\n let streamClosed = false;\n\n const chunkListener = (event: {\n streamName: string;\n chunkData: Uint8Array;\n chunkId: string;\n }) => {\n // Skip empty chunks to maintain consistency with disk reading behavior\n if (event.chunkData.byteLength === 0) {\n deliveredChunkIds.add(event.chunkId);\n return;\n }\n\n if (isReadingFromDisk) {\n deliveredChunkIds.add(event.chunkId);\n // Buffer chunks that arrive during disk reading to maintain order\n // Create a copy to prevent ArrayBuffer detachment when enqueued later\n bufferedEventChunks.push({\n chunkId: event.chunkId,\n chunkData: Uint8Array.from(event.chunkData),\n });\n } else if (!deliveredChunkIds.has(event.chunkId)) {\n // Guard against duplicates: polling may have already claimed this\n // chunk between its has() check and readBuffer() yield.\n deliveredChunkIds.add(event.chunkId);\n // After disk reading is complete, deliver chunks immediately\n // Create a copy to prevent ArrayBuffer detachment\n controller.enqueue(Uint8Array.from(event.chunkData));\n }\n };\n\n const closeListener = () => {\n // Buffer close event if disk reading is still in progress\n if (isReadingFromDisk) {\n pendingClose = true;\n return;\n }\n // Remove listeners before closing\n streamClosed = true;\n streamEmitter.off(`chunk:${name}` as const, chunkListener);\n streamEmitter.off(`close:${name}` as const, closeListener);\n if (pollInterval) {\n clearInterval(pollInterval);\n pollInterval = null;\n }\n try {\n controller.close();\n } catch {\n // Ignore if controller is already closed (e.g., from cancel() or EOF)\n }\n };\n removeListeners = closeListener;\n\n // Set up listeners FIRST to avoid missing events\n streamEmitter.on(`chunk:${name}` as const, chunkListener);\n streamEmitter.on(`close:${name}` as const, closeListener);\n\n // Now load existing chunks from disk.\n const { files: chunkFiles, extMap: fileExtMap } =\n await listChunkFilesForStream(chunksDir, name, tag);\n\n // Resolve negative startIndex relative to the number of data chunks\n // (excluding the trailing EOF marker chunk, if present).\n let dataChunkCount = chunkFiles.length;\n if (\n typeof startIndex === 'number' &&\n startIndex < 0 &&\n chunkFiles.length > 0\n ) {\n const lastFile = chunkFiles[chunkFiles.length - 1];\n const lastExt = fileExtMap.get(lastFile) ?? '.bin';\n // Note: this incurs an extra disk read to check the EOF marker.\n // Acceptable since negative startIndex is not a hot path.\n const lastChunk = deserializeChunk(\n await readBuffer(path.join(chunksDir, `${lastFile}${lastExt}`))\n );\n if (lastChunk?.eof === true) {\n dataChunkCount--;\n }\n }\n const resolvedStartIndex =\n typeof startIndex === 'number' && startIndex < 0\n ? Math.max(0, dataChunkCount + startIndex)\n : startIndex;\n\n // Process existing chunks, skipping any already delivered via events\n let isComplete = false;\n for (let i = resolvedStartIndex; i < chunkFiles.length; i++) {\n const file = chunkFiles[i];\n // Extract chunk ID from filename: \"streamName-chunkId\" or \"streamName-chunkId.tag\"\n const rawChunkId = file.substring(name.length + 1);\n // Strip tag suffix (e.g., \"chnk_ULID.vitest-0\" → \"chnk_ULID\")\n const chunkId = tag\n ? rawChunkId.replace(`.${tag}`, '')\n : rawChunkId;\n\n // Skip if already delivered via event\n if (deliveredChunkIds.has(chunkId)) {\n continue;\n }\n\n const ext = fileExtMap.get(file) ?? '.bin';\n const chunk = deserializeChunk(\n await readBuffer(path.join(chunksDir, `${file}${ext}`))\n );\n if (chunk?.eof === true) {\n isComplete = true;\n break;\n }\n // Track as handled so polling doesn't re-deliver\n deliveredChunkIds.add(chunkId);\n if (chunk.chunk.byteLength) {\n // Create a copy to prevent ArrayBuffer detachment\n controller.enqueue(Uint8Array.from(chunk.chunk));\n }\n }\n\n // Finished reading from disk - now deliver buffered event chunks in chronological order\n isReadingFromDisk = false;\n\n // Sort buffered chunks by ULID (chronological order)\n bufferedEventChunks.sort((a, b) =>\n a.chunkId.localeCompare(b.chunkId)\n );\n for (const buffered of bufferedEventChunks) {\n // Create a copy for defense in depth (already copied at storage, but be extra safe)\n controller.enqueue(Uint8Array.from(buffered.chunkData));\n }\n\n if (isComplete) {\n removeListeners();\n try {\n controller.close();\n } catch {\n // Ignore if controller is already closed (e.g., from closeListener event)\n }\n return;\n }\n\n // Process any pending close event that arrived during disk reading\n if (pendingClose) {\n streamEmitter.off(`chunk:${name}` as const, chunkListener);\n streamEmitter.off(`close:${name}` as const, closeListener);\n try {\n controller.close();\n } catch {\n // Ignore if controller is already closed\n }\n return;\n }\n\n // Track pre-startIndex chunks so polling doesn't re-deliver them\n for (\n let i = 0;\n i < resolvedStartIndex && i < chunkFiles.length;\n i++\n ) {\n const file = chunkFiles[i];\n const rawChunkId = file.substring(name.length + 1);\n const chunkId = tag\n ? rawChunkId.replace(`.${tag}`, '')\n : rawChunkId;\n deliveredChunkIds.add(chunkId);\n }\n\n // Start filesystem polling for cross-process streaming support.\n // The EventEmitter only works in-process; when the writer is in a\n // separate process (e.g. e2e test runner ↔ workbench app), polling\n // the shared filesystem is the fallback delivery mechanism.\n let isPolling = false;\n pollInterval = setInterval(async () => {\n if (isPolling) return;\n isPolling = true;\n try {\n const { files: currentFiles, extMap: currentExtMap } =\n await listChunkFilesForStream(chunksDir, name, tag);\n\n for (const file of currentFiles) {\n const rawChunkId = file.substring(name.length + 1);\n const chunkId = tag\n ? rawChunkId.replace(`.${tag}`, '')\n : rawChunkId;\n\n if (deliveredChunkIds.has(chunkId)) continue;\n deliveredChunkIds.add(chunkId);\n\n const ext = currentExtMap.get(file) ?? '.bin';\n const chunk = deserializeChunk(\n await readBuffer(path.join(chunksDir, `${file}${ext}`))\n );\n\n if (chunk?.eof === true) {\n streamClosed = true;\n if (pollInterval) {\n clearInterval(pollInterval);\n pollInterval = null;\n }\n streamEmitter.off(`chunk:${name}` as const, chunkListener);\n streamEmitter.off(`close:${name}` as const, closeListener);\n try {\n controller.close();\n } catch {\n // Ignore if controller is already closed\n }\n return;\n }\n\n // Guard against enqueue-after-close: closeListener may have\n // fired between our readBuffer() yield and this point.\n if (streamClosed) return;\n\n if (chunk.chunk.byteLength) {\n controller.enqueue(Uint8Array.from(chunk.chunk));\n }\n }\n } catch (err: unknown) {\n // Silently ignore transient filesystem errors (ENOENT, EACCES, etc.)\n // Surface unexpected errors so bugs aren't hidden\n if (!(err instanceof Error && 'code' in err)) {\n console.error('[world-local] Unexpected polling error:', err);\n }\n } finally {\n isPolling = false;\n }\n }, 100);\n },\n\n cancel() {\n removeListeners();\n if (pollInterval) {\n clearInterval(pollInterval);\n pollInterval = null;\n }\n },\n });\n },\n },\n };\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@workflow/world-local",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.4",
|
|
4
4
|
"description": "Local development World implementation for Workflow SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -28,9 +28,9 @@
|
|
|
28
28
|
"ulid": "~3.0.1",
|
|
29
29
|
"undici": "7.22.0",
|
|
30
30
|
"zod": "4.3.6",
|
|
31
|
-
"@workflow/errors": "5.0.0-beta.
|
|
32
|
-
"@workflow/
|
|
33
|
-
"@workflow/
|
|
31
|
+
"@workflow/errors": "5.0.0-beta.2",
|
|
32
|
+
"@workflow/world": "5.0.0-beta.2",
|
|
33
|
+
"@workflow/utils": "5.0.0-beta.2"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@opentelemetry/api": "1.9.0",
|