Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/inline-step-registration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@workflow/swc-plugin": patch
---

**BREAKING CHANGE**: Inline all step registrations as self-contained IIFEs instead of generating `import { registerStepFunction } from "workflow/internal/private"`. Closure variable access is also inlined. This eliminates the dependency on the `workflow` package being available in `node_modules`, enabling 3rd-party packages to define step functions. Registrations are now placed immediately after each function definition instead of being batched at the bottom of the file.
6 changes: 6 additions & 0 deletions .changeset/remove-private-subpath.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@workflow/core": patch
"workflow": patch
---

**BREAKING CHANGE**: Remove `@workflow/core/private` and `workflow/internal/private` public subpath exports. The SWC compiler plugin no longer generates imports from these paths.
11 changes: 6 additions & 5 deletions docs/content/docs/how-it-works/code-transform.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -89,20 +89,21 @@ export async function createUser(email: string) {

{/* @skip-typecheck: incomplete code sample */}
```typescript
import { registerStepFunction } from "workflow/internal/private"; // [!code highlight]

export async function createUser(email: string) {
return { id: crypto.randomUUID(), email };
}

registerStepFunction("step//workflows/user.js//createUser", createUser); // [!code highlight]
(function(__wf_fn, __wf_id) { // [!code highlight]
var __wf_sym = Symbol.for("@workflow/core//registeredSteps"), __wf_reg = globalThis[__wf_sym] || (globalThis[__wf_sym] = new Map()); // [!code highlight]
__wf_reg.set(__wf_id, __wf_fn); // [!code highlight]
__wf_fn.stepId = __wf_id; // [!code highlight]
})(createUser, "step//workflows/user.js//createUser"); // [!code highlight]
```

**What happens:**

- The `"use step"` directive is removed
- The function body is kept completely intact (no transformation)
- The function is registered with the runtime using `registerStepFunction()`
- The function is registered with the runtime via an inline IIFE (no imports needed)
- Step functions run with full Node.js/Deno/Bun access

**Why no transformation?** Step functions execute in your main runtime with full access to Node.js APIs, file system, databases, etc. They don't need any special handling—they just run normally.
Expand Down
18 changes: 7 additions & 11 deletions packages/core/e2e/e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -926,8 +926,8 @@ describe('e2e', () => {
// Workflow catches the error and returns it
expect(result.caught).toBe(true);
expect(result.message).toContain('Step error message');
// Stack trace can show either the original step function or its transformed wrapper name
expect(result.stack).toMatch(/errorStepFn|registerStepFunction/);
// Stack trace should contain the original step function name
expect(result.stack).toContain('errorStepFn');
expect(result.stack).not.toContain('evalmachine');

// Source maps are not supported everyhwere. Check the definition
Expand All @@ -948,10 +948,8 @@ describe('e2e', () => {
expect(failedStep.status).toBe('failed');
expect(failedStep.error.message).toContain('Step error message');

// Step error stack can show either the original step function or its transformed wrapper name
expect(failedStep.error.stack).toMatch(
/errorStepFn|registerStepFunction/
);
// Step error stack should contain the original step function name
expect(failedStep.error.stack).toContain('errorStepFn');
expect(failedStep.error.stack).not.toContain('evalmachine');

// Source maps are not supported everyhwere. Check the definition
Expand Down Expand Up @@ -982,9 +980,7 @@ describe('e2e', () => {
);
// Stack trace propagates to caught error with function names and source file
expect(result.stack).toContain('throwErrorFromStep');
expect(result.stack).toMatch(
/stepThatThrowsFromHelper|registerStepFunction/
);
expect(result.stack).toContain('stepThatThrowsFromHelper');
expect(result.stack).not.toContain('evalmachine');

// Source maps are not supported everyhwere. Check the definition
Expand All @@ -1004,8 +1000,8 @@ describe('e2e', () => {
);
expect(failedStep.status).toBe('failed');
expect(failedStep.error.stack).toContain('throwErrorFromStep');
expect(failedStep.error.stack).toMatch(
/stepThatThrowsFromHelper|registerStepFunction/
expect(failedStep.error.stack).toContain(
'stepThatThrowsFromHelper'
);
expect(failedStep.error.stack).not.toContain('evalmachine');
// Source maps are not supported everyhwere. Check the definition
Expand Down
4 changes: 0 additions & 4 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,6 @@
"types": "./dist/runtime/resume-hook.d.ts",
"default": "./dist/runtime/resume-hook.js"
},
"./private": {
"types": "./dist/private.d.ts",
"default": "./dist/private.js"
},
"./class-serialization": {
"types": "./dist/class-serialization.d.ts",
"default": "./dist/class-serialization.js"
Expand Down
14 changes: 9 additions & 5 deletions packages/core/src/private.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ function getBuiltinResponseStepAlias(stepId: string): StepFunction | undefined {
/**
* Register a step function to be served in the server bundle.
* Also sets the stepId property on the function for serialization support.
*
* Note: The SWC compiler plugin no longer generates calls to this function.
* Step registration is now inlined as a self-contained IIFE that writes
* directly to the global Map at Symbol.for("@workflow/core//registeredSteps").
* This function is kept for internal/test use only.
*/
export function registerStepFunction(stepId: string, stepFn: StepFunction) {
registeredSteps.set(stepId, stepFn);
Expand Down Expand Up @@ -117,11 +122,10 @@ export function getStepFunction(stepId: string): StepFunction | undefined {
return undefined;
}

/**
* Get closure variables for the current step function
* @internal
*/
export { __private_getClosureVars } from './step/get-closure-vars.js';
// Note: __private_getClosureVars is no longer re-exported here.
// The SWC compiler plugin now inlines closure variable access as a
// self-contained IIFE that reads directly from the global AsyncLocalStorage
// at Symbol.for("WORKFLOW_STEP_CONTEXT_STORAGE").

export interface WorkflowOrchestratorContext {
runId: string;
Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/serialization.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2188,7 +2188,9 @@ describe('step function serialization', () => {
const stepName = 'step//workflows/test.ts//calculate';

// Create a step function that accesses closure variables
const { __private_getClosureVars } = await import('./private.js');
const { __private_getClosureVars } = await import(
'./step/get-closure-vars.js'
);
const { contextStorage } = await import('./step/context-storage.js');

const stepFn = async (x: number) => {
Expand Down
Loading
Loading