From 50eaa284331ead545d26dca753f26ae01bfde1db Mon Sep 17 00:00:00 2001 From: Nathan Rajlich Date: Tue, 11 Nov 2025 12:08:37 -0800 Subject: [PATCH 01/23] Support passing step function references across serialization layer --- packages/core/src/runtime/world.ts | 4 ++-- packages/core/src/schemas.ts | 3 ++- packages/core/src/serialization.ts | 17 +++++++++++++++++ packages/core/src/step.ts | 14 +++++++++++++- packages/core/src/symbols.ts | 3 +++ 5 files changed, 37 insertions(+), 4 deletions(-) diff --git a/packages/core/src/runtime/world.ts b/packages/core/src/runtime/world.ts index 664944ee3..758f84917 100644 --- a/packages/core/src/runtime/world.ts +++ b/packages/core/src/runtime/world.ts @@ -1,10 +1,10 @@ import { createRequire } from 'node:module'; -import Path from 'node:path'; +import { join } from 'node:path'; import type { World } from '@workflow/world'; import { createEmbeddedWorld } from '@workflow/world-local'; import { createVercelWorld } from '@workflow/world-vercel'; -const require = createRequire(Path.join(process.cwd(), 'index.js')); +const require = createRequire(join(process.cwd(), 'index.js')); let worldCache: World | undefined; let stubbedWorldCache: World | undefined; diff --git a/packages/core/src/schemas.ts b/packages/core/src/schemas.ts index d07bc69bc..69f317033 100644 --- a/packages/core/src/schemas.ts +++ b/packages/core/src/schemas.ts @@ -63,4 +63,5 @@ export type Serializable = | Uint8ClampedArray | Uint16Array | Uint32Array - | WritableStream; + | WritableStream + | ((...args: Serializable[]) => Promise); // Step function diff --git a/packages/core/src/serialization.ts b/packages/core/src/serialization.ts index c67bcac7c..640286845 100644 --- a/packages/core/src/serialization.ts +++ b/packages/core/src/serialization.ts @@ -1,8 +1,10 @@ import { WorkflowRuntimeError } from '@workflow/errors'; import * as devalue from 'devalue'; +import { getStepFunction } from './private.js'; import { getWorld } from './runtime/world.js'; import { BODY_INIT_SYMBOL, + STEP_FUNCTION_NAME_SYMBOL, STREAM_NAME_SYMBOL, STREAM_TYPE_SYMBOL, WEBHOOK_RESPONSE_WRITABLE, @@ -169,6 +171,7 @@ export interface SerializableSpecial { redirected: boolean; }; Set: any[]; + StepFunction: string; // step function name/ID URL: string; URLSearchParams: string; Uint8Array: string; // base64 string @@ -275,6 +278,11 @@ function getCommonReducers(global: Record = globalThis) { }; }, Set: (value) => value instanceof global.Set && Array.from(value), + StepFunction: (value) => { + if (typeof value !== 'function') return false; + const stepName = value[STEP_FUNCTION_NAME_SYMBOL]; + return stepName ? stepName : false; + }, URL: (value) => value instanceof global.URL && value.href, URLSearchParams: (value) => { if (!(value instanceof global.URLSearchParams)) return false; @@ -516,6 +524,15 @@ function getCommonRevivers(global: Record = globalThis) { Map: (value) => new global.Map(value), RegExp: (value) => new global.RegExp(value.source, value.flags), Set: (value) => new global.Set(value), + StepFunction: (value) => { + const stepFn = getStepFunction(value); + if (!stepFn) { + throw new Error( + `Step function "${value}" not found. Make sure the step function is registered.` + ); + } + return stepFn; + }, URL: (value) => new global.URL(value), URLSearchParams: (value) => new global.URLSearchParams(value === '.' ? '' : value), diff --git a/packages/core/src/step.ts b/packages/core/src/step.ts index 0e6d9de72..f0c9e37ec 100644 --- a/packages/core/src/step.ts +++ b/packages/core/src/step.ts @@ -6,12 +6,13 @@ import { stepLogger } from './logger.js'; import type { WorkflowOrchestratorContext } from './private.js'; import type { Serializable } from './schemas.js'; import { hydrateStepReturnValue } from './serialization.js'; +import { STEP_FUNCTION_NAME_SYMBOL } from './symbols.js'; export function createUseStep(ctx: WorkflowOrchestratorContext) { return function useStep( stepName: string ) { - return (...args: Args): Promise => { + const stepFunction = (...args: Args): Promise => { const { promise, resolve, reject } = withResolvers(); const correlationId = `step_${ctx.generateUlid()}`; @@ -124,5 +125,16 @@ export function createUseStep(ctx: WorkflowOrchestratorContext) { return promise; }; + + // Attach the step name to the function so it can be properly serialized + // when passed as an argument to another step + Object.defineProperty(stepFunction, STEP_FUNCTION_NAME_SYMBOL, { + value: stepName, + writable: false, + enumerable: false, + configurable: false, + }); + + return stepFunction; }; } diff --git a/packages/core/src/symbols.ts b/packages/core/src/symbols.ts index 1df8ac074..f5957f322 100644 --- a/packages/core/src/symbols.ts +++ b/packages/core/src/symbols.ts @@ -9,3 +9,6 @@ export const BODY_INIT_SYMBOL = Symbol.for('BODY_INIT'); export const WEBHOOK_RESPONSE_WRITABLE = Symbol.for( 'WEBHOOK_RESPONSE_WRITABLE' ); +export const STEP_FUNCTION_NAME_SYMBOL = Symbol.for( + 'WORKFLOW_STEP_FUNCTION_NAME' +); From 7b8fbebde1e573a05f0f3d9df1a62780edfda139 Mon Sep 17 00:00:00 2001 From: Nathan Rajlich Date: Tue, 11 Nov 2025 13:22:04 -0800 Subject: [PATCH 02/23] tests --- packages/core/e2e/e2e.test.ts | 21 +++++++ packages/core/src/serialization.test.ts | 83 +++++++++++++++++++++++++ packages/core/src/serialization.ts | 5 +- packages/core/src/step.test.ts | 78 +++++++++++++++++++++++ workbench/example/workflows/99_e2e.ts | 21 +++++++ 5 files changed, 206 insertions(+), 2 deletions(-) diff --git a/packages/core/e2e/e2e.test.ts b/packages/core/e2e/e2e.test.ts index a5bfa2bd6..533883f42 100644 --- a/packages/core/e2e/e2e.test.ts +++ b/packages/core/e2e/e2e.test.ts @@ -598,4 +598,25 @@ describe('e2e', () => { expect(runData.error).toContain('Error from imported helper module'); } ); + + test( + 'stepFunctionPassingWorkflow - step function references can be passed as arguments', + { timeout: 60_000 }, + async () => { + // This workflow passes a step function reference to another step + // The receiving step calls the passed function and returns the result + const run = await triggerWorkflow('stepFunctionPassingWorkflow', []); + const returnValue = await getWorkflowReturnValue(run.runId); + + // doubleNumber(10) = 20, then multiply by 2 = 40 + expect(returnValue).toBe(40); + + // Verify the run completed successfully + const { json: runData } = await cliInspectJson( + `runs ${run.runId} --withData` + ); + expect(runData.status).toBe('completed'); + expect(runData.output).toBe(40); + } + ); }); diff --git a/packages/core/src/serialization.test.ts b/packages/core/src/serialization.test.ts index 746467aac..0c2df6faf 100644 --- a/packages/core/src/serialization.test.ts +++ b/packages/core/src/serialization.test.ts @@ -6,9 +6,16 @@ import { dehydrateStepReturnValue, dehydrateWorkflowArguments, dehydrateWorkflowReturnValue, + getCommonRevivers, getStreamType, + hydrateStepArguments, hydrateWorkflowArguments, } from './serialization.js'; +import { + getStepFunction, + registerStepFunction, + STEP_FUNCTION_NAME_SYMBOL, +} from './private.js'; import { STREAM_NAME_SYMBOL } from './symbols.js'; import { createContext } from './vm/index.js'; @@ -783,3 +790,79 @@ describe('step return value', () => { ); }); }); + +describe('step function serialization', () => { + const { globalThis: vmGlobalThis } = createContext({ + seed: 'test', + fixedTimestamp: 1714857600000, + }); + + it('should detect step function by checking for STEP_FUNCTION_NAME_SYMBOL', () => { + const stepName = 'myStep'; + const stepFn = async (x: number) => x * 2; + + // Attach the symbol like useStep() does + Object.defineProperty(stepFn, STEP_FUNCTION_NAME_SYMBOL, { + value: stepName, + writable: false, + enumerable: false, + configurable: false, + }); + + // Verify the symbol is attached correctly + expect((stepFn as any)[STEP_FUNCTION_NAME_SYMBOL]).toBe(stepName); + }); + + it('should not have STEP_FUNCTION_NAME_SYMBOL on regular functions', () => { + const regularFn = async (x: number) => x * 2; + + // Regular functions should not have the symbol + expect((regularFn as any)[STEP_FUNCTION_NAME_SYMBOL]).toBeUndefined(); + }); + + it('should lookup registered step function by name', () => { + const stepName = 'myRegisteredStep'; + const stepFn = async (x: number) => x * 2; + + // Register the step function + registerStepFunction(stepName, stepFn); + + // Should be retrievable by name + const retrieved = getStepFunction(stepName); + expect(retrieved).toBe(stepFn); + }); + + it('should return undefined for non-existent registered step function', () => { + const retrieved = getStepFunction('nonExistentStep'); + expect(retrieved).toBeUndefined(); + }); + + it('should deserialize step function name through reviver', () => { + const stepName = 'testStep'; + const stepFn = async () => 42; + + // Register the step function + registerStepFunction(stepName, stepFn); + + // Get the reviver and test it directly + const revivers = getCommonRevivers(vmGlobalThis); + const result = revivers.StepFunction(stepName); + + expect(result).toBe(stepFn); + }); + + it('should throw error when reviver cannot find registered step function', () => { + const revivers = getCommonRevivers(vmGlobalThis); + + let err: Error | undefined; + try { + revivers.StepFunction('nonExistentStep'); + } catch (err_) { + err = err_ as Error; + } + + expect(err).toBeDefined(); + expect(err?.message).toContain('Step function "nonExistentStep" not found'); + expect(err?.message).toContain('Make sure the step function is registered'); + }); +}); diff --git a/packages/core/src/serialization.ts b/packages/core/src/serialization.ts index 640286845..73f856d23 100644 --- a/packages/core/src/serialization.ts +++ b/packages/core/src/serialization.ts @@ -281,7 +281,8 @@ function getCommonReducers(global: Record = globalThis) { StepFunction: (value) => { if (typeof value !== 'function') return false; const stepName = value[STEP_FUNCTION_NAME_SYMBOL]; - return stepName ? stepName : false; + // Must return a truthy value; devalue will reject if we return false for a function + return stepName || false; }, URL: (value) => value instanceof global.URL && value.href, URLSearchParams: (value) => { @@ -472,7 +473,7 @@ function getStepReducers( }; } -function getCommonRevivers(global: Record = globalThis) { +export function getCommonRevivers(global: Record = globalThis) { function reviveArrayBuffer(value: string) { // Handle sentinel value for zero-length buffers const base64 = value === '.' ? '' : value; diff --git a/packages/core/src/step.test.ts b/packages/core/src/step.test.ts index bbc20ce25..d7ab375bc 100644 --- a/packages/core/src/step.test.ts +++ b/packages/core/src/step.test.ts @@ -5,6 +5,7 @@ import { monotonicFactory } from 'ulid'; import { describe, expect, it, vi } from 'vitest'; import { EventsConsumer } from './events-consumer.js'; import { WorkflowSuspension } from './global.js'; +import { STEP_FUNCTION_NAME_SYMBOL } from './symbols.js'; import type { WorkflowOrchestratorContext } from './private.js'; import { createUseStep } from './step.js'; import { createContext } from './vm/index.js'; @@ -175,4 +176,81 @@ describe('createUseStep', () => { ] `); }); + + it('should attach STEP_FUNCTION_NAME_SYMBOL to the returned function', async () => { + const ctx = setupWorkflowContext([ + { + eventId: 'evnt_0', + runId: 'wrun_123', + eventType: 'step_completed', + correlationId: 'step_01K11TFZ62YS0YYFDQ3E8B9YCV', + eventData: { + result: [42], + }, + createdAt: new Date(), + }, + ]); + + const useStep = createUseStep(ctx); + const myStep = useStep('calculateValue'); + + // The returned function should have the symbol attached with the step name + expect((myStep as any)[STEP_FUNCTION_NAME_SYMBOL]).toBe('calculateValue'); + }); + + it('should allow the symbol to be non-writable and non-configurable', async () => { + const ctx = setupWorkflowContext([]); + const useStep = createUseStep(ctx); + const myStep = useStep('immutableStep'); + + const descriptor = Object.getOwnPropertyDescriptor( + myStep as any, + STEP_FUNCTION_NAME_SYMBOL + ); + + expect(descriptor).toBeDefined(); + expect(descriptor?.value).toBe('immutableStep'); + expect(descriptor?.writable).toBe(false); + expect(descriptor?.configurable).toBe(false); + expect(descriptor?.enumerable).toBe(false); + }); + + it('should attach symbol to multiple step references independently', async () => { + const ctx = setupWorkflowContext([ + { + eventId: 'evnt_0', + runId: 'wrun_123', + eventType: 'step_completed', + correlationId: 'step_01K11TFZ62YS0YYFDQ3E8B9YCV', + eventData: { + result: [1], + }, + createdAt: new Date(), + }, + { + eventId: 'evnt_1', + runId: 'wrun_123', + eventType: 'step_completed', + correlationId: 'step_01K11TFZ62YS0YYFDQ3E8B9YCW', + eventData: { + result: [2], + }, + createdAt: new Date(), + }, + ]); + + const useStep = createUseStep(ctx); + const stepA = useStep('stepA'); + const stepB = useStep('stepB'); + + expect((stepA as any)[STEP_FUNCTION_NAME_SYMBOL]).toBe('stepA'); + expect((stepB as any)[STEP_FUNCTION_NAME_SYMBOL]).toBe('stepB'); + + // Call them to verify they work independently + const resultA = stepA(1); + const resultB = stepB(2); + + expect(await resultA).toBe(1); + expect(await resultB).toBe(2); + }); }); diff --git a/workbench/example/workflows/99_e2e.ts b/workbench/example/workflows/99_e2e.ts index 654daf162..a38590da2 100644 --- a/workbench/example/workflows/99_e2e.ts +++ b/workbench/example/workflows/99_e2e.ts @@ -466,3 +466,24 @@ export async function retryableAndFatalErrorWorkflow() { return { retryableResult, gotFatalError }; } + +////////////////////////////////////////////////////////// + +async function stepWithStepFunctionArg(stepFn: (x: number) => Promise) { + 'use step'; + // Call the passed step function reference + const result = await stepFn(10); + return result * 2; +} + +async function doubleNumber(x: number) { + 'use step'; + return x * 2; +} + +export async function stepFunctionPassingWorkflow() { + 'use workflow'; + // Pass a step function reference to another step + const result = await stepWithStepFunctionArg(doubleNumber); + return result; +} From 476147ad9dd31960563bfa0c718a149202217a6f Mon Sep 17 00:00:00 2001 From: Nathan Rajlich Date: Tue, 11 Nov 2025 14:44:02 -0800 Subject: [PATCH 03/23] . --- packages/core/src/serialization.test.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/core/src/serialization.test.ts b/packages/core/src/serialization.test.ts index 0c2df6faf..f142d02dc 100644 --- a/packages/core/src/serialization.test.ts +++ b/packages/core/src/serialization.test.ts @@ -1,6 +1,7 @@ import { runInContext } from 'node:vm'; import type { WorkflowRuntimeError } from '@workflow/errors'; import { describe, expect, it } from 'vitest'; +import { getStepFunction, registerStepFunction } from './private.js'; import { dehydrateStepArguments, dehydrateStepReturnValue, @@ -8,15 +9,9 @@ import { dehydrateWorkflowReturnValue, getCommonRevivers, getStreamType, - hydrateStepArguments, hydrateWorkflowArguments, } from './serialization.js'; -import { - getStepFunction, - registerStepFunction, - STEP_FUNCTION_NAME_SYMBOL, -} from './private.js'; -import { STREAM_NAME_SYMBOL } from './symbols.js'; +import { STEP_FUNCTION_NAME_SYMBOL, STREAM_NAME_SYMBOL } from './symbols.js'; import { createContext } from './vm/index.js'; describe('getStreamType', () => { From 530e47795c940303652aea46130d752b60048783 Mon Sep 17 00:00:00 2001 From: Nathan Rajlich Date: Tue, 11 Nov 2025 15:32:01 -0800 Subject: [PATCH 04/23] . --- packages/core/src/step.test.ts | 78 --------- packages/core/src/step.ts | 14 +- .../swc-plugin-workflow/transform/src/lib.rs | 152 ++++++++++++++++++ .../conflicting-directives/output-workflow.js | 6 + .../forbidden-expressions/output-workflow.js | 12 ++ .../errors/invalid-exports/output-workflow.js | 6 + .../output-workflow.js | 6 + .../non-async-functions/output-workflow.js | 6 + .../fixture/destructuring/output-workflow.js | 42 +++++ .../mixed-functions/output-workflow.js | 6 + .../module-level-step/output-workflow.js | 6 + .../fixture/single-step/output-workflow.js | 6 + .../step-with-imports/output-workflow.js | 6 + .../fixture/unused-exports/output-workflow.js | 6 + 14 files changed, 261 insertions(+), 91 deletions(-) diff --git a/packages/core/src/step.test.ts b/packages/core/src/step.test.ts index d7ab375bc..bbc20ce25 100644 --- a/packages/core/src/step.test.ts +++ b/packages/core/src/step.test.ts @@ -5,7 +5,6 @@ import { monotonicFactory } from 'ulid'; import { describe, expect, it, vi } from 'vitest'; import { EventsConsumer } from './events-consumer.js'; import { WorkflowSuspension } from './global.js'; -import { STEP_FUNCTION_NAME_SYMBOL } from './symbols.js'; import type { WorkflowOrchestratorContext } from './private.js'; import { createUseStep } from './step.js'; import { createContext } from './vm/index.js'; @@ -176,81 +175,4 @@ describe('createUseStep', () => { ] `); }); - - it('should attach STEP_FUNCTION_NAME_SYMBOL to the returned function', async () => { - const ctx = setupWorkflowContext([ - { - eventId: 'evnt_0', - runId: 'wrun_123', - eventType: 'step_completed', - correlationId: 'step_01K11TFZ62YS0YYFDQ3E8B9YCV', - eventData: { - result: [42], - }, - createdAt: new Date(), - }, - ]); - - const useStep = createUseStep(ctx); - const myStep = useStep('calculateValue'); - - // The returned function should have the symbol attached with the step name - expect((myStep as any)[STEP_FUNCTION_NAME_SYMBOL]).toBe('calculateValue'); - }); - - it('should allow the symbol to be non-writable and non-configurable', async () => { - const ctx = setupWorkflowContext([]); - const useStep = createUseStep(ctx); - const myStep = useStep('immutableStep'); - - const descriptor = Object.getOwnPropertyDescriptor( - myStep as any, - STEP_FUNCTION_NAME_SYMBOL - ); - - expect(descriptor).toBeDefined(); - expect(descriptor?.value).toBe('immutableStep'); - expect(descriptor?.writable).toBe(false); - expect(descriptor?.configurable).toBe(false); - expect(descriptor?.enumerable).toBe(false); - }); - - it('should attach symbol to multiple step references independently', async () => { - const ctx = setupWorkflowContext([ - { - eventId: 'evnt_0', - runId: 'wrun_123', - eventType: 'step_completed', - correlationId: 'step_01K11TFZ62YS0YYFDQ3E8B9YCV', - eventData: { - result: [1], - }, - createdAt: new Date(), - }, - { - eventId: 'evnt_1', - runId: 'wrun_123', - eventType: 'step_completed', - correlationId: 'step_01K11TFZ62YS0YYFDQ3E8B9YCW', - eventData: { - result: [2], - }, - createdAt: new Date(), - }, - ]); - - const useStep = createUseStep(ctx); - const stepA = useStep('stepA'); - const stepB = useStep('stepB'); - - expect((stepA as any)[STEP_FUNCTION_NAME_SYMBOL]).toBe('stepA'); - expect((stepB as any)[STEP_FUNCTION_NAME_SYMBOL]).toBe('stepB'); - - // Call them to verify they work independently - const resultA = stepA(1); - const resultB = stepB(2); - - expect(await resultA).toBe(1); - expect(await resultB).toBe(2); - }); }); diff --git a/packages/core/src/step.ts b/packages/core/src/step.ts index f0c9e37ec..0e6d9de72 100644 --- a/packages/core/src/step.ts +++ b/packages/core/src/step.ts @@ -6,13 +6,12 @@ import { stepLogger } from './logger.js'; import type { WorkflowOrchestratorContext } from './private.js'; import type { Serializable } from './schemas.js'; import { hydrateStepReturnValue } from './serialization.js'; -import { STEP_FUNCTION_NAME_SYMBOL } from './symbols.js'; export function createUseStep(ctx: WorkflowOrchestratorContext) { return function useStep( stepName: string ) { - const stepFunction = (...args: Args): Promise => { + return (...args: Args): Promise => { const { promise, resolve, reject } = withResolvers(); const correlationId = `step_${ctx.generateUlid()}`; @@ -125,16 +124,5 @@ export function createUseStep(ctx: WorkflowOrchestratorContext) { return promise; }; - - // Attach the step name to the function so it can be properly serialized - // when passed as an argument to another step - Object.defineProperty(stepFunction, STEP_FUNCTION_NAME_SYMBOL, { - value: stepName, - writable: false, - enumerable: false, - configurable: false, - }); - - return stepFunction; }; } diff --git a/packages/swc-plugin-workflow/transform/src/lib.rs b/packages/swc-plugin-workflow/transform/src/lib.rs index 1ab67a0ae..9edcaeec0 100644 --- a/packages/swc-plugin-workflow/transform/src/lib.rs +++ b/packages/swc-plugin-workflow/transform/src/lib.rs @@ -789,6 +789,119 @@ impl StepTransform { }) } + // Mark a step function with the STEP_FUNCTION_NAME_SYMBOL in workflow mode + fn create_step_function_marking(&self, fn_name: &str, span: swc_core::common::Span) -> Stmt { + let step_id = self.create_id(Some(fn_name), span, false); + + // Create: Object.defineProperty(functionName, Symbol.for('WORKFLOW_STEP_FUNCTION_NAME'), { + // value: "stepId", + // writable: false, + // enumerable: false, + // configurable: false + // }) + Stmt::Expr(ExprStmt { + span: DUMMY_SP, + expr: Box::new(Expr::Call(CallExpr { + span: DUMMY_SP, + ctxt: SyntaxContext::empty(), + callee: Callee::Expr(Box::new(Expr::Member(MemberExpr { + span: DUMMY_SP, + obj: Box::new(Expr::Ident(Ident::new( + "Object".into(), + DUMMY_SP, + SyntaxContext::empty(), + ))), + prop: MemberProp::Ident(IdentName::new("defineProperty".into(), DUMMY_SP)), + }))), + args: vec![ + // First argument: functionName + ExprOrSpread { + spread: None, + expr: Box::new(Expr::Ident(Ident::new( + fn_name.into(), + DUMMY_SP, + SyntaxContext::empty(), + ))), + }, + // Second argument: Symbol.for('WORKFLOW_STEP_FUNCTION_NAME') + ExprOrSpread { + spread: None, + expr: Box::new(Expr::Call(CallExpr { + span: DUMMY_SP, + ctxt: SyntaxContext::empty(), + callee: Callee::Expr(Box::new(Expr::Member(MemberExpr { + span: DUMMY_SP, + obj: Box::new(Expr::Ident(Ident::new( + "Symbol".into(), + DUMMY_SP, + SyntaxContext::empty(), + ))), + prop: MemberProp::Ident(IdentName::new("for".into(), DUMMY_SP)), + }))), + args: vec![ExprOrSpread { + spread: None, + expr: Box::new(Expr::Lit(Lit::Str(Str { + span: DUMMY_SP, + value: "WORKFLOW_STEP_FUNCTION_NAME".into(), + raw: None, + }))), + }], + type_args: None, + })), + }, + // Third argument: property descriptor object + ExprOrSpread { + spread: None, + expr: Box::new(Expr::Object(ObjectLit { + span: DUMMY_SP, + props: vec![ + PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp { + key: PropName::Ident(IdentName::new("value".into(), DUMMY_SP)), + value: Box::new(Expr::Lit(Lit::Str(Str { + span: DUMMY_SP, + value: step_id.into(), + raw: None, + }))), + }))), + PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp { + key: PropName::Ident(IdentName::new( + "writable".into(), + DUMMY_SP, + )), + value: Box::new(Expr::Lit(Lit::Bool(Bool { + span: DUMMY_SP, + value: false, + }))), + }))), + PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp { + key: PropName::Ident(IdentName::new( + "enumerable".into(), + DUMMY_SP, + )), + value: Box::new(Expr::Lit(Lit::Bool(Bool { + span: DUMMY_SP, + value: false, + }))), + }))), + PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp { + key: PropName::Ident(IdentName::new( + "configurable".into(), + DUMMY_SP, + )), + value: Box::new(Expr::Lit(Lit::Bool(Bool { + span: DUMMY_SP, + value: false, + }))), + }))), + ], + })), + }, + ], + type_args: None, + })), + }) + } + // Create a registration call for step mode fn create_registration_call(&mut self, name: &str, span: swc_core::common::Span) { // Only register each function once @@ -2020,6 +2133,45 @@ impl VisitMut for StepTransform { } } + // In workflow mode, mark all step functions with the STEP_FUNCTION_NAME_SYMBOL + if self.mode == TransformMode::Workflow { + let step_functions: Vec<_> = self.step_function_names.iter().cloned().collect(); + + // Collect function marking statements first (to avoid borrow conflicts) + let mut step_marking_statements = Vec::new(); + for item in items.iter() { + match item { + ModuleItem::Stmt(Stmt::Decl(Decl::Fn(fn_decl))) => { + let fn_name = fn_decl.ident.sym.to_string(); + if step_functions.contains(&fn_name) { + step_marking_statements.push( + self.create_step_function_marking(&fn_name, fn_decl.function.span), + ); + } + } + ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(export_decl)) => { + if let Decl::Fn(fn_decl) = &export_decl.decl { + let fn_name = fn_decl.ident.sym.to_string(); + if step_functions.contains(&fn_name) { + step_marking_statements.push( + self.create_step_function_marking( + &fn_name, + fn_decl.function.span, + ), + ); + } + } + } + _ => {} + } + } + + // Now add all the marking statements + for stmt in step_marking_statements { + items.push(ModuleItem::Stmt(stmt)); + } + } + // Clear the workflow_functions_needing_id since we've already processed them self.workflow_functions_needing_id.clear(); diff --git a/packages/swc-plugin-workflow/transform/tests/errors/conflicting-directives/output-workflow.js b/packages/swc-plugin-workflow/transform/tests/errors/conflicting-directives/output-workflow.js index 3c0dca241..d5bd33d25 100644 --- a/packages/swc-plugin-workflow/transform/tests/errors/conflicting-directives/output-workflow.js +++ b/packages/swc-plugin-workflow/transform/tests/errors/conflicting-directives/output-workflow.js @@ -5,3 +5,9 @@ export async function test() { return globalThis[Symbol.for("WORKFLOW_USE_STEP")]("step//input.js//test")(); } +Object.defineProperty(test, Symbol.for("WORKFLOW_STEP_FUNCTION_NAME"), { + value: "step//input.js//test", + writable: false, + enumerable: false, + configurable: false +}); diff --git a/packages/swc-plugin-workflow/transform/tests/errors/forbidden-expressions/output-workflow.js b/packages/swc-plugin-workflow/transform/tests/errors/forbidden-expressions/output-workflow.js index ac7650ce5..9f533648a 100644 --- a/packages/swc-plugin-workflow/transform/tests/errors/forbidden-expressions/output-workflow.js +++ b/packages/swc-plugin-workflow/transform/tests/errors/forbidden-expressions/output-workflow.js @@ -12,3 +12,15 @@ class TestClass extends BaseClass { return super.method(); } } +Object.defineProperty(stepWithThis, Symbol.for("WORKFLOW_STEP_FUNCTION_NAME"), { + value: "step//input.js//stepWithThis", + writable: false, + enumerable: false, + configurable: false +}); +Object.defineProperty(stepWithArguments, Symbol.for("WORKFLOW_STEP_FUNCTION_NAME"), { + value: "step//input.js//stepWithArguments", + writable: false, + enumerable: false, + configurable: false +}); diff --git a/packages/swc-plugin-workflow/transform/tests/errors/invalid-exports/output-workflow.js b/packages/swc-plugin-workflow/transform/tests/errors/invalid-exports/output-workflow.js index 2a7f4cfc2..c684ad55a 100644 --- a/packages/swc-plugin-workflow/transform/tests/errors/invalid-exports/output-workflow.js +++ b/packages/swc-plugin-workflow/transform/tests/errors/invalid-exports/output-workflow.js @@ -13,3 +13,9 @@ export * from './other'; export async function validStep() { return globalThis[Symbol.for("WORKFLOW_USE_STEP")]("step//input.js//validStep")(); } +Object.defineProperty(validStep, Symbol.for("WORKFLOW_STEP_FUNCTION_NAME"), { + value: "step//input.js//validStep", + writable: false, + enumerable: false, + configurable: false +}); diff --git a/packages/swc-plugin-workflow/transform/tests/errors/misplaced-function-directive/output-workflow.js b/packages/swc-plugin-workflow/transform/tests/errors/misplaced-function-directive/output-workflow.js index 5ce34928e..92e43e692 100644 --- a/packages/swc-plugin-workflow/transform/tests/errors/misplaced-function-directive/output-workflow.js +++ b/packages/swc-plugin-workflow/transform/tests/errors/misplaced-function-directive/output-workflow.js @@ -8,3 +8,9 @@ export const badWorkflow = async ()=>{ 'use workflow'; return true; }; +Object.defineProperty(badStep, Symbol.for("WORKFLOW_STEP_FUNCTION_NAME"), { + value: "step//input.js//badStep", + writable: false, + enumerable: false, + configurable: false +}); diff --git a/packages/swc-plugin-workflow/transform/tests/errors/non-async-functions/output-workflow.js b/packages/swc-plugin-workflow/transform/tests/errors/non-async-functions/output-workflow.js index 260788cba..51ddfc5b8 100644 --- a/packages/swc-plugin-workflow/transform/tests/errors/non-async-functions/output-workflow.js +++ b/packages/swc-plugin-workflow/transform/tests/errors/non-async-functions/output-workflow.js @@ -24,3 +24,9 @@ export const validWorkflow = async ()=>{ return 'test'; }; validWorkflow.workflowId = "workflow//input.js//validWorkflow"; +Object.defineProperty(validStep, Symbol.for("WORKFLOW_STEP_FUNCTION_NAME"), { + value: "step//input.js//validStep", + writable: false, + enumerable: false, + configurable: false +}); diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/destructuring/output-workflow.js b/packages/swc-plugin-workflow/transform/tests/fixture/destructuring/output-workflow.js index 1ed07b495..262e7d6c7 100644 --- a/packages/swc-plugin-workflow/transform/tests/fixture/destructuring/output-workflow.js +++ b/packages/swc-plugin-workflow/transform/tests/fixture/destructuring/output-workflow.js @@ -44,3 +44,45 @@ export async function multiple({ a, b }, { c, d }) { export async function rest_top_level(a, b, ...rest) { return globalThis[Symbol.for("WORKFLOW_USE_STEP")]("step//input.js//rest_top_level")(a, b, ...rest); } +Object.defineProperty(destructure, Symbol.for("WORKFLOW_STEP_FUNCTION_NAME"), { + value: "step//input.js//destructure", + writable: false, + enumerable: false, + configurable: false +}); +Object.defineProperty(process_array, Symbol.for("WORKFLOW_STEP_FUNCTION_NAME"), { + value: "step//input.js//process_array", + writable: false, + enumerable: false, + configurable: false +}); +Object.defineProperty(nested_destructure, Symbol.for("WORKFLOW_STEP_FUNCTION_NAME"), { + value: "step//input.js//nested_destructure", + writable: false, + enumerable: false, + configurable: false +}); +Object.defineProperty(with_defaults, Symbol.for("WORKFLOW_STEP_FUNCTION_NAME"), { + value: "step//input.js//with_defaults", + writable: false, + enumerable: false, + configurable: false +}); +Object.defineProperty(with_rest, Symbol.for("WORKFLOW_STEP_FUNCTION_NAME"), { + value: "step//input.js//with_rest", + writable: false, + enumerable: false, + configurable: false +}); +Object.defineProperty(multiple, Symbol.for("WORKFLOW_STEP_FUNCTION_NAME"), { + value: "step//input.js//multiple", + writable: false, + enumerable: false, + configurable: false +}); +Object.defineProperty(rest_top_level, Symbol.for("WORKFLOW_STEP_FUNCTION_NAME"), { + value: "step//input.js//rest_top_level", + writable: false, + enumerable: false, + configurable: false +}); diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/mixed-functions/output-workflow.js b/packages/swc-plugin-workflow/transform/tests/fixture/mixed-functions/output-workflow.js index de3235a96..a299fd8ce 100644 --- a/packages/swc-plugin-workflow/transform/tests/fixture/mixed-functions/output-workflow.js +++ b/packages/swc-plugin-workflow/transform/tests/fixture/mixed-functions/output-workflow.js @@ -9,3 +9,9 @@ export async function normalFunction(a, b) { return a * b; } workflowFunction.workflowId = "workflow//input.js//workflowFunction"; +Object.defineProperty(stepFunction, Symbol.for("WORKFLOW_STEP_FUNCTION_NAME"), { + value: "step//input.js//stepFunction", + writable: false, + enumerable: false, + configurable: false +}); diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/module-level-step/output-workflow.js b/packages/swc-plugin-workflow/transform/tests/fixture/module-level-step/output-workflow.js index 127712b65..8c5a80455 100644 --- a/packages/swc-plugin-workflow/transform/tests/fixture/module-level-step/output-workflow.js +++ b/packages/swc-plugin-workflow/transform/tests/fixture/module-level-step/output-workflow.js @@ -7,3 +7,9 @@ export async function step(input) { return globalThis[Symbol.for("WORKFLOW_USE_STEP")]("step//input.js//step")(input); } export const stepArrow = async (input)=>globalThis[Symbol.for("WORKFLOW_USE_STEP")]("step//input.js//stepArrow")(input); +Object.defineProperty(step, Symbol.for("WORKFLOW_STEP_FUNCTION_NAME"), { + value: "step//input.js//step", + writable: false, + enumerable: false, + configurable: false +}); diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/single-step/output-workflow.js b/packages/swc-plugin-workflow/transform/tests/fixture/single-step/output-workflow.js index 607db576f..d5fe6cc26 100644 --- a/packages/swc-plugin-workflow/transform/tests/fixture/single-step/output-workflow.js +++ b/packages/swc-plugin-workflow/transform/tests/fixture/single-step/output-workflow.js @@ -2,3 +2,9 @@ export async function add(a, b) { return globalThis[Symbol.for("WORKFLOW_USE_STEP")]("step//input.js//add")(a, b); } +Object.defineProperty(add, Symbol.for("WORKFLOW_STEP_FUNCTION_NAME"), { + value: "step//input.js//add", + writable: false, + enumerable: false, + configurable: false +}); diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/step-with-imports/output-workflow.js b/packages/swc-plugin-workflow/transform/tests/fixture/step-with-imports/output-workflow.js index 93bcdd6cb..b0608409e 100644 --- a/packages/swc-plugin-workflow/transform/tests/fixture/step-with-imports/output-workflow.js +++ b/packages/swc-plugin-workflow/transform/tests/fixture/step-with-imports/output-workflow.js @@ -10,3 +10,9 @@ export function normalFunction() { useful.doSomething(); return usefulHelper(); } +Object.defineProperty(processData, Symbol.for("WORKFLOW_STEP_FUNCTION_NAME"), { + value: "step//input.js//processData", + writable: false, + enumerable: false, + configurable: false +}); diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/unused-exports/output-workflow.js b/packages/swc-plugin-workflow/transform/tests/fixture/unused-exports/output-workflow.js index 1137f50ed..5e774f86e 100644 --- a/packages/swc-plugin-workflow/transform/tests/fixture/unused-exports/output-workflow.js +++ b/packages/swc-plugin-workflow/transform/tests/fixture/unused-exports/output-workflow.js @@ -21,3 +21,9 @@ function internalHelper(value) { export function calculate(x) { return internalHelper(x); } +Object.defineProperty(processData, Symbol.for("WORKFLOW_STEP_FUNCTION_NAME"), { + value: "step//input.js//processData", + writable: false, + enumerable: false, + configurable: false +}); From c4cd86bda47ec1ad4fc07515a482e6b0c282ec85 Mon Sep 17 00:00:00 2001 From: Nathan Rajlich Date: Wed, 12 Nov 2025 00:15:50 -0800 Subject: [PATCH 05/23] Use nate's devalue fork --- packages/core/package.json | 2 +- packages/core/src/serialization.test.ts | 52 +++++++++ pnpm-lock.yaml | 140 ++++++++++++++++++------ 3 files changed, 162 insertions(+), 32 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index 9c16213bc..dbef7bd96 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -57,7 +57,7 @@ "@workflow/world-local": "workspace:*", "@workflow/world-vercel": "workspace:*", "debug": "^4.4.3", - "devalue": "^5.4.1", + "devalue": "TooTallNate/devalue#feat/func", "ms": "2.1.3", "nanoid": "^5.1.6", "seedrandom": "^3.0.5", diff --git a/packages/core/src/serialization.test.ts b/packages/core/src/serialization.test.ts index f142d02dc..f5c736261 100644 --- a/packages/core/src/serialization.test.ts +++ b/packages/core/src/serialization.test.ts @@ -9,6 +9,7 @@ import { dehydrateWorkflowReturnValue, getCommonRevivers, getStreamType, + getWorkflowReducers, hydrateWorkflowArguments, } from './serialization.js'; import { STEP_FUNCTION_NAME_SYMBOL, STREAM_NAME_SYMBOL } from './symbols.js'; @@ -860,4 +861,55 @@ describe('step function serialization', () => { expect(err?.message).toContain('Step function "nonExistentStep" not found'); expect(err?.message).toContain('Make sure the step function is registered'); }); + + it('should dehydrate step function passed as argument to a step', () => { + const stepName = 'step//workflows/test.ts//myStep'; + const stepFn = async (x: number) => x * 2; + + // Register the step function + registerStepFunction(stepName, stepFn); + + // Attach the symbol to the function (like the SWC compiler would) + Object.defineProperty(stepFn, STEP_FUNCTION_NAME_SYMBOL, { + value: stepName, + writable: false, + enumerable: false, + configurable: false, + }); + + // Simulate passing a step function as an argument within a workflow + // When calling a step from within a workflow context + const args = [stepFn, 42]; + + // This should serialize the step function by its name using the reducer + const dehydrated = dehydrateStepArguments(args, globalThis); + + // Verify it dehydrated successfully + expect(dehydrated).toBeDefined(); + expect(Array.isArray(dehydrated)).toBe(true); + // The dehydrated structure is the flattened format from devalue + // It should contain the step function serialized as its name + expect(dehydrated).toContain(stepName); + expect(dehydrated).toContain(42); + }); + + it('should serialize step function to name through reducer', () => { + const stepName = 'step//workflows/test.ts//anotherStep'; + const stepFn = async () => 'result'; + + // Attach the symbol to the function (like the SWC compiler would) + Object.defineProperty(stepFn, STEP_FUNCTION_NAME_SYMBOL, { + value: stepName, + writable: false, + enumerable: false, + configurable: false, + }); + + // Get the reducer and verify it detects the step function + const reducer = getWorkflowReducers(globalThis).StepFunction; + const result = reducer(stepFn); + + // Should return the step name + expect(result).toBe(stepName); + }); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 40608fb52..c7a074fb8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -445,8 +445,8 @@ importers: specifier: ^4.4.3 version: 4.4.3(supports-color@8.1.1) devalue: - specifier: ^5.4.1 - version: 5.4.1 + specifier: TooTallNate/devalue#feat/func + version: https://codeload.github.com/TooTallNate/devalue/tar.gz/94f4801da073739690df6cf7857fa72f2e5206d2 ms: specifier: 2.1.3 version: 2.1.3 @@ -573,14 +573,14 @@ importers: dependencies: '@nuxt/kit': specifier: ^4.2.0 - version: 4.2.0(magicast@0.3.5) + version: 4.2.0(magicast@0.5.1) '@workflow/nitro': specifier: workspace:* version: link:../nitro devDependencies: '@nuxt/module-builder': specifier: ^1.0.2 - version: 1.0.2(@nuxt/cli@3.29.3(magicast@0.3.5))(@vue/compiler-core@3.5.22)(esbuild@0.25.11)(typescript@5.9.3)(vue@3.5.22(typescript@5.9.3)) + version: 1.0.2(@nuxt/cli@3.29.3(magicast@0.5.1))(@vue/compiler-core@3.5.22)(esbuild@0.25.11)(typescript@5.9.3)(vue@3.5.22(typescript@5.9.3)) '@nuxt/schema': specifier: ^4.2.0 version: 4.2.0 @@ -592,7 +592,7 @@ importers: version: link:../tsconfig nuxt: specifier: ^4.0.0 - version: 4.2.0(@biomejs/biome@2.3.3)(@parcel/watcher@2.5.1)(@types/node@22.19.0)(@vercel/functions@3.1.4(@aws-sdk/credential-provider-web-identity@3.844.0))(@vue/compiler-sfc@3.5.22)(better-sqlite3@11.10.0)(db0@0.3.4(better-sqlite3@11.10.0)(drizzle-orm@0.31.4(@opentelemetry/api@1.9.0)(@types/react@19.1.13)(better-sqlite3@11.10.0)(pg@8.16.3)(postgres@3.4.7)(react@19.2.0)))(drizzle-orm@0.31.4(@opentelemetry/api@1.9.0)(@types/react@19.1.13)(better-sqlite3@11.10.0)(pg@8.16.3)(postgres@3.4.7)(react@19.2.0))(eslint@9.38.0(jiti@2.6.1))(ioredis@5.8.2)(lightningcss@1.30.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.52.5)(terser@5.44.0)(tsx@4.20.6)(typescript@5.9.3)(vite@7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))(yaml@2.8.1) + version: 4.2.0(@biomejs/biome@2.3.3)(@parcel/watcher@2.5.1)(@types/node@22.19.0)(@vercel/functions@3.1.4(@aws-sdk/credential-provider-web-identity@3.844.0))(@vue/compiler-sfc@3.5.22)(better-sqlite3@11.10.0)(db0@0.3.4(better-sqlite3@11.10.0)(drizzle-orm@0.31.4(@opentelemetry/api@1.9.0)(@types/react@19.1.13)(better-sqlite3@11.10.0)(pg@8.16.3)(postgres@3.4.7)(react@19.2.0)))(drizzle-orm@0.31.4(@opentelemetry/api@1.9.0)(@types/react@19.1.13)(better-sqlite3@11.10.0)(pg@8.16.3)(postgres@3.4.7)(react@19.2.0))(eslint@9.38.0(jiti@2.6.1))(ioredis@5.8.2)(lightningcss@1.30.1)(magicast@0.5.1)(optionator@0.9.4)(rollup@4.52.5)(terser@5.44.0)(tsx@4.20.6)(typescript@5.9.3)(vite@7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))(yaml@2.8.1) packages/sveltekit: dependencies: @@ -6429,12 +6429,13 @@ packages: detect-node-es@1.1.0: resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} - devalue@5.4.1: - resolution: {integrity: sha512-YtoaOfsqjbZQKGIMRYDWKjUmSB4VJ/RElB+bXZawQAQYAo4xu08GKTMVlsZDTF6R2MbAgjcAQRPI5eIyRAT2OQ==} - devalue@5.4.2: resolution: {integrity: sha512-MwPZTKEPK2k8Qgfmqrd48ZKVvzSQjgW0lXLxiIBA8dQjtf/6mw6pggHNLcyDKyf+fI6eXxlQwPsfaCMTU5U+Bw==} + devalue@https://codeload.github.com/TooTallNate/devalue/tar.gz/94f4801da073739690df6cf7857fa72f2e5206d2: + resolution: {tarball: https://codeload.github.com/TooTallNate/devalue/tar.gz/94f4801da073739690df6cf7857fa72f2e5206d2} + version: 5.4.2 + devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} @@ -11356,10 +11357,10 @@ snapshots: '@date-fns/tz@1.4.1': {} - '@dxup/nuxt@0.2.1(magicast@0.3.5)': + '@dxup/nuxt@0.2.1(magicast@0.5.1)': dependencies: '@dxup/unimport': 0.1.1 - '@nuxt/kit': 4.2.0(magicast@0.3.5) + '@nuxt/kit': 4.2.0(magicast@0.5.1) chokidar: 4.0.3 pathe: 2.0.3 tinyglobby: 0.2.15 @@ -12158,6 +12159,38 @@ snapshots: transitivePeerDependencies: - magicast + '@nuxt/cli@3.29.3(magicast@0.5.1)': + dependencies: + c12: 3.3.1(magicast@0.5.1) + citty: 0.1.6 + clipboardy: 5.0.0 + confbox: 0.2.2 + consola: 3.4.2 + defu: 6.1.4 + exsolve: 1.0.7 + fuse.js: 7.1.0 + get-port-please: 3.2.0 + giget: 2.0.0 + h3: 1.15.4 + jiti: 2.6.1 + listhen: 1.9.0 + nypm: 0.6.2 + ofetch: 1.4.1 + ohash: 2.0.11 + pathe: 2.0.3 + perfect-debounce: 2.0.0 + pkg-types: 2.3.0 + scule: 1.3.0 + semver: 7.7.3 + srvx: 0.8.16 + std-env: 3.10.0 + tinyexec: 1.0.1 + ufo: 1.6.1 + undici: 7.16.0 + youch: 4.1.0-beta.11 + transitivePeerDependencies: + - magicast + '@nuxt/devalue@2.0.2': {} '@nuxt/devtools-kit@2.6.5(magicast@0.3.5)(vite@7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))': @@ -12248,6 +12281,34 @@ snapshots: transitivePeerDependencies: - magicast + '@nuxt/kit@3.19.3(magicast@0.5.1)': + dependencies: + c12: 3.3.1(magicast@0.5.1) + consola: 3.4.2 + defu: 6.1.4 + destr: 2.0.5 + errx: 0.1.0 + exsolve: 1.0.7 + ignore: 7.0.5 + jiti: 2.6.1 + klona: 2.0.6 + knitwork: 1.2.0 + mlly: 1.8.0 + ohash: 2.0.11 + pathe: 2.0.3 + pkg-types: 2.3.0 + rc9: 2.1.2 + scule: 1.3.0 + semver: 7.7.3 + std-env: 3.10.0 + tinyglobby: 0.2.15 + ufo: 1.6.1 + unctx: 2.4.1 + unimport: 5.5.0 + untyped: 2.0.0 + transitivePeerDependencies: + - magicast + '@nuxt/kit@4.1.3(magicast@0.3.5)': dependencies: c12: 3.3.1(magicast@0.3.5) @@ -12275,9 +12336,9 @@ snapshots: transitivePeerDependencies: - magicast - '@nuxt/kit@4.2.0(magicast@0.3.5)': + '@nuxt/kit@4.2.0(magicast@0.5.1)': dependencies: - c12: 3.3.1(magicast@0.3.5) + c12: 3.3.1(magicast@0.5.1) consola: 3.4.2 defu: 6.1.4 destr: 2.0.5 @@ -12300,9 +12361,9 @@ snapshots: transitivePeerDependencies: - magicast - '@nuxt/module-builder@1.0.2(@nuxt/cli@3.29.3(magicast@0.3.5))(@vue/compiler-core@3.5.22)(esbuild@0.25.11)(typescript@5.9.3)(vue@3.5.22(typescript@5.9.3))': + '@nuxt/module-builder@1.0.2(@nuxt/cli@3.29.3(magicast@0.5.1))(@vue/compiler-core@3.5.22)(esbuild@0.25.11)(typescript@5.9.3)(vue@3.5.22(typescript@5.9.3))': dependencies: - '@nuxt/cli': 3.29.3(magicast@0.3.5) + '@nuxt/cli': 3.29.3(magicast@0.5.1) citty: 0.1.6 consola: 3.4.2 defu: 6.1.4 @@ -12323,10 +12384,10 @@ snapshots: - vue - vue-tsc - '@nuxt/nitro-server@4.2.0(467f0d7dd4deb2b48ae371f4206e5247)': + '@nuxt/nitro-server@4.2.0(c5ccce64900d7de6391abc767fe18400)': dependencies: '@nuxt/devalue': 2.0.2 - '@nuxt/kit': 4.2.0(magicast@0.3.5) + '@nuxt/kit': 4.2.0(magicast@0.5.1) '@unhead/vue': 2.0.19(vue@3.5.22(typescript@5.9.3)) '@vue/shared': 3.5.22 consola: 3.4.2 @@ -12341,7 +12402,7 @@ snapshots: klona: 2.0.6 mocked-exports: 0.1.1 nitropack: 2.12.9(@vercel/functions@3.1.4(@aws-sdk/credential-provider-web-identity@3.844.0))(better-sqlite3@11.10.0)(drizzle-orm@0.31.4(@opentelemetry/api@1.9.0)(@types/react@19.1.13)(better-sqlite3@11.10.0)(pg@8.16.3)(postgres@3.4.7)(react@19.2.0)) - nuxt: 4.2.0(@biomejs/biome@2.3.3)(@parcel/watcher@2.5.1)(@types/node@22.19.0)(@vercel/functions@3.1.4(@aws-sdk/credential-provider-web-identity@3.844.0))(@vue/compiler-sfc@3.5.22)(better-sqlite3@11.10.0)(db0@0.3.4(better-sqlite3@11.10.0)(drizzle-orm@0.31.4(@opentelemetry/api@1.9.0)(@types/react@19.1.13)(better-sqlite3@11.10.0)(pg@8.16.3)(postgres@3.4.7)(react@19.2.0)))(drizzle-orm@0.31.4(@opentelemetry/api@1.9.0)(@types/react@19.1.13)(better-sqlite3@11.10.0)(pg@8.16.3)(postgres@3.4.7)(react@19.2.0))(eslint@9.38.0(jiti@2.6.1))(ioredis@5.8.2)(lightningcss@1.30.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.52.5)(terser@5.44.0)(tsx@4.20.6)(typescript@5.9.3)(vite@7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))(yaml@2.8.1) + nuxt: 4.2.0(@biomejs/biome@2.3.3)(@parcel/watcher@2.5.1)(@types/node@22.19.0)(@vercel/functions@3.1.4(@aws-sdk/credential-provider-web-identity@3.844.0))(@vue/compiler-sfc@3.5.22)(better-sqlite3@11.10.0)(db0@0.3.4(better-sqlite3@11.10.0)(drizzle-orm@0.31.4(@opentelemetry/api@1.9.0)(@types/react@19.1.13)(better-sqlite3@11.10.0)(pg@8.16.3)(postgres@3.4.7)(react@19.2.0)))(drizzle-orm@0.31.4(@opentelemetry/api@1.9.0)(@types/react@19.1.13)(better-sqlite3@11.10.0)(pg@8.16.3)(postgres@3.4.7)(react@19.2.0))(eslint@9.38.0(jiti@2.6.1))(ioredis@5.8.2)(lightningcss@1.30.1)(magicast@0.5.1)(optionator@0.9.4)(rollup@4.52.5)(terser@5.44.0)(tsx@4.20.6)(typescript@5.9.3)(vite@7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))(yaml@2.8.1) pathe: 2.0.3 pkg-types: 2.3.0 radix3: 1.1.2 @@ -12422,6 +12483,23 @@ snapshots: transitivePeerDependencies: - magicast + '@nuxt/telemetry@2.6.6(magicast@0.5.1)': + dependencies: + '@nuxt/kit': 3.19.3(magicast@0.5.1) + citty: 0.1.6 + consola: 3.4.2 + destr: 2.0.5 + dotenv: 16.6.1 + git-url-parse: 16.1.0 + is-docker: 3.0.0 + ofetch: 1.4.1 + package-manager-detector: 1.5.0 + pathe: 2.0.3 + rc9: 2.1.2 + std-env: 3.10.0 + transitivePeerDependencies: + - magicast + '@nuxt/vite-builder@4.1.3(@biomejs/biome@2.3.3)(@types/node@22.19.0)(eslint@9.38.0(jiti@2.6.1))(lightningcss@1.30.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.52.5)(terser@5.44.0)(tsx@4.20.6)(typescript@5.9.3)(vue@3.5.22(typescript@5.9.3))(yaml@2.8.1)': dependencies: '@nuxt/kit': 4.1.3(magicast@0.3.5) @@ -12479,9 +12557,9 @@ snapshots: - vue-tsc - yaml - '@nuxt/vite-builder@4.2.0(6ae0ec26ed42ec2260ee0e24fabd45a0)': + '@nuxt/vite-builder@4.2.0(ddfcec0b6fb0741c3f54ef608f6a030d)': dependencies: - '@nuxt/kit': 4.2.0(magicast@0.3.5) + '@nuxt/kit': 4.2.0(magicast@0.5.1) '@rollup/plugin-replace': 6.0.2(rollup@4.52.5) '@vitejs/plugin-vue': 6.0.1(vite@7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))(vue@3.5.22(typescript@5.9.3)) '@vitejs/plugin-vue-jsx': 5.1.1(vite@7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))(vue@3.5.22(typescript@5.9.3)) @@ -12499,7 +12577,7 @@ snapshots: magic-string: 0.30.21 mlly: 1.8.0 mocked-exports: 0.1.1 - nuxt: 4.2.0(@biomejs/biome@2.3.3)(@parcel/watcher@2.5.1)(@types/node@22.19.0)(@vercel/functions@3.1.4(@aws-sdk/credential-provider-web-identity@3.844.0))(@vue/compiler-sfc@3.5.22)(better-sqlite3@11.10.0)(db0@0.3.4(better-sqlite3@11.10.0)(drizzle-orm@0.31.4(@opentelemetry/api@1.9.0)(@types/react@19.1.13)(better-sqlite3@11.10.0)(pg@8.16.3)(postgres@3.4.7)(react@19.2.0)))(drizzle-orm@0.31.4(@opentelemetry/api@1.9.0)(@types/react@19.1.13)(better-sqlite3@11.10.0)(pg@8.16.3)(postgres@3.4.7)(react@19.2.0))(eslint@9.38.0(jiti@2.6.1))(ioredis@5.8.2)(lightningcss@1.30.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.52.5)(terser@5.44.0)(tsx@4.20.6)(typescript@5.9.3)(vite@7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))(yaml@2.8.1) + nuxt: 4.2.0(@biomejs/biome@2.3.3)(@parcel/watcher@2.5.1)(@types/node@22.19.0)(@vercel/functions@3.1.4(@aws-sdk/credential-provider-web-identity@3.844.0))(@vue/compiler-sfc@3.5.22)(better-sqlite3@11.10.0)(db0@0.3.4(better-sqlite3@11.10.0)(drizzle-orm@0.31.4(@opentelemetry/api@1.9.0)(@types/react@19.1.13)(better-sqlite3@11.10.0)(pg@8.16.3)(postgres@3.4.7)(react@19.2.0)))(drizzle-orm@0.31.4(@opentelemetry/api@1.9.0)(@types/react@19.1.13)(better-sqlite3@11.10.0)(pg@8.16.3)(postgres@3.4.7)(react@19.2.0))(eslint@9.38.0(jiti@2.6.1))(ioredis@5.8.2)(lightningcss@1.30.1)(magicast@0.5.1)(optionator@0.9.4)(rollup@4.52.5)(terser@5.44.0)(tsx@4.20.6)(typescript@5.9.3)(vite@7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))(yaml@2.8.1) pathe: 2.0.3 pkg-types: 2.3.0 postcss: 8.5.6 @@ -16334,10 +16412,10 @@ snapshots: detect-node-es@1.1.0: {} - devalue@5.4.1: {} - devalue@5.4.2: {} + devalue@https://codeload.github.com/TooTallNate/devalue/tar.gz/94f4801da073739690df6cf7857fa72f2e5206d2: {} + devlop@1.1.0: dependencies: dequal: 2.0.3 @@ -18876,7 +18954,7 @@ snapshots: cookie-es: 2.0.0 defu: 6.1.4 destr: 2.0.5 - devalue: 5.4.1 + devalue: 5.4.2 errx: 0.1.0 esbuild: 0.25.11 escape-string-regexp: 5.0.0 @@ -18984,19 +19062,19 @@ snapshots: - xml2js - yaml - nuxt@4.2.0(@biomejs/biome@2.3.3)(@parcel/watcher@2.5.1)(@types/node@22.19.0)(@vercel/functions@3.1.4(@aws-sdk/credential-provider-web-identity@3.844.0))(@vue/compiler-sfc@3.5.22)(better-sqlite3@11.10.0)(db0@0.3.4(better-sqlite3@11.10.0)(drizzle-orm@0.31.4(@opentelemetry/api@1.9.0)(@types/react@19.1.13)(better-sqlite3@11.10.0)(pg@8.16.3)(postgres@3.4.7)(react@19.2.0)))(drizzle-orm@0.31.4(@opentelemetry/api@1.9.0)(@types/react@19.1.13)(better-sqlite3@11.10.0)(pg@8.16.3)(postgres@3.4.7)(react@19.2.0))(eslint@9.38.0(jiti@2.6.1))(ioredis@5.8.2)(lightningcss@1.30.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.52.5)(terser@5.44.0)(tsx@4.20.6)(typescript@5.9.3)(vite@7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))(yaml@2.8.1): + nuxt@4.2.0(@biomejs/biome@2.3.3)(@parcel/watcher@2.5.1)(@types/node@22.19.0)(@vercel/functions@3.1.4(@aws-sdk/credential-provider-web-identity@3.844.0))(@vue/compiler-sfc@3.5.22)(better-sqlite3@11.10.0)(db0@0.3.4(better-sqlite3@11.10.0)(drizzle-orm@0.31.4(@opentelemetry/api@1.9.0)(@types/react@19.1.13)(better-sqlite3@11.10.0)(pg@8.16.3)(postgres@3.4.7)(react@19.2.0)))(drizzle-orm@0.31.4(@opentelemetry/api@1.9.0)(@types/react@19.1.13)(better-sqlite3@11.10.0)(pg@8.16.3)(postgres@3.4.7)(react@19.2.0))(eslint@9.38.0(jiti@2.6.1))(ioredis@5.8.2)(lightningcss@1.30.1)(magicast@0.5.1)(optionator@0.9.4)(rollup@4.52.5)(terser@5.44.0)(tsx@4.20.6)(typescript@5.9.3)(vite@7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))(yaml@2.8.1): dependencies: - '@dxup/nuxt': 0.2.1(magicast@0.3.5) - '@nuxt/cli': 3.29.3(magicast@0.3.5) + '@dxup/nuxt': 0.2.1(magicast@0.5.1) + '@nuxt/cli': 3.29.3(magicast@0.5.1) '@nuxt/devtools': 2.6.5(vite@7.1.12(@types/node@22.19.0)(jiti@2.6.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))(vue@3.5.22(typescript@5.9.3)) - '@nuxt/kit': 4.2.0(magicast@0.3.5) - '@nuxt/nitro-server': 4.2.0(467f0d7dd4deb2b48ae371f4206e5247) + '@nuxt/kit': 4.2.0(magicast@0.5.1) + '@nuxt/nitro-server': 4.2.0(c5ccce64900d7de6391abc767fe18400) '@nuxt/schema': 4.2.0 - '@nuxt/telemetry': 2.6.6(magicast@0.3.5) - '@nuxt/vite-builder': 4.2.0(6ae0ec26ed42ec2260ee0e24fabd45a0) + '@nuxt/telemetry': 2.6.6(magicast@0.5.1) + '@nuxt/vite-builder': 4.2.0(ddfcec0b6fb0741c3f54ef608f6a030d) '@unhead/vue': 2.0.19(vue@3.5.22(typescript@5.9.3)) '@vue/shared': 3.5.22 - c12: 3.3.1(magicast@0.3.5) + c12: 3.3.1(magicast@0.5.1) chokidar: 4.0.3 compatx: 0.2.0 consola: 3.4.2 From 7c4e850163065a472c00d4b4b0403c4a88d84270 Mon Sep 17 00:00:00 2001 From: Nathan Rajlich Date: Wed, 12 Nov 2025 00:26:47 -0800 Subject: [PATCH 06/23] Fix build --- packages/core/package.json | 2 +- pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index dbef7bd96..cc124037e 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -57,7 +57,7 @@ "@workflow/world-local": "workspace:*", "@workflow/world-vercel": "workspace:*", "debug": "^4.4.3", - "devalue": "TooTallNate/devalue#feat/func", + "devalue": "TooTallNate/devalue#feat/func-with-types", "ms": "2.1.3", "nanoid": "^5.1.6", "seedrandom": "^3.0.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c7a074fb8..31c6811d8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -445,8 +445,8 @@ importers: specifier: ^4.4.3 version: 4.4.3(supports-color@8.1.1) devalue: - specifier: TooTallNate/devalue#feat/func - version: https://codeload.github.com/TooTallNate/devalue/tar.gz/94f4801da073739690df6cf7857fa72f2e5206d2 + specifier: TooTallNate/devalue#feat/func-with-types + version: https://codeload.github.com/TooTallNate/devalue/tar.gz/acaf804d8b152675c06543786defc985402a48c9 ms: specifier: 2.1.3 version: 2.1.3 @@ -6432,8 +6432,8 @@ packages: devalue@5.4.2: resolution: {integrity: sha512-MwPZTKEPK2k8Qgfmqrd48ZKVvzSQjgW0lXLxiIBA8dQjtf/6mw6pggHNLcyDKyf+fI6eXxlQwPsfaCMTU5U+Bw==} - devalue@https://codeload.github.com/TooTallNate/devalue/tar.gz/94f4801da073739690df6cf7857fa72f2e5206d2: - resolution: {tarball: https://codeload.github.com/TooTallNate/devalue/tar.gz/94f4801da073739690df6cf7857fa72f2e5206d2} + devalue@https://codeload.github.com/TooTallNate/devalue/tar.gz/acaf804d8b152675c06543786defc985402a48c9: + resolution: {tarball: https://codeload.github.com/TooTallNate/devalue/tar.gz/acaf804d8b152675c06543786defc985402a48c9} version: 5.4.2 devlop@1.1.0: @@ -16414,7 +16414,7 @@ snapshots: devalue@5.4.2: {} - devalue@https://codeload.github.com/TooTallNate/devalue/tar.gz/94f4801da073739690df6cf7857fa72f2e5206d2: {} + devalue@https://codeload.github.com/TooTallNate/devalue/tar.gz/acaf804d8b152675c06543786defc985402a48c9: {} devlop@1.1.0: dependencies: From 52d120609621dae48c94d59549f432bd4c2472db Mon Sep 17 00:00:00 2001 From: Nathan Rajlich Date: Wed, 12 Nov 2025 00:55:17 -0800 Subject: [PATCH 07/23] Changeset --- .changeset/tidy-states-see.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/tidy-states-see.md diff --git a/.changeset/tidy-states-see.md b/.changeset/tidy-states-see.md new file mode 100644 index 000000000..d77555507 --- /dev/null +++ b/.changeset/tidy-states-see.md @@ -0,0 +1,6 @@ +--- +"@workflow/swc-plugin": patch +"@workflow/core": patch +--- + +Support passing step function references across serialization layer From 42f7adc2452e8ca1260ef51fff94a484ceef76f9 Mon Sep 17 00:00:00 2001 From: Nathan Rajlich Date: Wed, 12 Nov 2025 00:56:43 -0800 Subject: [PATCH 08/23] . --- .changeset/tidy-states-see.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/tidy-states-see.md b/.changeset/tidy-states-see.md index d77555507..0b26a975b 100644 --- a/.changeset/tidy-states-see.md +++ b/.changeset/tidy-states-see.md @@ -3,4 +3,4 @@ "@workflow/core": patch --- -Support passing step function references across serialization layer +Support serializing step function references From e7a491d84b7f664f04b81091591f84699ce21c7d Mon Sep 17 00:00:00 2001 From: Nathan Rajlich Date: Wed, 12 Nov 2025 01:50:11 -0800 Subject: [PATCH 09/23] Update packages/core/src/serialization.ts Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com> --- packages/core/src/serialization.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/serialization.ts b/packages/core/src/serialization.ts index 73f856d23..f50fca7ae 100644 --- a/packages/core/src/serialization.ts +++ b/packages/core/src/serialization.ts @@ -281,8 +281,8 @@ function getCommonReducers(global: Record = globalThis) { StepFunction: (value) => { if (typeof value !== 'function') return false; const stepName = value[STEP_FUNCTION_NAME_SYMBOL]; - // Must return a truthy value; devalue will reject if we return false for a function - return stepName || false; + // Avoid returning a falsy value for empty string step names + return typeof stepName === 'string' ? stepName : false; }, URL: (value) => value instanceof global.URL && value.href, URLSearchParams: (value) => { From 10b77c49e87800f97ddf610648e86a755195b8cf Mon Sep 17 00:00:00 2001 From: Nathan Rajlich Date: Wed, 12 Nov 2025 09:59:47 -0800 Subject: [PATCH 10/23] Use latest devalue --- packages/core/package.json | 2 +- pnpm-lock.yaml | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index b95c0e0a5..1e96167a9 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -57,7 +57,7 @@ "@workflow/world-local": "workspace:*", "@workflow/world-vercel": "workspace:*", "debug": "^4.4.3", - "devalue": "TooTallNate/devalue#feat/func-with-types", + "devalue": "^5.5.0", "ms": "2.1.3", "nanoid": "^5.1.6", "seedrandom": "^3.0.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4156c4f28..09c4c2d8f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -445,8 +445,8 @@ importers: specifier: ^4.4.3 version: 4.4.3(supports-color@8.1.1) devalue: - specifier: TooTallNate/devalue#feat/func-with-types - version: https://codeload.github.com/TooTallNate/devalue/tar.gz/acaf804d8b152675c06543786defc985402a48c9 + specifier: ^5.5.0 + version: 5.5.0 ms: specifier: 2.1.3 version: 2.1.3 @@ -6303,9 +6303,8 @@ packages: devalue@5.4.2: resolution: {integrity: sha512-MwPZTKEPK2k8Qgfmqrd48ZKVvzSQjgW0lXLxiIBA8dQjtf/6mw6pggHNLcyDKyf+fI6eXxlQwPsfaCMTU5U+Bw==} - devalue@https://codeload.github.com/TooTallNate/devalue/tar.gz/acaf804d8b152675c06543786defc985402a48c9: - resolution: {tarball: https://codeload.github.com/TooTallNate/devalue/tar.gz/acaf804d8b152675c06543786defc985402a48c9} - version: 5.4.2 + devalue@5.5.0: + resolution: {integrity: sha512-69sM5yrHfFLJt0AZ9QqZXGCPfJ7fQjvpln3Rq5+PS03LD32Ost1Q9N+eEnaQwGRIriKkMImXD56ocjQmfjbV3w==} devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} @@ -16231,7 +16230,7 @@ snapshots: devalue@5.4.2: {} - devalue@https://codeload.github.com/TooTallNate/devalue/tar.gz/acaf804d8b152675c06543786defc985402a48c9: {} + devalue@5.5.0: {} devlop@1.1.0: dependencies: From 1480889b1ec7c7843fc596143bb9c07502f5b1c8 Mon Sep 17 00:00:00 2001 From: Nathan Rajlich Date: Wed, 12 Nov 2025 10:20:39 -0800 Subject: [PATCH 11/23] Isolate test --- package.json | 2 +- packages/core/e2e/e2e.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 5e80e2d39..a36409e5d 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "test": "turbo test", "clean": "turbo clean", "typecheck": "turbo typecheck", - "test:e2e": "vitest run packages/core/e2e/e2e.test.ts", + "test:e2e": "vitest run --allowOnly packages/core/e2e/e2e.test.ts", "lint": "biome check", "format": "biome format --write", "changeset": "changeset", diff --git a/packages/core/e2e/e2e.test.ts b/packages/core/e2e/e2e.test.ts index 41aa5551e..d17f636f9 100644 --- a/packages/core/e2e/e2e.test.ts +++ b/packages/core/e2e/e2e.test.ts @@ -611,7 +611,7 @@ describe('e2e', () => { } ); - test( + test.only( 'stepFunctionPassingWorkflow - step function references can be passed as arguments', { timeout: 60_000 }, async () => { From c60de66f24f9e1411142e5d613a9c6f7592988b1 Mon Sep 17 00:00:00 2001 From: Nathan Rajlich Date: Wed, 12 Nov 2025 11:01:48 -0800 Subject: [PATCH 12/23] Add step name transform to arrow function steps --- .../swc-plugin-workflow/transform/src/lib.rs | 22 +++++++++++++++++++ .../module-level-step/output-workflow.js | 6 +++++ .../step-arrow-function/output-workflow.js | 6 +++++ .../output-workflow.js | 6 +++++ 4 files changed, 40 insertions(+) diff --git a/packages/swc-plugin-workflow/transform/src/lib.rs b/packages/swc-plugin-workflow/transform/src/lib.rs index 9edcaeec0..25a4afe74 100644 --- a/packages/swc-plugin-workflow/transform/src/lib.rs +++ b/packages/swc-plugin-workflow/transform/src/lib.rs @@ -2160,6 +2160,28 @@ impl VisitMut for StepTransform { ), ); } + } else if let Decl::Var(var_decl) = &export_decl.decl { + // Handle exported variable declarations like `export const stepArrow = async () => {}` + for declarator in &var_decl.decls { + if let Pat::Ident(binding) = &declarator.name { + let name = binding.id.sym.to_string(); + if step_functions.contains(&name) { + if let Some(init) = &declarator.init { + let span = match &**init { + Expr::Fn(fn_expr) => fn_expr.function.span, + Expr::Arrow(arrow_expr) => arrow_expr.span, + _ => declarator.span, + }; + step_marking_statements.push( + self.create_step_function_marking( + &name, + span, + ), + ); + } + } + } + } } } _ => {} diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/module-level-step/output-workflow.js b/packages/swc-plugin-workflow/transform/tests/fixture/module-level-step/output-workflow.js index 8c5a80455..e96ec552d 100644 --- a/packages/swc-plugin-workflow/transform/tests/fixture/module-level-step/output-workflow.js +++ b/packages/swc-plugin-workflow/transform/tests/fixture/module-level-step/output-workflow.js @@ -13,3 +13,9 @@ Object.defineProperty(step, Symbol.for("WORKFLOW_STEP_FUNCTION_NAME"), { enumerable: false, configurable: false }); +Object.defineProperty(stepArrow, Symbol.for("WORKFLOW_STEP_FUNCTION_NAME"), { + value: "step//input.js//stepArrow", + writable: false, + enumerable: false, + configurable: false +}); diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/step-arrow-function/output-workflow.js b/packages/swc-plugin-workflow/transform/tests/fixture/step-arrow-function/output-workflow.js index 115cbcb4d..01301f1dc 100644 --- a/packages/swc-plugin-workflow/transform/tests/fixture/step-arrow-function/output-workflow.js +++ b/packages/swc-plugin-workflow/transform/tests/fixture/step-arrow-function/output-workflow.js @@ -1,2 +1,8 @@ /**__internal_workflows{"steps":{"input.js":{"multiply":{"stepId":"step//input.js//multiply"}}}}*/; export const multiply = async (a, b)=>globalThis[Symbol.for("WORKFLOW_USE_STEP")]("step//input.js//multiply")(a, b); +Object.defineProperty(multiply, Symbol.for("WORKFLOW_STEP_FUNCTION_NAME"), { + value: "step//input.js//multiply", + writable: false, + enumerable: false, + configurable: false +}); diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/unused-variables-and-types/output-workflow.js b/packages/swc-plugin-workflow/transform/tests/fixture/unused-variables-and-types/output-workflow.js index 1ecf355c6..522771658 100644 --- a/packages/swc-plugin-workflow/transform/tests/fixture/unused-variables-and-types/output-workflow.js +++ b/packages/swc-plugin-workflow/transform/tests/fixture/unused-variables-and-types/output-workflow.js @@ -8,3 +8,9 @@ export const sendRecipientEmail = async ({ recipientEmail, cardImage, cardText, export function normalFunction() { return 'this stays because it is exported'; } +Object.defineProperty(sendRecipientEmail, Symbol.for("WORKFLOW_STEP_FUNCTION_NAME"), { + value: "step//input.js//sendRecipientEmail", + writable: false, + enumerable: false, + configurable: false +}); From 57e4899bb29b432dfbae9a0e2834b1d02a3fb0cb Mon Sep 17 00:00:00 2001 From: Nathan Rajlich Date: Wed, 12 Nov 2025 11:21:19 -0800 Subject: [PATCH 13/23] . --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 534ad8790..34b572d68 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -231,7 +231,7 @@ jobs: run: pnpm install --frozen-lockfile - name: Run Initial Build - run: pnpm turbo run build --filter='!./workbench/*' + run: pnpm turbo run build --force --filter='!./workbench/*' - name: Run Build Tests run: pnpm vitest run packages/core/e2e/local-build.test.ts From 921ae65411850c1ebe34019ab66cf7d012ccefbe Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Wed, 12 Nov 2025 12:44:41 -0800 Subject: [PATCH 14/23] update tsconfig --- packages/nuxt/tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nuxt/tsconfig.json b/packages/nuxt/tsconfig.json index 1760a52dd..94680962f 100644 --- a/packages/nuxt/tsconfig.json +++ b/packages/nuxt/tsconfig.json @@ -1,4 +1,4 @@ { - "extends": "./.nuxt/tsconfig.json", + "extends": "@workflow/tsconfig", "exclude": ["dist", "node_modules"] } From 344439c5c79008dee696cf368904865debd8170d Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Wed, 12 Nov 2025 12:55:32 -0800 Subject: [PATCH 15/23] update --- packages/nuxt/tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nuxt/tsconfig.json b/packages/nuxt/tsconfig.json index 94680962f..a0cea69f2 100644 --- a/packages/nuxt/tsconfig.json +++ b/packages/nuxt/tsconfig.json @@ -1,4 +1,4 @@ { - "extends": "@workflow/tsconfig", + "extends": "@workflow/tsconfig/base.json", "exclude": ["dist", "node_modules"] } From de91783f28330dfabfd3ad8613d47ca1ccf15bfd Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Wed, 12 Nov 2025 12:57:16 -0800 Subject: [PATCH 16/23] update --- packages/nuxt/tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nuxt/tsconfig.json b/packages/nuxt/tsconfig.json index a0cea69f2..322cf8315 100644 --- a/packages/nuxt/tsconfig.json +++ b/packages/nuxt/tsconfig.json @@ -1,4 +1,4 @@ { "extends": "@workflow/tsconfig/base.json", - "exclude": ["dist", "node_modules"] + "exclude": ["dist", "node_modules", ".nuxt"] } From c655f2b7a34abcbfd0554c3950f1e2fa38ac1fd7 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Wed, 12 Nov 2025 13:10:58 -0800 Subject: [PATCH 17/23] fix --- packages/nuxt/src/module.ts | 3 ++- packages/nuxt/tsconfig.json | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/nuxt/src/module.ts b/packages/nuxt/src/module.ts index 60f2e43af..cab0c6965 100644 --- a/packages/nuxt/src/module.ts +++ b/packages/nuxt/src/module.ts @@ -1,4 +1,5 @@ import { defineNuxtModule } from '@nuxt/kit'; +import type { NuxtModule } from '@nuxt/schema'; import type { ModuleOptions as NitroModuleOptions } from '@workflow/nitro'; // Module options TypeScript interface definition @@ -30,4 +31,4 @@ export default defineNuxtModule({ nuxt.options.nitro.modules.push('@workflow/nitro'); } }, -}); +}) satisfies NuxtModule; diff --git a/packages/nuxt/tsconfig.json b/packages/nuxt/tsconfig.json index 322cf8315..b06d679c1 100644 --- a/packages/nuxt/tsconfig.json +++ b/packages/nuxt/tsconfig.json @@ -1,4 +1,8 @@ { "extends": "@workflow/tsconfig/base.json", - "exclude": ["dist", "node_modules", ".nuxt"] + "exclude": ["dist", "node_modules"], + "compilerOptions": { + "declaration": false, + "declarationMap": false + } } From a5f85e4c71f1963f7c1d08199d15bc3523cb2b22 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Wed, 12 Nov 2025 13:40:42 -0800 Subject: [PATCH 18/23] fix --- packages/core/package.json | 2 +- pnpm-lock.yaml | 17 ++++++----------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index 1e96167a9..ab1fc1344 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -57,7 +57,7 @@ "@workflow/world-local": "workspace:*", "@workflow/world-vercel": "workspace:*", "debug": "^4.4.3", - "devalue": "^5.5.0", + "devalue": "5.5.0", "ms": "2.1.3", "nanoid": "^5.1.6", "seedrandom": "^3.0.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 09c4c2d8f..01a938d1d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -445,7 +445,7 @@ importers: specifier: ^4.4.3 version: 4.4.3(supports-color@8.1.1) devalue: - specifier: ^5.5.0 + specifier: 5.5.0 version: 5.5.0 ms: specifier: 2.1.3 @@ -6300,9 +6300,6 @@ packages: detect-node-es@1.1.0: resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} - devalue@5.4.2: - resolution: {integrity: sha512-MwPZTKEPK2k8Qgfmqrd48ZKVvzSQjgW0lXLxiIBA8dQjtf/6mw6pggHNLcyDKyf+fI6eXxlQwPsfaCMTU5U+Bw==} - devalue@5.5.0: resolution: {integrity: sha512-69sM5yrHfFLJt0AZ9QqZXGCPfJ7fQjvpln3Rq5+PS03LD32Ost1Q9N+eEnaQwGRIriKkMImXD56ocjQmfjbV3w==} @@ -12209,7 +12206,7 @@ snapshots: consola: 3.4.2 defu: 6.1.4 destr: 2.0.5 - devalue: 5.4.2 + devalue: 5.5.0 errx: 0.1.0 escape-string-regexp: 5.0.0 exsolve: 1.0.7 @@ -14499,7 +14496,7 @@ snapshots: '@types/cookie': 0.6.0 acorn: 8.15.0 cookie: 0.6.0 - devalue: 5.4.2 + devalue: 5.5.0 esm-env: 1.2.2 kleur: 4.1.5 magic-string: 0.30.21 @@ -14521,7 +14518,7 @@ snapshots: '@types/cookie': 0.6.0 acorn: 8.15.0 cookie: 0.6.0 - devalue: 5.4.2 + devalue: 5.5.0 esm-env: 1.2.2 kleur: 4.1.5 magic-string: 0.30.21 @@ -16228,8 +16225,6 @@ snapshots: detect-node-es@1.1.0: {} - devalue@5.4.2: {} - devalue@5.5.0: {} devlop@1.1.0: @@ -18753,7 +18748,7 @@ snapshots: cookie-es: 2.0.0 defu: 6.1.4 destr: 2.0.5 - devalue: 5.4.2 + devalue: 5.5.0 errx: 0.1.0 esbuild: 0.25.11 escape-string-regexp: 5.0.0 @@ -18880,7 +18875,7 @@ snapshots: cookie-es: 2.0.0 defu: 6.1.4 destr: 2.0.5 - devalue: 5.4.2 + devalue: 5.5.0 errx: 0.1.0 escape-string-regexp: 5.0.0 exsolve: 1.0.7 From a16ee81579b306c446a66b922136c9f22e188a62 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Wed, 12 Nov 2025 13:50:01 -0800 Subject: [PATCH 19/23] debug stuff --- .github/workflows/tests.yml | 2 +- package.json | 2 +- packages/core/e2e/e2e.test.ts | 2 +- packages/core/src/serialization.ts | 14 ++++++++++++++ 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 34b572d68..534ad8790 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -231,7 +231,7 @@ jobs: run: pnpm install --frozen-lockfile - name: Run Initial Build - run: pnpm turbo run build --force --filter='!./workbench/*' + run: pnpm turbo run build --filter='!./workbench/*' - name: Run Build Tests run: pnpm vitest run packages/core/e2e/local-build.test.ts diff --git a/package.json b/package.json index a36409e5d..5e80e2d39 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "test": "turbo test", "clean": "turbo clean", "typecheck": "turbo typecheck", - "test:e2e": "vitest run --allowOnly packages/core/e2e/e2e.test.ts", + "test:e2e": "vitest run packages/core/e2e/e2e.test.ts", "lint": "biome check", "format": "biome format --write", "changeset": "changeset", diff --git a/packages/core/e2e/e2e.test.ts b/packages/core/e2e/e2e.test.ts index fe81039ee..020fc6824 100644 --- a/packages/core/e2e/e2e.test.ts +++ b/packages/core/e2e/e2e.test.ts @@ -688,7 +688,7 @@ describe('e2e', () => { } ); - test.only( + test( 'stepFunctionPassingWorkflow - step function references can be passed as arguments', { timeout: 60_000 }, async () => { diff --git a/packages/core/src/serialization.ts b/packages/core/src/serialization.ts index f50fca7ae..f1351a03e 100644 --- a/packages/core/src/serialization.ts +++ b/packages/core/src/serialization.ts @@ -279,8 +279,15 @@ function getCommonReducers(global: Record = globalThis) { }, Set: (value) => value instanceof global.Set && Array.from(value), StepFunction: (value) => { + console.log('serialize StepFunction', { + value, + typpe: typeof value, + }); if (typeof value !== 'function') return false; const stepName = value[STEP_FUNCTION_NAME_SYMBOL]; + console.log({ + stepName, + }); // Avoid returning a falsy value for empty string step names return typeof stepName === 'string' ? stepName : false; }, @@ -862,9 +869,16 @@ export function dehydrateStepArguments( global: Record ) { try { + console.error('dehydrating', { + value, + }); const str = devalue.stringify(value, getWorkflowReducers(global)); return revive(str); } catch (error) { + console.error('serialize fail', { + value, + error, + }); throw new WorkflowRuntimeError( `Failed to serialize step arguments. Ensure you're passing serializable types (plain objects, arrays, primitives, Date, RegExp, Map, Set).`, { slug: 'serialization-failed', cause: error } From 8a91661ac5794abf78cefbe323413c2832c07602 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Wed, 12 Nov 2025 13:56:05 -0800 Subject: [PATCH 20/23] logs --- packages/core/src/serialization.ts | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/packages/core/src/serialization.ts b/packages/core/src/serialization.ts index f1351a03e..f50fca7ae 100644 --- a/packages/core/src/serialization.ts +++ b/packages/core/src/serialization.ts @@ -279,15 +279,8 @@ function getCommonReducers(global: Record = globalThis) { }, Set: (value) => value instanceof global.Set && Array.from(value), StepFunction: (value) => { - console.log('serialize StepFunction', { - value, - typpe: typeof value, - }); if (typeof value !== 'function') return false; const stepName = value[STEP_FUNCTION_NAME_SYMBOL]; - console.log({ - stepName, - }); // Avoid returning a falsy value for empty string step names return typeof stepName === 'string' ? stepName : false; }, @@ -869,16 +862,9 @@ export function dehydrateStepArguments( global: Record ) { try { - console.error('dehydrating', { - value, - }); const str = devalue.stringify(value, getWorkflowReducers(global)); return revive(str); } catch (error) { - console.error('serialize fail', { - value, - error, - }); throw new WorkflowRuntimeError( `Failed to serialize step arguments. Ensure you're passing serializable types (plain objects, arrays, primitives, Date, RegExp, Map, Set).`, { slug: 'serialization-failed', cause: error } From edf679a99a524cb85ed69314400226b8b480b1ca Mon Sep 17 00:00:00 2001 From: Nathan Rajlich Date: Wed, 12 Nov 2025 14:43:30 -0800 Subject: [PATCH 21/23] . --- packages/core/package.json | 2 +- pnpm-lock.yaml | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index 791e2f227..bd1efacb5 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -57,7 +57,7 @@ "@workflow/world-local": "workspace:*", "@workflow/world-vercel": "workspace:*", "debug": "4.4.3", - "devalue": "5.4.1", + "devalue": "5.5.0", "ms": "2.1.3", "nanoid": "5.1.6", "seedrandom": "3.0.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8750d7a52..1000648b8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -445,8 +445,8 @@ importers: specifier: 4.4.3 version: 4.4.3(supports-color@8.1.1) devalue: - specifier: 5.4.1 - version: 5.4.1 + specifier: 5.5.0 + version: 5.5.0 ms: specifier: 2.1.3 version: 2.1.3 @@ -6869,8 +6869,8 @@ packages: peerDependencies: typescript: ^5.4.4 - devalue@5.4.1: - resolution: {integrity: sha512-YtoaOfsqjbZQKGIMRYDWKjUmSB4VJ/RElB+bXZawQAQYAo4xu08GKTMVlsZDTF6R2MbAgjcAQRPI5eIyRAT2OQ==} + devalue@5.5.0: + resolution: {integrity: sha512-69sM5yrHfFLJt0AZ9QqZXGCPfJ7fQjvpln3Rq5+PS03LD32Ost1Q9N+eEnaQwGRIriKkMImXD56ocjQmfjbV3w==} devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} @@ -15724,7 +15724,7 @@ snapshots: '@types/cookie': 0.6.0 acorn: 8.15.0 cookie: 0.6.0 - devalue: 5.4.1 + devalue: 5.5.0 esm-env: 1.2.2 kleur: 4.1.5 magic-string: 0.30.21 @@ -15746,7 +15746,7 @@ snapshots: '@types/cookie': 0.6.0 acorn: 8.15.0 cookie: 0.6.0 - devalue: 5.4.1 + devalue: 5.5.0 esm-env: 1.2.2 kleur: 4.1.5 magic-string: 0.30.21 @@ -17634,7 +17634,7 @@ snapshots: transitivePeerDependencies: - supports-color - devalue@5.4.1: {} + devalue@5.5.0: {} devlop@1.1.0: dependencies: @@ -20372,7 +20372,7 @@ snapshots: cookie-es: 2.0.0 defu: 6.1.4 destr: 2.0.5 - devalue: 5.4.1 + devalue: 5.5.0 errx: 0.1.0 esbuild: 0.25.11 escape-string-regexp: 5.0.0 @@ -20498,7 +20498,7 @@ snapshots: cookie-es: 2.0.0 defu: 6.1.4 destr: 2.0.5 - devalue: 5.4.1 + devalue: 5.5.0 errx: 0.1.0 esbuild: 0.25.11 escape-string-regexp: 5.0.0 From 456fa9578d8fb5a191bc662ec52d2dbbde76029e Mon Sep 17 00:00:00 2001 From: Nathan Rajlich Date: Wed, 12 Nov 2025 16:38:46 -0800 Subject: [PATCH 22/23] peter --- packages/core/e2e/e2e.test.ts | 11 +++++++++++ packages/core/src/serialization.ts | 1 - 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/core/e2e/e2e.test.ts b/packages/core/e2e/e2e.test.ts index 020fc6824..bc5631f96 100644 --- a/packages/core/e2e/e2e.test.ts +++ b/packages/core/e2e/e2e.test.ts @@ -706,6 +706,17 @@ describe('e2e', () => { ); expect(runData.status).toBe('completed'); expect(runData.output).toBe(40); + + // Verify that exactly 2 steps were executed: + // 1. stepWithStepFunctionArg(doubleNumber) + // (doubleNumber(10) is run inside the stepWithStepFunctionArg step) + const { json: eventsData } = await cliInspectJson( + `events --run ${run.runId} --json` + ); + const stepExecutionEvents = eventsData.events?.filter( + (event: any) => event.type === 'step-execution-completed' + ); + expect(stepExecutionEvents).toHaveLength(1); } ); }); diff --git a/packages/core/src/serialization.ts b/packages/core/src/serialization.ts index f50fca7ae..08871db83 100644 --- a/packages/core/src/serialization.ts +++ b/packages/core/src/serialization.ts @@ -281,7 +281,6 @@ function getCommonReducers(global: Record = globalThis) { StepFunction: (value) => { if (typeof value !== 'function') return false; const stepName = value[STEP_FUNCTION_NAME_SYMBOL]; - // Avoid returning a falsy value for empty string step names return typeof stepName === 'string' ? stepName : false; }, URL: (value) => value instanceof global.URL && value.href, From 35384f64929a784663f42e3715af27f0aa633c15 Mon Sep 17 00:00:00 2001 From: Nathan Rajlich Date: Wed, 12 Nov 2025 17:25:19 -0800 Subject: [PATCH 23/23] Fix test --- packages/core/e2e/e2e.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/e2e/e2e.test.ts b/packages/core/e2e/e2e.test.ts index bc5631f96..006aa64ea 100644 --- a/packages/core/e2e/e2e.test.ts +++ b/packages/core/e2e/e2e.test.ts @@ -713,10 +713,10 @@ describe('e2e', () => { const { json: eventsData } = await cliInspectJson( `events --run ${run.runId} --json` ); - const stepExecutionEvents = eventsData.events?.filter( - (event: any) => event.type === 'step-execution-completed' + const stepCompletedEvents = eventsData.filter( + (event) => event.eventType === 'step_completed' ); - expect(stepExecutionEvents).toHaveLength(1); + expect(stepCompletedEvents).toHaveLength(1); } ); });