Added support for nested step functions inside workflow functions, improving the SWC plugin's ability to handle complex workflow patterns.
### What changed?
- Added support for nested step functions inside workflow functions
- Implemented hoisting of nested step functions in step mode
- Added tracking of default exports that need to be replaced with expressions
- Refactored function processing logic into a dedicated `process_stmt` method
- Fixed directive detection and removal in various contexts
- Improved handling of workflow functions with both file-level and inline directives
- Added conditional body replacement for workflow functions based on directive source
- Added test cases for nested step functions in workflow functions
### How to test?
1. Create a workflow function with a nested step function:
```javascript
export async function example(a, b) {
"use workflow";
async function step(a, b) {
"use step";
return a + b;
}
const val = await step(a, b);
return val;
}
```
2. Verify the output in different modes:
- Step mode: The nested step function is hoisted to module level
- Workflow mode: The nested step function is replaced with a proxy reference
- Client mode: The workflow function throws an error when called directly
### Why make this change?
This change enables more complex workflow patterns by allowing step functions to be defined inside workflow functions. Previously, all step functions had to be defined at the module level, which limited code organization options. With this change, developers can define step functions closer to where they're used, improving code readability and maintainability while still ensuring proper transformation for all execution modes.
Added support for step functions defined as object properties in the SWC plugin.
### What changed?
This PR extends the SWC plugin to support step functions defined as object properties. It now handles three different patterns:
1. Arrow functions in object properties: `tool({ execute: async () => { "use step"; ... } })`
2. Function expressions in object properties: `tool({ execute: async function() { "use step"; ... } })`
3. Method syntax in objects: `tool({ async execute() { "use step"; ... } })`
The implementation:
- Detects "use step" directives in object property functions
- Hoists these functions in step mode
- Converts them to step initializer calls in workflow mode
- Properly tracks and registers these functions with appropriate identifiers
- Updates metadata generation to include object property step functions
### How to test?
Test with various object property function patterns:
```javascript
// Arrow function in object property
const weatherTool = tool({
execute: async ({ location }) => {
"use step";
return { location, temperature: 72 };
}
});
// Function expression in object property
const timeTool = tool({
execute: async function() {
"use step";
return { time: new Date().toISOString() };
}
});
// Method syntax
const dateTool = tool({
async execute() {
"use step";
return { date: new Date().toDateString() };
}
});
```
### Why make this change?
This change enables a common pattern in JavaScript where functions are defined as object properties, particularly in AI SDK tool definitions.
Previously, the SWC plugin only supported top-level function declarations and expressions with the `"use step"` directive. This enhancement provides a more natural developer experience by allowing step functions to be defined directly within their object context.
Replace `Symbol.for("WORKFLOW_STEP_FUNCTION_NAME")` with a standard `stepId` property for step function annotation.
### What changed?
- Replaced the use of `STEP_FUNCTION_NAME_SYMBOL` (a Symbol) with a regular `stepId` property on step functions
- Updated the serialization logic to check for the `stepId` property instead of the Symbol
- Removed the `STEP_FUNCTION_NAME_SYMBOL` constant from symbols.ts
- Updated tests to reflect this change in implementation
### Why make this change?
This more closely resembles how Workflow functions are annotated with a `workflowId` property.
Improved step function serialization by moving the function name symbol definition from the SWC plugin to the runtime.
### What changed?
- Modified `createUseStep` in `packages/core/src/step.ts` to accept a `stepFunctionName` parameter and apply both the name property and the `WORKFLOW_STEP_FUNCTION_NAME` symbol directly to the step function at creation time
- Removed the code in the SWC plugin that was previously responsible for adding the `WORKFLOW_STEP_FUNCTION_NAME` symbol to step functions
- Updated all test outputs to reflect the removal of the redundant symbol definition code
### How to test?
1. Run the existing test suite to ensure all tests pass
2. Create a workflow with step functions and verify that:
- Step functions maintain their original function names
- Step functions have the `WORKFLOW_STEP_FUNCTION_NAME` symbol properly attached
- Serialization and deserialization of step functions works correctly
### Why make this change?
This change improves the architecture by:
1. Moving the responsibility for defining step function metadata to the runtime where it belongs
2. Ensuring step functions maintain their original names, which helps with debugging and error messages
3. Reducing redundancy by defining the symbol in one place instead of having the SWC plugin generate it for each function
4. Making the code more maintainable by centralizing the step function creation logic
- Modified SWC plugin to convert exported step functions to const declarations
- Instead of wrapping functions (calling useStep on every invocation), functions are now assigned directly to the result of useStep() call
- This ensures useStep() is called only once at module initialization, improving performance
- Applies to both file-level and function-level 'use step' directives
- All step function exports now use pattern: export const fn = globalThis[Symbol.for('WORKFLOW_USE_STEP')](stepId, fnName)
- Updated all test fixtures to reflect the new transformation
Signed-off-by: Nathan Rajlich <n@n8.io>
* Normalize Workbenches
Normalize trigger scripts across workbenches
fix: include hono in local build test
test: include src dir for test
test: add workflow dir config in test to fix sveltekit dev tests
add temp 7_full in example wokrflow
format
fix(sveltekit): detecting workflow folders and customizable dir
Remove 7_full and 1_simple error
replace API symlink in webpack workbench
Fix sveltekit and vite tests
Fix sveltekit symlinks
Test fixes
Fix sveltekit workflows path
Dont symlink routes in vite
Include e2e tests for hono and vite
fix error tests post normalization
wip - attempted fixes
* Add claude demo command
* fix: normalize workbench tests (#292)
* Proper stacktrace propogation in world
Proper stacktrace propogation in world
* Standardize the error type in the world spec
* Normalize Workbenches
Normalize trigger scripts across workbenches
fix: include hono in local build test
test: include src dir for test
test: add workflow dir config in test to fix sveltekit dev tests
add temp 7_full in example wokrflow
format
fix(sveltekit): detecting workflow folders and customizable dir
Remove 7_full and 1_simple error
replace API symlink in webpack workbench
Fix sveltekit and vite tests
Fix sveltekit symlinks
Test fixes
Fix sveltekit workflows path
Dont symlink routes in vite
Include e2e tests for hono and vite
* fix error tests post normalization
* fix(sveltekit): reading file on hmr delete
* changeset
* fix(vite): add resolve symlink script
* fix(vite): missing building on hmr
* test local builder in vite
* test: increase timeout on hookWorkflow
* test: ignore vite based apps in crossFileWorkflow
* test: fix nitro based apps status codes
* fix: intercept default vite spa handler on 404 workflow routes
* fix: vite hook route returning 422
* test: use 422 for hookWorkflow expected
* test: fix hono returning 404
* chore: add comment to middleware to clarify
* make api route for duplicate case
* revert
* revert: nitro builder
* add back nitro unhandled rejection logic
* test: add hono
* changeset
* fix: unused method
* fix: remove duplicate import
* remove
* chore: add comments to clarify
* test remove vite symlink script
---------
Co-authored-by: Pranay Prakash <pranay.gp@gmail.com>
* refactor: add top level resolve symlinks script
* fix: cleanup builder directories (#319)
* fix: add sveltekit server routes to builder
* fix: remove root workflow dir check
* fix missing root level workflow route
* Fix: The constructor now hardcodes `dirs: ['src/routes', 'src/lib']` which silently ignores any user\-provided `dirs` option passed to the plugin\, breaking the documented API and removing support for custom workflow directories\.
* Fix: The test expectations don\'t match the new implementation of `getWorkflowDirs()`\. The mock provides `scanDirs` which the new code no longer uses\, and the new implementation adds scanning of `routesDir` and `apiDir` instead\.
* fix(nitro): use src dir
---------
Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>
* refactor(nitro): use suppressUndefinedRejections
* revert: sveltekit builder
---------
Co-authored-by: Adrian <me@adriandlam.com>
Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>
* wip: add waituntil to toplevel requests
i'll revert this because we should waituntil for the entire operation and not the discrete ones
Signed-off-by: Gal Schlezinger <gal@spitfire.co.il>
* Revert "wip: add waituntil to toplevel requests"
This reverts commit 8da40b86c85265d0d5a4bc78e6a404475d44e514.
Signed-off-by: Gal Schlezinger <gal@spitfire.co.il>
* core: add waitUntil to start and resumeHook
Signed-off-by: Gal Schlezinger <gal@spitfire.co.il>
* changeset
Signed-off-by: Gal Schlezinger <gal@spitfire.co.il>
---------
Signed-off-by: Gal Schlezinger <gal@spitfire.co.il>
Fixes:
```
Error: @workflow/core#test: command (D:\a\workflow\workflow\packages\core) C:\Users\runneradmin\setup-pnpm\node_modules\.bin\.tools\pnpm\10.20.0\bin\pnpm.CMD run test exited (1)
> @workflow/core@4.0.1-beta.11 test D:\a\workflow\workflow\packages\core
> WORKFLOW_TARGET_WORLD=embedded vitest run src
'WORKFLOW_TARGET_WORLD' is not recognized as an internal or external command,
operable program or batch file.
ELIFECYCLE Test failed. See above for more details.
```
* workflow: stub workflow/api package so it can be referenced in a workflow context
what's not allowed is to call these functions, so let's stub them and make them fail in runtime.
i guess that eventually we will actually implement some of them (start or resumeHook or whatever) in a durable way.
Signed-off-by: Gal Schlezinger <gal@spitfire.co.il>
* changeset
Signed-off-by: Gal Schlezinger <gal@spitfire.co.il>
* Update packages/workflow/src/api-workflow.ts
Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>
Signed-off-by: Gal Schlezinger <gal@spitfire.co.il>
---------
Signed-off-by: Gal Schlezinger <gal@spitfire.co.il>
Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>
* Proper stacktrace propogation in world
Proper stacktrace propogation in world
* Merge Reconciliation
* Standardize the error type in the world spec
* Deduplicate vercel world utils
* fix undefined type issue
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
* feat: add getPort method for detecting pid port
* lockfile
* update tests and fix getPort usage
* changeset
* docs: update sveltekit getting started
* fix: use pid-port
* fix: not using config port env
* fix: remove unused getPort from world core
* remove unused stuff
* fix: world local config returning port 3000 as fallback
* changeset
* fix: rebase conflicts
* fix: util test missing http import
* changeset
* fix: wrong import for getPort in core runtime
* fix: getPort in @workflow/utils being imported into workflow runtime
* test: simplify sveltekit test
* fix missing import in test
* fix: async await stuff with getPort
* refactor: move getPort to @workflow/utils/get-port
* test: simplfiy getPort tests
* test: fix sveltekit ports