From 4dd00461b2a2661534e42029c160d064b5f0c64b Mon Sep 17 00:00:00 2001 From: Karthik Kalyanaraman Date: Thu, 12 Mar 2026 17:26:45 -0700 Subject: [PATCH 01/13] add stepName with events --- packages/world-vercel/src/events.ts | 4 +++- packages/world/src/events.ts | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/world-vercel/src/events.ts b/packages/world-vercel/src/events.ts index 11e8355e08..b5906fe904 100644 --- a/packages/world-vercel/src/events.ts +++ b/packages/world-vercel/src/events.ts @@ -47,7 +47,8 @@ function filterEventData(event: any, resolveData: 'none' | 'all'): Event { eventDataRef: _eventDataRef, ...rest } = event; - return rest; + const stepName = _eventData?.stepName; + return stepName !== undefined ? { ...rest, stepName } : rest; } return event; } @@ -88,6 +89,7 @@ const EventWithRefsSchema = z.object({ eventData: z.any().optional(), createdAt: z.coerce.date(), specVersion: z.number().default(1), + stepName: z.string().optional(), }); /** diff --git a/packages/world/src/events.ts b/packages/world/src/events.ts index b433c8fb7f..390d6adc6d 100644 --- a/packages/world/src/events.ts +++ b/packages/world/src/events.ts @@ -266,6 +266,7 @@ export const EventSchema = AllEventsSchema.and( eventId: z.string(), createdAt: z.coerce.date(), specVersion: z.number().optional(), + stepName: z.string().optional(), }) ); From 87ba96292203ec955c41f0bbe28c72e72b169158 Mon Sep 17 00:00:00 2001 From: Karthik Kalyanaraman Date: Thu, 12 Mar 2026 17:28:54 -0700 Subject: [PATCH 02/13] add changeset --- .changeset/wise-hornets-fetch.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/wise-hornets-fetch.md diff --git a/.changeset/wise-hornets-fetch.md b/.changeset/wise-hornets-fetch.md new file mode 100644 index 0000000000..50edec9ec4 --- /dev/null +++ b/.changeset/wise-hornets-fetch.md @@ -0,0 +1,6 @@ +--- +"@workflow/world-vercel": patch +"@workflow/world": patch +--- + +Preserve stepName on events when resolveData is 'none' From 86842e736c22fb2c21ec378e4758f29e55497735 Mon Sep 17 00:00:00 2001 From: Karthik Kalyanaraman Date: Thu, 12 Mar 2026 17:36:49 -0700 Subject: [PATCH 03/13] add workflowname to run created --- packages/world-vercel/src/events.ts | 8 +++++++- packages/world/src/events.ts | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/world-vercel/src/events.ts b/packages/world-vercel/src/events.ts index b5906fe904..a8fbc4adc0 100644 --- a/packages/world-vercel/src/events.ts +++ b/packages/world-vercel/src/events.ts @@ -48,7 +48,12 @@ function filterEventData(event: any, resolveData: 'none' | 'all'): Event { ...rest } = event; const stepName = _eventData?.stepName; - return stepName !== undefined ? { ...rest, stepName } : rest; + const workflowName = _eventData?.workflowName; + return { + ...rest, + ...(stepName !== undefined ? { stepName } : {}), + ...(workflowName !== undefined ? { workflowName } : {}), + }; } return event; } @@ -90,6 +95,7 @@ const EventWithRefsSchema = z.object({ createdAt: z.coerce.date(), specVersion: z.number().default(1), stepName: z.string().optional(), + workflowName: z.string().optional(), }); /** diff --git a/packages/world/src/events.ts b/packages/world/src/events.ts index 390d6adc6d..a3c4799d40 100644 --- a/packages/world/src/events.ts +++ b/packages/world/src/events.ts @@ -267,6 +267,7 @@ export const EventSchema = AllEventsSchema.and( createdAt: z.coerce.date(), specVersion: z.number().optional(), stepName: z.string().optional(), + workflowName: z.string().optional(), }) ); From 8113f836c8f776a6ed4816cf362cd1347dd9513f Mon Sep 17 00:00:00 2001 From: Karthik Kalyanaraman Date: Thu, 12 Mar 2026 17:47:41 -0700 Subject: [PATCH 04/13] add postgres migration --- .changeset/wise-hornets-fetch.md | 3 ++- .../migrations/0010_add_event_step_workflow_name.sql | 2 ++ .../src/drizzle/migrations/meta/_journal.json | 7 +++++++ packages/world-postgres/src/drizzle/schema.ts | 2 ++ 4 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 packages/world-postgres/src/drizzle/migrations/0010_add_event_step_workflow_name.sql diff --git a/.changeset/wise-hornets-fetch.md b/.changeset/wise-hornets-fetch.md index 50edec9ec4..601660ab5d 100644 --- a/.changeset/wise-hornets-fetch.md +++ b/.changeset/wise-hornets-fetch.md @@ -1,6 +1,7 @@ --- "@workflow/world-vercel": patch "@workflow/world": patch +"@workflow/world-postgres": patch --- -Preserve stepName on events when resolveData is 'none' +Add stepName and workflowName to Event schema and propagate through all worlds diff --git a/packages/world-postgres/src/drizzle/migrations/0010_add_event_step_workflow_name.sql b/packages/world-postgres/src/drizzle/migrations/0010_add_event_step_workflow_name.sql new file mode 100644 index 0000000000..c372c9d107 --- /dev/null +++ b/packages/world-postgres/src/drizzle/migrations/0010_add_event_step_workflow_name.sql @@ -0,0 +1,2 @@ +ALTER TABLE "workflow"."workflow_events" ADD COLUMN "step_name" varchar;--> statement-breakpoint +ALTER TABLE "workflow"."workflow_events" ADD COLUMN "workflow_name" varchar; diff --git a/packages/world-postgres/src/drizzle/migrations/meta/_journal.json b/packages/world-postgres/src/drizzle/migrations/meta/_journal.json index f4956666fc..a3264e9b4f 100644 --- a/packages/world-postgres/src/drizzle/migrations/meta/_journal.json +++ b/packages/world-postgres/src/drizzle/migrations/meta/_journal.json @@ -71,6 +71,13 @@ "when": 1770500000000, "tag": "0009_add_is_webhook", "breakpoints": true + }, + { + "idx": 10, + "version": "7", + "when": 1771000000000, + "tag": "0010_add_event_step_workflow_name", + "breakpoints": true } ] } diff --git a/packages/world-postgres/src/drizzle/schema.ts b/packages/world-postgres/src/drizzle/schema.ts index f353ef8ca1..e7254bbe1d 100644 --- a/packages/world-postgres/src/drizzle/schema.ts +++ b/packages/world-postgres/src/drizzle/schema.ts @@ -111,6 +111,8 @@ export const events = schema.table( eventDataJson: jsonb('payload'), eventData: Cbor()('payload_cbor'), specVersion: integer('spec_version'), + stepName: varchar('step_name'), + workflowName: varchar('workflow_name'), } satisfies DrizzlishOfType< Cborized >, From 4ecdd3cdc88170d9c95a6f14d78b8d51791bcc97 Mon Sep 17 00:00:00 2001 From: Karthik Kalyanaraman Date: Thu, 12 Mar 2026 17:49:05 -0700 Subject: [PATCH 05/13] update world-local --- .changeset/wise-hornets-fetch.md | 1 + packages/world-local/src/storage/events-storage.ts | 10 ++-------- packages/world-local/src/storage/filters.ts | 8 +++++++- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.changeset/wise-hornets-fetch.md b/.changeset/wise-hornets-fetch.md index 601660ab5d..3f3df5b5e0 100644 --- a/.changeset/wise-hornets-fetch.md +++ b/.changeset/wise-hornets-fetch.md @@ -2,6 +2,7 @@ "@workflow/world-vercel": patch "@workflow/world": patch "@workflow/world-postgres": patch +"@workflow/world-local": patch --- Add stepName and workflowName to Event schema and propagate through all worlds diff --git a/packages/world-local/src/storage/events-storage.ts b/packages/world-local/src/storage/events-storage.ts index 12ab6556de..eb61399212 100644 --- a/packages/world-local/src/storage/events-storage.ts +++ b/packages/world-local/src/storage/events-storage.ts @@ -779,10 +779,7 @@ export function createEventsStorage(basedir: string): Storage['events'] { if (resolveData === 'none') { return { ...result, - data: result.data.map((event) => { - const { eventData: _eventData, ...rest } = event as any; - return rest; - }), + data: result.data.map((event) => filterEventData(event, resolveData)), }; } @@ -810,10 +807,7 @@ export function createEventsStorage(basedir: string): Storage['events'] { if (resolveData === 'none') { return { ...result, - data: result.data.map((event) => { - const { eventData: _eventData, ...rest } = event as any; - return rest; - }), + data: result.data.map((event) => filterEventData(event, resolveData)), }; } diff --git a/packages/world-local/src/storage/filters.ts b/packages/world-local/src/storage/filters.ts index 9685f1a24f..e9fb0873b7 100644 --- a/packages/world-local/src/storage/filters.ts +++ b/packages/world-local/src/storage/filters.ts @@ -74,7 +74,13 @@ export function filterEventData( ): Event { if (resolveData === 'none') { const { eventData: _eventData, ...rest } = event as any; - return rest; + const stepName = _eventData?.stepName; + const workflowName = _eventData?.workflowName; + return { + ...rest, + ...(stepName !== undefined ? { stepName } : {}), + ...(workflowName !== undefined ? { workflowName } : {}), + }; } return event; } From 895bcdedb384038995dd0d11de69275c43abc385 Mon Sep 17 00:00:00 2001 From: Karthik Kalyanaraman Date: Thu, 12 Mar 2026 17:53:48 -0700 Subject: [PATCH 06/13] update world-local --- .../migrations/0010_add_event_step_workflow_name.sql | 2 -- .../src/drizzle/migrations/meta/_journal.json | 7 ------- packages/world-postgres/src/drizzle/schema.ts | 7 ++++--- packages/world-postgres/src/storage.ts | 11 ++++++++--- 4 files changed, 12 insertions(+), 15 deletions(-) delete mode 100644 packages/world-postgres/src/drizzle/migrations/0010_add_event_step_workflow_name.sql diff --git a/packages/world-postgres/src/drizzle/migrations/0010_add_event_step_workflow_name.sql b/packages/world-postgres/src/drizzle/migrations/0010_add_event_step_workflow_name.sql deleted file mode 100644 index c372c9d107..0000000000 --- a/packages/world-postgres/src/drizzle/migrations/0010_add_event_step_workflow_name.sql +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE "workflow"."workflow_events" ADD COLUMN "step_name" varchar;--> statement-breakpoint -ALTER TABLE "workflow"."workflow_events" ADD COLUMN "workflow_name" varchar; diff --git a/packages/world-postgres/src/drizzle/migrations/meta/_journal.json b/packages/world-postgres/src/drizzle/migrations/meta/_journal.json index a3264e9b4f..f4956666fc 100644 --- a/packages/world-postgres/src/drizzle/migrations/meta/_journal.json +++ b/packages/world-postgres/src/drizzle/migrations/meta/_journal.json @@ -71,13 +71,6 @@ "when": 1770500000000, "tag": "0009_add_is_webhook", "breakpoints": true - }, - { - "idx": 10, - "version": "7", - "when": 1771000000000, - "tag": "0010_add_event_step_workflow_name", - "breakpoints": true } ] } diff --git a/packages/world-postgres/src/drizzle/schema.ts b/packages/world-postgres/src/drizzle/schema.ts index e7254bbe1d..f0b20524c3 100644 --- a/packages/world-postgres/src/drizzle/schema.ts +++ b/packages/world-postgres/src/drizzle/schema.ts @@ -111,10 +111,11 @@ export const events = schema.table( eventDataJson: jsonb('payload'), eventData: Cbor()('payload_cbor'), specVersion: integer('spec_version'), - stepName: varchar('step_name'), - workflowName: varchar('workflow_name'), } satisfies DrizzlishOfType< - Cborized + Cborized< + Omit & { eventData?: undefined }, + 'eventData' + > >, (tb) => [index().on(tb.runId), index().on(tb.correlationId)] ); diff --git a/packages/world-postgres/src/storage.ts b/packages/world-postgres/src/storage.ts index c8eb7b9626..e85081a4cb 100644 --- a/packages/world-postgres/src/storage.ts +++ b/packages/world-postgres/src/storage.ts @@ -1336,9 +1336,14 @@ function filterHookData(hook: Hook, resolveData: ResolveData): Hook { function filterEventData(event: Event, resolveData: ResolveData): Event { if (resolveData === 'none' && 'eventData' in event) { - const { eventData: _, ...rest } = event; - - return rest as Event; + const { eventData: _eventData, ...rest } = event; + const stepName = (_eventData as any)?.stepName; + const workflowName = (_eventData as any)?.workflowName; + return { + ...rest, + ...(stepName !== undefined ? { stepName } : {}), + ...(workflowName !== undefined ? { workflowName } : {}), + } as Event; } return event; } From 40cbfa0a550b89d1bac2b9f24754c4cb82854e77 Mon Sep 17 00:00:00 2001 From: Karthik Kalyanaraman Date: Thu, 12 Mar 2026 18:27:48 -0700 Subject: [PATCH 07/13] preserve the fields in the original shape --- .changeset/wise-hornets-fetch.md | 3 +-- packages/world-local/src/storage/filters.ts | 12 ++++++++---- packages/world-postgres/src/drizzle/schema.ts | 5 +---- packages/world-postgres/src/storage.ts | 12 ++++++++---- packages/world-vercel/src/events.ts | 14 ++++++++------ packages/world/src/events.ts | 2 -- 6 files changed, 26 insertions(+), 22 deletions(-) diff --git a/.changeset/wise-hornets-fetch.md b/.changeset/wise-hornets-fetch.md index 3f3df5b5e0..7c11cacf09 100644 --- a/.changeset/wise-hornets-fetch.md +++ b/.changeset/wise-hornets-fetch.md @@ -1,8 +1,7 @@ --- "@workflow/world-vercel": patch -"@workflow/world": patch "@workflow/world-postgres": patch "@workflow/world-local": patch --- -Add stepName and workflowName to Event schema and propagate through all worlds +Preserve stepName and workflowName in eventData when resolveData is 'none' diff --git a/packages/world-local/src/storage/filters.ts b/packages/world-local/src/storage/filters.ts index e9fb0873b7..c19d2a4eef 100644 --- a/packages/world-local/src/storage/filters.ts +++ b/packages/world-local/src/storage/filters.ts @@ -74,12 +74,16 @@ export function filterEventData( ): Event { if (resolveData === 'none') { const { eventData: _eventData, ...rest } = event as any; - const stepName = _eventData?.stepName; - const workflowName = _eventData?.workflowName; + const minimalEventData: Record = {}; + if (_eventData?.stepName !== undefined) + minimalEventData.stepName = _eventData.stepName; + if (_eventData?.workflowName !== undefined) + minimalEventData.workflowName = _eventData.workflowName; return { ...rest, - ...(stepName !== undefined ? { stepName } : {}), - ...(workflowName !== undefined ? { workflowName } : {}), + ...(Object.keys(minimalEventData).length > 0 + ? { eventData: minimalEventData } + : {}), }; } return event; diff --git a/packages/world-postgres/src/drizzle/schema.ts b/packages/world-postgres/src/drizzle/schema.ts index f0b20524c3..f353ef8ca1 100644 --- a/packages/world-postgres/src/drizzle/schema.ts +++ b/packages/world-postgres/src/drizzle/schema.ts @@ -112,10 +112,7 @@ export const events = schema.table( eventData: Cbor()('payload_cbor'), specVersion: integer('spec_version'), } satisfies DrizzlishOfType< - Cborized< - Omit & { eventData?: undefined }, - 'eventData' - > + Cborized >, (tb) => [index().on(tb.runId), index().on(tb.correlationId)] ); diff --git a/packages/world-postgres/src/storage.ts b/packages/world-postgres/src/storage.ts index e85081a4cb..b52d4ec52c 100644 --- a/packages/world-postgres/src/storage.ts +++ b/packages/world-postgres/src/storage.ts @@ -1337,12 +1337,16 @@ function filterHookData(hook: Hook, resolveData: ResolveData): Hook { function filterEventData(event: Event, resolveData: ResolveData): Event { if (resolveData === 'none' && 'eventData' in event) { const { eventData: _eventData, ...rest } = event; - const stepName = (_eventData as any)?.stepName; - const workflowName = (_eventData as any)?.workflowName; + const minimalEventData: Record = {}; + if ((_eventData as any)?.stepName !== undefined) + minimalEventData.stepName = (_eventData as any).stepName; + if ((_eventData as any)?.workflowName !== undefined) + minimalEventData.workflowName = (_eventData as any).workflowName; return { ...rest, - ...(stepName !== undefined ? { stepName } : {}), - ...(workflowName !== undefined ? { workflowName } : {}), + ...(Object.keys(minimalEventData).length > 0 + ? { eventData: minimalEventData } + : {}), } as Event; } return event; diff --git a/packages/world-vercel/src/events.ts b/packages/world-vercel/src/events.ts index a8fbc4adc0..028992d4e6 100644 --- a/packages/world-vercel/src/events.ts +++ b/packages/world-vercel/src/events.ts @@ -47,12 +47,16 @@ function filterEventData(event: any, resolveData: 'none' | 'all'): Event { eventDataRef: _eventDataRef, ...rest } = event; - const stepName = _eventData?.stepName; - const workflowName = _eventData?.workflowName; + const minimalEventData: Record = {}; + if (_eventData?.stepName !== undefined) + minimalEventData.stepName = _eventData.stepName; + if (_eventData?.workflowName !== undefined) + minimalEventData.workflowName = _eventData.workflowName; return { ...rest, - ...(stepName !== undefined ? { stepName } : {}), - ...(workflowName !== undefined ? { workflowName } : {}), + ...(Object.keys(minimalEventData).length > 0 + ? { eventData: minimalEventData } + : {}), }; } return event; @@ -94,8 +98,6 @@ const EventWithRefsSchema = z.object({ eventData: z.any().optional(), createdAt: z.coerce.date(), specVersion: z.number().default(1), - stepName: z.string().optional(), - workflowName: z.string().optional(), }); /** diff --git a/packages/world/src/events.ts b/packages/world/src/events.ts index a3c4799d40..b433c8fb7f 100644 --- a/packages/world/src/events.ts +++ b/packages/world/src/events.ts @@ -266,8 +266,6 @@ export const EventSchema = AllEventsSchema.and( eventId: z.string(), createdAt: z.coerce.date(), specVersion: z.number().optional(), - stepName: z.string().optional(), - workflowName: z.string().optional(), }) ); From 6f66e2ce74ca24ecbd254b0288d8063217de8f1b Mon Sep 17 00:00:00 2001 From: Karthik Kalyanaraman Date: Thu, 12 Mar 2026 18:37:13 -0700 Subject: [PATCH 08/13] fix tests --- packages/world-local/src/storage.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/world-local/src/storage.test.ts b/packages/world-local/src/storage.test.ts index b6f5b079c0..df1d9cdffb 100644 --- a/packages/world-local/src/storage.test.ts +++ b/packages/world-local/src/storage.test.ts @@ -1024,7 +1024,10 @@ describe('Storage', () => { // step_created + step_completed = 2 events expect(result.data).toHaveLength(2); - expect((result.data[0] as any).eventData).toBeUndefined(); + // step_created preserves stepName in minimal eventData + expect((result.data[0] as any).eventData).toEqual({ + stepName: 'test-step', + }); expect((result.data[1] as any).eventData).toBeUndefined(); expect(result.data[0].correlationId).toBe(correlationId); }); From cacaa7f36d8143c6668a5fae23bcc038c08cabf4 Mon Sep 17 00:00:00 2001 From: Karthik Kalyanaraman Date: Fri, 13 Mar 2026 14:30:12 -0700 Subject: [PATCH 09/13] strip only ref/payload fields --- .changeset/wise-hornets-fetch.md | 2 +- packages/world-local/src/storage.test.ts | 2 +- .../world-local/src/storage/events-storage.ts | 18 +++-- packages/world-local/src/storage/filters.ts | 59 ++++++++++----- packages/world-local/src/storage/legacy.ts | 4 +- packages/world-postgres/src/storage.ts | 68 +++++++++++------ packages/world-vercel/src/events.ts | 73 ++++++++++++------- 7 files changed, 150 insertions(+), 76 deletions(-) diff --git a/.changeset/wise-hornets-fetch.md b/.changeset/wise-hornets-fetch.md index 7c11cacf09..043d332799 100644 --- a/.changeset/wise-hornets-fetch.md +++ b/.changeset/wise-hornets-fetch.md @@ -4,4 +4,4 @@ "@workflow/world-local": patch --- -Preserve stepName and workflowName in eventData when resolveData is 'none' +Strip only ref/payload fields from eventData when resolveData is 'none', preserving all other metadata diff --git a/packages/world-local/src/storage.test.ts b/packages/world-local/src/storage.test.ts index df1d9cdffb..767f654e1e 100644 --- a/packages/world-local/src/storage.test.ts +++ b/packages/world-local/src/storage.test.ts @@ -1024,7 +1024,7 @@ describe('Storage', () => { // step_created + step_completed = 2 events expect(result.data).toHaveLength(2); - // step_created preserves stepName in minimal eventData + // step_created: ref field 'input' stripped, metadata like stepName preserved expect((result.data[0] as any).eventData).toEqual({ stepName: 'test-step', }); diff --git a/packages/world-local/src/storage/events-storage.ts b/packages/world-local/src/storage/events-storage.ts index eb61399212..aa7266574f 100644 --- a/packages/world-local/src/storage/events-storage.ts +++ b/packages/world-local/src/storage/events-storage.ts @@ -30,7 +30,7 @@ import { writeExclusive, writeJSON, } from '../fs.js'; -import { filterEventData } from './filters.js'; +import { stripEventDataRefs } from './filters.js'; import { getObjectCreatedAt, hashToken, monotonicUlid } from './helpers.js'; import { deleteAllHooksForRun } from './hooks-storage.js'; import { handleLegacyEvent } from './legacy.js'; @@ -168,7 +168,7 @@ export function createEventsStorage(basedir: string): Storage['events'] { const resolveData = params?.resolveData ?? DEFAULT_RESOLVE_DATA_OPTION; return { - event: filterEventData(event, resolveData), + event: stripEventDataRefs(event, resolveData), run: currentRun, }; } @@ -621,7 +621,7 @@ export function createEventsStorage(basedir: string): Storage['events'] { const resolveData = params?.resolveData ?? DEFAULT_RESOLVE_DATA_OPTION; - const filteredEvent = filterEventData(conflictEvent, resolveData); + const filteredEvent = stripEventDataRefs(conflictEvent, resolveData); // Return EventResult with conflict event (no hook entity created) return { @@ -736,7 +736,7 @@ export function createEventsStorage(basedir: string): Storage['events'] { await writeJSON(eventPath, event); const resolveData = params?.resolveData ?? DEFAULT_RESOLVE_DATA_OPTION; - const filteredEvent = filterEventData(event, resolveData); + const filteredEvent = stripEventDataRefs(event, resolveData); // Return EventResult with event and any created/updated entity return { @@ -756,7 +756,7 @@ export function createEventsStorage(basedir: string): Storage['events'] { throw new Error(`Event ${eventId} in run ${runId} not found`); } const resolveData = params?.resolveData ?? DEFAULT_RESOLVE_DATA_OPTION; - return filterEventData(event, resolveData); + return stripEventDataRefs(event, resolveData); }, async list(params) { @@ -779,7 +779,9 @@ export function createEventsStorage(basedir: string): Storage['events'] { if (resolveData === 'none') { return { ...result, - data: result.data.map((event) => filterEventData(event, resolveData)), + data: result.data.map((event) => + stripEventDataRefs(event, resolveData) + ), }; } @@ -807,7 +809,9 @@ export function createEventsStorage(basedir: string): Storage['events'] { if (resolveData === 'none') { return { ...result, - data: result.data.map((event) => filterEventData(event, resolveData)), + data: result.data.map((event) => + stripEventDataRefs(event, resolveData) + ), }; } diff --git a/packages/world-local/src/storage/filters.ts b/packages/world-local/src/storage/filters.ts index c19d2a4eef..45a253bd65 100644 --- a/packages/world-local/src/storage/filters.ts +++ b/packages/world-local/src/storage/filters.ts @@ -65,28 +65,53 @@ export function filterStepData( } /** - * Filter event data based on resolveData setting. - * When resolveData is 'none', strips eventData to reduce payload size. + * Fields within eventData that hold ref/payload data per event type. + * When resolveData is 'none', only these fields are stripped — all other + * metadata (stepName, workflowName, etc.) is preserved. */ -export function filterEventData( +const EVENT_DATA_REF_FIELDS: Record = { + run_created: ['input'], + run_completed: ['output'], + run_failed: ['error'], + step_created: ['input'], + step_completed: ['result'], + step_failed: ['error'], + step_retrying: ['error'], + hook_created: ['metadata'], + hook_received: ['payload'], +}; + +/** + * Strip ref/payload fields from eventData based on resolveData setting. + * When resolveData is 'none', removes only large data fields (refs) from + * eventData while preserving metadata like stepName, workflowName, etc. + */ +export function stripEventDataRefs( event: Event, resolveData: 'none' | 'all' ): Event { - if (resolveData === 'none') { - const { eventData: _eventData, ...rest } = event as any; - const minimalEventData: Record = {}; - if (_eventData?.stepName !== undefined) - minimalEventData.stepName = _eventData.stepName; - if (_eventData?.workflowName !== undefined) - minimalEventData.workflowName = _eventData.workflowName; - return { - ...rest, - ...(Object.keys(minimalEventData).length > 0 - ? { eventData: minimalEventData } - : {}), - }; + if (resolveData !== 'none') return event; + if (!('eventData' in event)) return event; + + const eventData = (event as any).eventData; + if (!eventData || typeof eventData !== 'object') { + const { eventData: _, ...rest } = event as any; + return rest; } - return event; + + const refFields = EVENT_DATA_REF_FIELDS[event.eventType]; + if (!refFields || refFields.length === 0) return event; + + const stripped = { ...eventData }; + for (const field of refFields) { + delete stripped[field]; + } + + const { eventData: _, ...rest } = event as any; + return { + ...rest, + ...(Object.keys(stripped).length > 0 ? { eventData: stripped } : {}), + }; } /** diff --git a/packages/world-local/src/storage/legacy.ts b/packages/world-local/src/storage/legacy.ts index e72a135f43..0ca6004040 100644 --- a/packages/world-local/src/storage/legacy.ts +++ b/packages/world-local/src/storage/legacy.ts @@ -3,7 +3,7 @@ import type { Event, EventResult, WorkflowRun } from '@workflow/world'; import { SPEC_VERSION_CURRENT } from '@workflow/world'; import { DEFAULT_RESOLVE_DATA_OPTION } from '../config.js'; import { writeJSON } from '../fs.js'; -import { filterEventData, filterRunData } from './filters.js'; +import { filterRunData, stripEventDataRefs } from './filters.js'; import { monotonicUlid } from './helpers.js'; import { deleteAllHooksForRun } from './hooks-storage.js'; @@ -72,7 +72,7 @@ export async function handleLegacyEvent( const compositeKey = `${runId}-${eventId}`; const eventPath = path.join(basedir, 'events', `${compositeKey}.json`); await writeJSON(eventPath, event); - return { event: filterEventData(event, resolveData) }; + return { event: stripEventDataRefs(event, resolveData) }; } default: diff --git a/packages/world-postgres/src/storage.ts b/packages/world-postgres/src/storage.ts index b52d4ec52c..f7ddcae714 100644 --- a/packages/world-postgres/src/storage.ts +++ b/packages/world-postgres/src/storage.ts @@ -243,7 +243,7 @@ async function handleLegacyEventPostgres( runId, eventId, }); - return { event: filterEventData(event, resolveData) }; + return { event: stripEventDataRefs(event, resolveData) }; } default: @@ -428,7 +428,7 @@ export function createEventsStorage(drizzle: Drizzle): Storage['events'] { const parsed = EventSchema.parse(result); const resolveData = params?.resolveData ?? 'all'; return { - event: filterEventData(parsed, resolveData), + event: stripEventDataRefs(parsed, resolveData), run: fullRun ? deserializeRunError(compact(fullRun)) : undefined, }; } @@ -915,7 +915,7 @@ export function createEventsStorage(drizzle: Drizzle): Storage['events'] { const parsedConflict = EventSchema.parse(conflictResult); const resolveData = params?.resolveData ?? 'all'; return { - event: filterEventData(parsedConflict, resolveData), + event: stripEventDataRefs(parsedConflict, resolveData), run, step, hook: undefined, @@ -1055,7 +1055,7 @@ export function createEventsStorage(drizzle: Drizzle): Storage['events'] { const parsed = EventSchema.parse(result); const resolveData = params?.resolveData ?? 'all'; return { - event: filterEventData(parsed, resolveData), + event: stripEventDataRefs(parsed, resolveData), run, step, hook, @@ -1082,7 +1082,7 @@ export function createEventsStorage(drizzle: Drizzle): Storage['events'] { value.eventData ||= value.eventDataJson; const parsed = EventSchema.parse(compact(value)); const resolveData = params?.resolveData ?? 'all'; - return filterEventData(parsed, resolveData); + return stripEventDataRefs(parsed, resolveData); }, async list(params: ListEventsParams): Promise> { const limit = params?.pagination?.limit ?? 100; @@ -1112,7 +1112,7 @@ export function createEventsStorage(drizzle: Drizzle): Storage['events'] { data: values.map((v) => { v.eventData ||= v.eventDataJson; const parsed = EventSchema.parse(compact(v)); - return filterEventData(parsed, resolveData); + return stripEventDataRefs(parsed, resolveData); }), cursor: values.at(-1)?.eventId ?? null, hasMore: all.length > limit, @@ -1146,7 +1146,7 @@ export function createEventsStorage(drizzle: Drizzle): Storage['events'] { data: values.map((v) => { v.eventData ||= v.eventDataJson; const parsed = EventSchema.parse(compact(v)); - return filterEventData(parsed, resolveData); + return stripEventDataRefs(parsed, resolveData); }), cursor: values.at(-1)?.eventId ?? null, hasMore: all.length > limit, @@ -1334,20 +1334,44 @@ function filterHookData(hook: Hook, resolveData: ResolveData): Hook { return hook; } -function filterEventData(event: Event, resolveData: ResolveData): Event { - if (resolveData === 'none' && 'eventData' in event) { - const { eventData: _eventData, ...rest } = event; - const minimalEventData: Record = {}; - if ((_eventData as any)?.stepName !== undefined) - minimalEventData.stepName = (_eventData as any).stepName; - if ((_eventData as any)?.workflowName !== undefined) - minimalEventData.workflowName = (_eventData as any).workflowName; - return { - ...rest, - ...(Object.keys(minimalEventData).length > 0 - ? { eventData: minimalEventData } - : {}), - } as Event; +/** + * Fields within eventData that hold ref/payload data per event type. + * When resolveData is 'none', only these fields are stripped — all other + * metadata (stepName, workflowName, etc.) is preserved. + */ +const EVENT_DATA_REF_FIELDS: Record = { + run_created: ['input'], + run_completed: ['output'], + run_failed: ['error'], + step_created: ['input'], + step_completed: ['result'], + step_failed: ['error'], + step_retrying: ['error'], + hook_created: ['metadata'], + hook_received: ['payload'], +}; + +function stripEventDataRefs(event: Event, resolveData: ResolveData): Event { + if (resolveData !== 'none') return event; + if (!('eventData' in event)) return event; + + const eventData = event.eventData; + if (!eventData || typeof eventData !== 'object') { + const { eventData: _, ...rest } = event; + return rest as Event; + } + + const refFields = EVENT_DATA_REF_FIELDS[event.eventType]; + if (!refFields || refFields.length === 0) return event; + + const stripped = { ...(eventData as Record) }; + for (const field of refFields) { + delete stripped[field]; } - return event; + + const { eventData: _, ...rest } = event; + return { + ...rest, + ...(Object.keys(stripped).length > 0 ? { eventData: stripped } : {}), + } as Event; } diff --git a/packages/world-vercel/src/events.ts b/packages/world-vercel/src/events.ts index 028992d4e6..6166cf091d 100644 --- a/packages/world-vercel/src/events.ts +++ b/packages/world-vercel/src/events.ts @@ -37,29 +37,50 @@ import { makeRequest, } from './utils.js'; -// Helper to filter event data based on resolveData setting. -// Strips both eventData and eventDataRef since the server always returns -// lazy refs now, and callers with resolveData='none' should not see either. -function filterEventData(event: any, resolveData: 'none' | 'all'): Event { - if (resolveData === 'none') { - const { - eventData: _eventData, - eventDataRef: _eventDataRef, - ...rest - } = event; - const minimalEventData: Record = {}; - if (_eventData?.stepName !== undefined) - minimalEventData.stepName = _eventData.stepName; - if (_eventData?.workflowName !== undefined) - minimalEventData.workflowName = _eventData.workflowName; - return { - ...rest, - ...(Object.keys(minimalEventData).length > 0 - ? { eventData: minimalEventData } - : {}), - }; +/** + * Fields within eventData that hold ref/payload data per event type. + * When resolveData is 'none', only these fields are stripped — all other + * metadata (stepName, workflowName, etc.) is preserved. + */ +const EVENT_DATA_REF_FIELDS: Record = { + run_created: ['input'], + run_completed: ['output'], + run_failed: ['error'], + step_created: ['input'], + step_completed: ['result'], + step_failed: ['error'], + step_retrying: ['error'], + hook_created: ['metadata'], + hook_received: ['payload'], +}; + +// Strip ref/payload fields from eventData based on resolveData setting. +// Also strips the legacy eventDataRef field since the server always returns +// lazy refs now, and callers with resolveData='none' should not see them. +function stripEventDataRefs(event: any, resolveData: 'none' | 'all'): Event { + if (resolveData !== 'none') return event; + + const { eventDataRef: _eventDataRef, ...withoutLegacyRef } = event; + + const eventData = withoutLegacyRef.eventData; + if (!eventData || typeof eventData !== 'object') { + const { eventData: _, ...rest } = withoutLegacyRef; + return rest; + } + + const refFields = EVENT_DATA_REF_FIELDS[withoutLegacyRef.eventType as string]; + if (!refFields || refFields.length === 0) return withoutLegacyRef; + + const stripped = { ...eventData }; + for (const field of refFields) { + delete stripped[field]; } - return event; + + const { eventData: _, ...rest } = withoutLegacyRef; + return { + ...rest, + ...(Object.keys(stripped).length > 0 ? { eventData: stripped } : {}), + }; } // Schema for EventResult wire format returned by events.create. @@ -281,7 +302,7 @@ export async function getEvent( schema: (resolveData === 'none' ? EventWithRefsSchema : EventSchema) as any, }); - return filterEventData(event as any, resolveData); + return stripEventDataRefs(event as any, resolveData); } export async function getWorkflowRunEvents( @@ -371,7 +392,7 @@ export async function getWorkflowRunEvents( return { ...response, data: response.data.map((event: any) => - filterEventData(event, resolveData) + stripEventDataRefs(event, resolveData) ), }; } @@ -432,7 +453,7 @@ export async function createWorkflowRunEvent( }); return { - event: filterEventData(wireResult.event, resolveData), + event: stripEventDataRefs(wireResult.event, resolveData), run: wireResult.run, step: wireResult.step ? deserializeStep(wireResult.step) : undefined, hook: wireResult.hook, @@ -452,7 +473,7 @@ export async function createWorkflowRunEvent( // undefined (lazy ref mode), so deserializeError normalizes it into the // StructuredError shape expected by WorkflowRun consumers. return { - event: filterEventData(wireResult.event, resolveData), + event: stripEventDataRefs(wireResult.event, resolveData), run: wireResult.run ? deserializeError(wireResult.run) : undefined, From 1bf88fdf127ec83ac98dc511a87429259f2852d3 Mon Sep 17 00:00:00 2001 From: Karthik Kalyanaraman Date: Fri, 13 Mar 2026 17:02:17 -0700 Subject: [PATCH 10/13] stub the helper into world --- .changeset/wise-hornets-fetch.md | 1 + packages/world-local/src/storage/filters.ts | 53 +----------------- packages/world-postgres/src/storage.ts | 43 +-------------- packages/world-vercel/src/events.ts | 59 +++++---------------- packages/world/src/events.ts | 50 +++++++++++++++++ packages/world/src/index.ts | 2 + 6 files changed, 69 insertions(+), 139 deletions(-) diff --git a/.changeset/wise-hornets-fetch.md b/.changeset/wise-hornets-fetch.md index 043d332799..72594978e6 100644 --- a/.changeset/wise-hornets-fetch.md +++ b/.changeset/wise-hornets-fetch.md @@ -1,4 +1,5 @@ --- +"@workflow/world": patch "@workflow/world-vercel": patch "@workflow/world-postgres": patch "@workflow/world-local": patch diff --git a/packages/world-local/src/storage/filters.ts b/packages/world-local/src/storage/filters.ts index 45a253bd65..49ea5fe020 100644 --- a/packages/world-local/src/storage/filters.ts +++ b/packages/world-local/src/storage/filters.ts @@ -1,5 +1,4 @@ import type { - Event, Hook, Step, StepWithoutData, @@ -7,6 +6,8 @@ import type { WorkflowRunWithoutData, } from '@workflow/world'; +export { stripEventDataRefs } from '@workflow/world'; + /** * Filter run data based on resolveData setting. * When resolveData is 'none', strips input/output to reduce payload size. @@ -64,56 +65,6 @@ export function filterStepData( return step; } -/** - * Fields within eventData that hold ref/payload data per event type. - * When resolveData is 'none', only these fields are stripped — all other - * metadata (stepName, workflowName, etc.) is preserved. - */ -const EVENT_DATA_REF_FIELDS: Record = { - run_created: ['input'], - run_completed: ['output'], - run_failed: ['error'], - step_created: ['input'], - step_completed: ['result'], - step_failed: ['error'], - step_retrying: ['error'], - hook_created: ['metadata'], - hook_received: ['payload'], -}; - -/** - * Strip ref/payload fields from eventData based on resolveData setting. - * When resolveData is 'none', removes only large data fields (refs) from - * eventData while preserving metadata like stepName, workflowName, etc. - */ -export function stripEventDataRefs( - event: Event, - resolveData: 'none' | 'all' -): Event { - if (resolveData !== 'none') return event; - if (!('eventData' in event)) return event; - - const eventData = (event as any).eventData; - if (!eventData || typeof eventData !== 'object') { - const { eventData: _, ...rest } = event as any; - return rest; - } - - const refFields = EVENT_DATA_REF_FIELDS[event.eventType]; - if (!refFields || refFields.length === 0) return event; - - const stripped = { ...eventData }; - for (const field of refFields) { - delete stripped[field]; - } - - const { eventData: _, ...rest } = event as any; - return { - ...rest, - ...(Object.keys(stripped).length > 0 ? { eventData: stripped } : {}), - }; -} - /** * Filter hook data based on resolveData setting. * When resolveData is 'none', strips metadata to reduce payload size. diff --git a/packages/world-postgres/src/storage.ts b/packages/world-postgres/src/storage.ts index f7ddcae714..7649a21081 100644 --- a/packages/world-postgres/src/storage.ts +++ b/packages/world-postgres/src/storage.ts @@ -27,6 +27,7 @@ import { requiresNewerWorld, SPEC_VERSION_CURRENT, StepSchema, + stripEventDataRefs, validateUlidTimestamp, WorkflowRunSchema, } from '@workflow/world'; @@ -1333,45 +1334,3 @@ function filterHookData(hook: Hook, resolveData: ResolveData): Hook { } return hook; } - -/** - * Fields within eventData that hold ref/payload data per event type. - * When resolveData is 'none', only these fields are stripped — all other - * metadata (stepName, workflowName, etc.) is preserved. - */ -const EVENT_DATA_REF_FIELDS: Record = { - run_created: ['input'], - run_completed: ['output'], - run_failed: ['error'], - step_created: ['input'], - step_completed: ['result'], - step_failed: ['error'], - step_retrying: ['error'], - hook_created: ['metadata'], - hook_received: ['payload'], -}; - -function stripEventDataRefs(event: Event, resolveData: ResolveData): Event { - if (resolveData !== 'none') return event; - if (!('eventData' in event)) return event; - - const eventData = event.eventData; - if (!eventData || typeof eventData !== 'object') { - const { eventData: _, ...rest } = event; - return rest as Event; - } - - const refFields = EVENT_DATA_REF_FIELDS[event.eventType]; - if (!refFields || refFields.length === 0) return event; - - const stripped = { ...(eventData as Record) }; - for (const field of refFields) { - delete stripped[field]; - } - - const { eventData: _, ...rest } = event; - return { - ...rest, - ...(Object.keys(stripped).length > 0 ? { eventData: stripped } : {}), - } as Event; -} diff --git a/packages/world-vercel/src/events.ts b/packages/world-vercel/src/events.ts index 6166cf091d..b2c15ab58b 100644 --- a/packages/world-vercel/src/events.ts +++ b/packages/world-vercel/src/events.ts @@ -12,6 +12,7 @@ import { type ListEventsParams, type PaginatedResponse, PaginatedResponseSchema, + stripEventDataRefs, validateUlidTimestamp, type WorkflowRun, WorkflowRunSchema, @@ -37,50 +38,16 @@ import { makeRequest, } from './utils.js'; -/** - * Fields within eventData that hold ref/payload data per event type. - * When resolveData is 'none', only these fields are stripped — all other - * metadata (stepName, workflowName, etc.) is preserved. - */ -const EVENT_DATA_REF_FIELDS: Record = { - run_created: ['input'], - run_completed: ['output'], - run_failed: ['error'], - step_created: ['input'], - step_completed: ['result'], - step_failed: ['error'], - step_retrying: ['error'], - hook_created: ['metadata'], - hook_received: ['payload'], -}; - -// Strip ref/payload fields from eventData based on resolveData setting. -// Also strips the legacy eventDataRef field since the server always returns -// lazy refs now, and callers with resolveData='none' should not see them. -function stripEventDataRefs(event: any, resolveData: 'none' | 'all'): Event { +// Wraps stripEventDataRefs to also strip the legacy eventDataRef field, +// since the server always returns lazy refs and callers with +// resolveData='none' should not see them. +function stripEventAndLegacyRefs( + event: any, + resolveData: 'none' | 'all' +): Event { if (resolveData !== 'none') return event; - const { eventDataRef: _eventDataRef, ...withoutLegacyRef } = event; - - const eventData = withoutLegacyRef.eventData; - if (!eventData || typeof eventData !== 'object') { - const { eventData: _, ...rest } = withoutLegacyRef; - return rest; - } - - const refFields = EVENT_DATA_REF_FIELDS[withoutLegacyRef.eventType as string]; - if (!refFields || refFields.length === 0) return withoutLegacyRef; - - const stripped = { ...eventData }; - for (const field of refFields) { - delete stripped[field]; - } - - const { eventData: _, ...rest } = withoutLegacyRef; - return { - ...rest, - ...(Object.keys(stripped).length > 0 ? { eventData: stripped } : {}), - }; + return stripEventDataRefs(withoutLegacyRef, resolveData); } // Schema for EventResult wire format returned by events.create. @@ -302,7 +269,7 @@ export async function getEvent( schema: (resolveData === 'none' ? EventWithRefsSchema : EventSchema) as any, }); - return stripEventDataRefs(event as any, resolveData); + return stripEventAndLegacyRefs(event as any, resolveData); } export async function getWorkflowRunEvents( @@ -392,7 +359,7 @@ export async function getWorkflowRunEvents( return { ...response, data: response.data.map((event: any) => - stripEventDataRefs(event, resolveData) + stripEventAndLegacyRefs(event, resolveData) ), }; } @@ -453,7 +420,7 @@ export async function createWorkflowRunEvent( }); return { - event: stripEventDataRefs(wireResult.event, resolveData), + event: stripEventAndLegacyRefs(wireResult.event, resolveData), run: wireResult.run, step: wireResult.step ? deserializeStep(wireResult.step) : undefined, hook: wireResult.hook, @@ -473,7 +440,7 @@ export async function createWorkflowRunEvent( // undefined (lazy ref mode), so deserializeError normalizes it into the // StructuredError shape expected by WorkflowRun consumers. return { - event: stripEventDataRefs(wireResult.event, resolveData), + event: stripEventAndLegacyRefs(wireResult.event, resolveData), run: wireResult.run ? deserializeError(wireResult.run) : undefined, diff --git a/packages/world/src/events.ts b/packages/world/src/events.ts index b433c8fb7f..56863af23c 100644 --- a/packages/world/src/events.ts +++ b/packages/world/src/events.ts @@ -2,6 +2,56 @@ import { z } from 'zod'; import { SerializedDataSchema } from './serialization.js'; import type { PaginationOptions, ResolveData } from './shared.js'; +/** + * Fields within eventData that hold ref/payload data per event type. + * When resolveData is 'none', only these fields are stripped — all other + * metadata (stepName, workflowName, etc.) is preserved. + */ +export const EVENT_DATA_REF_FIELDS: Record = { + run_created: ['input'], + run_completed: ['output'], + run_failed: ['error'], + step_created: ['input'], + step_completed: ['result'], + step_failed: ['error'], + step_retrying: ['error'], + hook_created: ['metadata'], + hook_received: ['payload'], +}; + +/** + * Strip ref/payload fields from eventData based on resolveData setting. + * When resolveData is 'none', removes only large data fields (refs) from + * eventData while preserving metadata like stepName, workflowName, etc. + */ +export function stripEventDataRefs( + event: Event, + resolveData: ResolveData +): Event { + if (resolveData !== 'none') return event; + if (!('eventData' in event)) return event; + + const eventData = (event as any).eventData; + if (!eventData || typeof eventData !== 'object') { + const { eventData: _, ...rest } = event as any; + return rest; + } + + const refFields = EVENT_DATA_REF_FIELDS[event.eventType]; + if (!refFields || refFields.length === 0) return event; + + const stripped = { ...eventData }; + for (const field of refFields) { + delete stripped[field]; + } + + const { eventData: _, ...rest } = event as any; + return { + ...rest, + ...(Object.keys(stripped).length > 0 ? { eventData: stripped } : {}), + }; +} + // Event type enum export const EventTypeSchema = z.enum([ // Run lifecycle events diff --git a/packages/world/src/index.ts b/packages/world/src/index.ts index 7822032f8a..3e7ed1c4fb 100644 --- a/packages/world/src/index.ts +++ b/packages/world/src/index.ts @@ -2,8 +2,10 @@ export type * from './events.js'; export { BaseEventSchema, CreateEventSchema, + EVENT_DATA_REF_FIELDS, EventSchema, EventTypeSchema, + stripEventDataRefs, } from './events.js'; export type * from './hooks.js'; export { HookSchema } from './hooks.js'; From 00df17109d06dfddb5b0e169ce410afeb22eee01 Mon Sep 17 00:00:00 2001 From: Karthik Kalyanaraman Date: Fri, 13 Mar 2026 17:05:05 -0700 Subject: [PATCH 11/13] add test coverage --- packages/world-local/src/storage.test.ts | 123 ++++++++++++++++++++++- 1 file changed, 121 insertions(+), 2 deletions(-) diff --git a/packages/world-local/src/storage.test.ts b/packages/world-local/src/storage.test.ts index 767f654e1e..641ad2c623 100644 --- a/packages/world-local/src/storage.test.ts +++ b/packages/world-local/src/storage.test.ts @@ -2,8 +2,11 @@ import { promises as fs } from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import { WorkflowAPIError } from '@workflow/errors'; -import type { Storage } from '@workflow/world'; -import { DEFAULT_TIMESTAMP_THRESHOLD_MS } from '@workflow/world'; +import type { Event, Storage } from '@workflow/world'; +import { + DEFAULT_TIMESTAMP_THRESHOLD_MS, + stripEventDataRefs, +} from '@workflow/world'; import { monotonicFactory } from 'ulid'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import { writeJSON } from './fs.js'; @@ -17,6 +20,120 @@ import { updateStep, } from './test-helpers.js'; +describe('stripEventDataRefs', () => { + const baseEvent = { + runId: 'wrun_test', + eventId: 'evnt_test', + createdAt: new Date(), + specVersion: 2, + }; + + it('should strip input ref from step_created, keep stepName', () => { + const event = { + ...baseEvent, + eventType: 'step_created' as const, + correlationId: 'step_1', + eventData: { stepName: 'my-step', input: new Uint8Array([1, 2, 3]) }, + } as Event; + + const result = stripEventDataRefs(event, 'none') as any; + expect(result.eventData).toEqual({ stepName: 'my-step' }); + expect(result.eventData).not.toHaveProperty('input'); + }); + + it('should strip input ref from run_created, keep workflowName and deploymentId', () => { + const event = { + ...baseEvent, + eventType: 'run_created' as const, + eventData: { + deploymentId: 'dpl_123', + workflowName: 'my-workflow', + input: new Uint8Array([1, 2, 3]), + }, + } as Event; + + const result = stripEventDataRefs(event, 'none') as any; + expect(result.eventData).toEqual({ + deploymentId: 'dpl_123', + workflowName: 'my-workflow', + }); + expect(result.eventData).not.toHaveProperty('input'); + }); + + it('should strip result ref from step_completed entirely', () => { + const event = { + ...baseEvent, + eventType: 'step_completed' as const, + correlationId: 'step_1', + eventData: { result: new Uint8Array([4, 5]) }, + } as Event; + + const result = stripEventDataRefs(event, 'none') as any; + expect(result.eventData).toBeUndefined(); + }); + + it('should strip error from run_failed, keep errorCode', () => { + const event = { + ...baseEvent, + eventType: 'run_failed' as const, + eventData: { error: 'something broke', errorCode: 'TIMEOUT' }, + } as Event; + + const result = stripEventDataRefs(event, 'none') as any; + expect(result.eventData).toEqual({ errorCode: 'TIMEOUT' }); + expect(result.eventData).not.toHaveProperty('error'); + }); + + it('should strip error from step_failed, keep stack', () => { + const event = { + ...baseEvent, + eventType: 'step_failed' as const, + correlationId: 'step_1', + eventData: { error: 'failed', stack: 'Error: failed\n at ...' }, + } as Event; + + const result = stripEventDataRefs(event, 'none') as any; + expect(result.eventData).toEqual({ stack: 'Error: failed\n at ...' }); + expect(result.eventData).not.toHaveProperty('error'); + }); + + it('should not strip anything when resolveData is "all"', () => { + const event = { + ...baseEvent, + eventType: 'step_created' as const, + correlationId: 'step_1', + eventData: { stepName: 'my-step', input: new Uint8Array([1, 2, 3]) }, + } as Event; + + const result = stripEventDataRefs(event, 'all') as any; + expect(result.eventData.stepName).toBe('my-step'); + expect(result.eventData.input).toBeDefined(); + }); + + it('should pass through events with no ref fields (e.g. run_started)', () => { + const event = { + ...baseEvent, + eventType: 'run_started' as const, + } as Event; + + const result = stripEventDataRefs(event, 'none'); + expect(result).toEqual(event); + }); + + it('should strip metadata from hook_created, keep token', () => { + const event = { + ...baseEvent, + eventType: 'hook_created' as const, + correlationId: 'hook_1', + eventData: { token: 'tok_abc', metadata: { some: 'data' } }, + } as Event; + + const result = stripEventDataRefs(event, 'none') as any; + expect(result.eventData).toEqual({ token: 'tok_abc' }); + expect(result.eventData).not.toHaveProperty('metadata'); + }); +}); + describe('Storage', () => { let testDir: string; let storage: Storage; @@ -1028,6 +1145,8 @@ describe('Storage', () => { expect((result.data[0] as any).eventData).toEqual({ stepName: 'test-step', }); + expect((result.data[0] as any).eventData).not.toHaveProperty('input'); + // step_completed: only ref field 'result' exists, so eventData is removed entirely expect((result.data[1] as any).eventData).toBeUndefined(); expect(result.data[0].correlationId).toBe(correlationId); }); From 2b574226665c79621f0055b962c196b0fdfdf68c Mon Sep 17 00:00:00 2001 From: Karthik Kalyanaraman Date: Fri, 13 Mar 2026 17:19:11 -0700 Subject: [PATCH 12/13] fix web package --- packages/web/app/lib/client/hooks/use-events-list-data.ts | 4 ++-- packages/web/app/lib/client/hooks/use-trace-viewer.test.ts | 2 +- packages/web/app/lib/client/hooks/use-trace-viewer.ts | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/web/app/lib/client/hooks/use-events-list-data.ts b/packages/web/app/lib/client/hooks/use-events-list-data.ts index da90fea69c..9451306fc5 100644 --- a/packages/web/app/lib/client/hooks/use-events-list-data.ts +++ b/packages/web/app/lib/client/hooks/use-events-list-data.ts @@ -53,7 +53,7 @@ export function useEventsListData( fetchEvents(env, runId, { sortOrder, limit: INITIAL_PAGE_SIZE, - withData: true, + withData: false, }) ); if (fetchError) { @@ -108,7 +108,7 @@ export function useEventsListData( cursor, sortOrder, limit: LOAD_MORE_PAGE_SIZE, - withData: true, + withData: false, }) ); if (fetchError) { diff --git a/packages/web/app/lib/client/hooks/use-trace-viewer.test.ts b/packages/web/app/lib/client/hooks/use-trace-viewer.test.ts index 62c13cbb35..a3e9ba9114 100644 --- a/packages/web/app/lib/client/hooks/use-trace-viewer.test.ts +++ b/packages/web/app/lib/client/hooks/use-trace-viewer.test.ts @@ -144,7 +144,7 @@ describe('useWorkflowTraceViewerData', () => { 'run-1', expect.objectContaining({ sortOrder: 'asc', - withData: true, + withData: false, }) ); }); diff --git a/packages/web/app/lib/client/hooks/use-trace-viewer.ts b/packages/web/app/lib/client/hooks/use-trace-viewer.ts index ad383b40ae..aea0f30f5a 100644 --- a/packages/web/app/lib/client/hooks/use-trace-viewer.ts +++ b/packages/web/app/lib/client/hooks/use-trace-viewer.ts @@ -52,7 +52,7 @@ export function useWorkflowTraceViewerData( fetchEvents(env, runId, { sortOrder: 'asc', limit: INITIAL_PAGE_SIZE, - withData: true, + withData: false, }) ), ]); @@ -103,7 +103,7 @@ export function useWorkflowTraceViewerData( cursor: eventsCursor, sortOrder: 'asc', limit: LOAD_MORE_PAGE_SIZE, - withData: true, + withData: false, }) ); @@ -161,7 +161,7 @@ export function useWorkflowTraceViewerData( cursor: eventsCursor, sortOrder: 'asc', limit: LIVE_POLL_LIMIT, - withData: true, + withData: false, }) ), setItems: setEvents, From 913180ac0758d8befe5cfa5b7286023c4b7c31c9 Mon Sep 17 00:00:00 2001 From: Karthik Kalyanaraman Date: Fri, 13 Mar 2026 17:19:48 -0700 Subject: [PATCH 13/13] fix web package to not pass withData: true --- .changeset/wise-hornets-fetch.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.changeset/wise-hornets-fetch.md b/.changeset/wise-hornets-fetch.md index 72594978e6..e0fd0b0bd3 100644 --- a/.changeset/wise-hornets-fetch.md +++ b/.changeset/wise-hornets-fetch.md @@ -1,4 +1,5 @@ --- +"@workflow/web": patch "@workflow/world": patch "@workflow/world-vercel": patch "@workflow/world-postgres": patch