-
Notifications
You must be signed in to change notification settings - Fork 178
Add support for "use step" functions in class instance methods
#777
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 01-27-add_discovered_serializable_classes_in_all_context_modes
Are you sure you want to change the base?
Conversation
🦋 Changeset detectedLatest commit: 86c563c The changes in this PR will be included in the next version bump. This PR includes changesets to release 18 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
🧪 E2E Test Results❌ Some tests failed Summary
❌ Failed Tests🌍 Community Worlds (169 failed)mongodb (42 failed):
redis (42 failed):
starter (43 failed):
turso (42 failed):
Details by Category✅ ▲ Vercel Production
✅ 💻 Local Development
✅ 📦 Local Production
✅ 🐘 Local Postgres
✅ 🪟 Windows
❌ 🌍 Community Worlds
✅ 📋 Other
|
|
Warning This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
This stack of pull requests is managed by Graphite. Learn more about stacking. |
2809f4c to
d9a2fca
Compare
84dfb97 to
90402ec
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Adds SWC transform + runtime support so class instance methods can be marked with "use step" and invoked as workflow steps while preserving this via serialization.
Changes:
- Update SWC transform to detect
"use step"in instance methods, generateClassName.prototype.methodNamestep registrations, and proxy instance methods in workflow bundles. - Register serialization classes so
thisvalues can be dehydrated/hydrated across workflow ↔ step boundaries. - Add end-to-end coverage plus transform fixtures for instance-method steps (including nested steps), and adjust error fixtures.
Reviewed changes
Copilot reviewed 23 out of 23 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| workbench/example/workflows/99_e2e.ts | Adds Counter + instanceMethodStepWorkflow e2e workflow demonstrating instance-method steps with custom serialization. |
| packages/core/e2e/e2e.test.ts | Adds e2e test validating instance-method step execution + step list expectations. |
| packages/swc-plugin-workflow/transform/src/lib.rs | Core transform changes: allow "use step" on instance methods, register prototype step functions, and proxy them in workflow mode. |
| packages/swc-plugin-workflow/transform/tests/fixture/instance-method-step/input.js | New fixture input for instance-method step transformation. |
| packages/swc-plugin-workflow/transform/tests/fixture/instance-method-step/output-client.js | Expected client output for instance-method step fixture. |
| packages/swc-plugin-workflow/transform/tests/fixture/instance-method-step/output-step.js | Expected step-bundle output for instance-method step fixture. |
| packages/swc-plugin-workflow/transform/tests/fixture/instance-method-step/output-workflow.js | Expected workflow-bundle output for instance-method step fixture. |
| packages/swc-plugin-workflow/transform/tests/fixture/instance-method-nested-step/input.js | New fixture input for nested steps inside an instance-method step. |
| packages/swc-plugin-workflow/transform/tests/fixture/instance-method-nested-step/output-client.js | Expected client output for nested instance-method step fixture. |
| packages/swc-plugin-workflow/transform/tests/fixture/instance-method-nested-step/output-step.js | Expected step-bundle output for nested instance-method step fixture. |
| packages/swc-plugin-workflow/transform/tests/fixture/instance-method-nested-step/output-workflow.js | Expected workflow-bundle output for nested instance-method step fixture. |
| packages/swc-plugin-workflow/transform/tests/errors/instance-methods/input.js | Updates error fixture to allow "use step" on instance methods but still forbid "use workflow". |
| packages/swc-plugin-workflow/transform/tests/errors/instance-methods/output-client.js | Expected client output for updated instance-method error fixture. |
| packages/swc-plugin-workflow/transform/tests/errors/instance-methods/output-client.stderr | Updated expected stderr for client mode. |
| packages/swc-plugin-workflow/transform/tests/errors/instance-methods/output-step.js | Expected step output for updated instance-method error fixture. |
| packages/swc-plugin-workflow/transform/tests/errors/instance-methods/output-step.stderr | Updated expected stderr for step mode. |
| packages/swc-plugin-workflow/transform/tests/errors/instance-methods/output-workflow.js | Expected workflow output for updated instance-method error fixture. |
| packages/swc-plugin-workflow/transform/tests/errors/instance-methods/output-workflow.stderr | Updated expected stderr for workflow mode. |
| packages/swc-plugin-workflow/transform/tests/errors/forbidden-expressions/output-step.js | Updates expected output to treat instance method step as supported. |
| packages/swc-plugin-workflow/transform/tests/errors/forbidden-expressions/output-step.stderr | Adjusted expected stderr (now empty). |
| packages/swc-plugin-workflow/transform/tests/errors/forbidden-expressions/output-workflow.js | Updates expected output to proxy/register the instance method step. |
| packages/swc-plugin-workflow/transform/tests/errors/forbidden-expressions/output-workflow.stderr | Adjusted expected stderr (now empty). |
| .changeset/legal-parts-happen.md | Publishes a patch changeset for @workflow/swc-plugin. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| prop: MemberProp::Ident(IdentName::new( | ||
| method_name.into(), | ||
| DUMMY_SP, | ||
| )), |
Copilot
AI
Jan 30, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instance method step registration builds ClassName.prototype.<method_name> using MemberProp::Ident, but method_name may come from PropName::Str and can contain characters that aren’t valid identifiers. This would generate invalid JS (or wrong property access). Carry the original key type through and emit computed member access (e.g. prototype["..."]) when the method key is a string.
| prop: MemberProp::Ident(IdentName::new( | |
| method_name.into(), | |
| DUMMY_SP, | |
| )), | |
| prop: MemberProp::Computed(ComputedPropName { | |
| span: DUMMY_SP, | |
| expr: Box::new(Expr::Lit(Lit::Str(Str { | |
| span: DUMMY_SP, | |
| value: method_name.into(), | |
| raw: None, | |
| }))), | |
| }), |
| prop: MemberProp::Ident(IdentName::new( | ||
| method_name.into(), | ||
| DUMMY_SP, | ||
| )), |
Copilot
AI
Jan 30, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Workflow-mode proxy assignment uses MemberProp::Ident for method_name on ClassName.prototype, but method_name may originate from a string key. This can emit invalid JS and also won’t match the stripping logic if the class method key was a string. Use computed member access when appropriate (based on the original PropName).
| prop: MemberProp::Ident(IdentName::new( | |
| method_name.into(), | |
| DUMMY_SP, | |
| )), | |
| prop: MemberProp::Computed(ComputedPropName { | |
| span: DUMMY_SP, | |
| expr: Box::new(Expr::Lit(Lit::Str(Str { | |
| span: DUMMY_SP, | |
| value: method_name.into(), | |
| raw: None, | |
| }))), | |
| }), |
| )); | ||
|
|
||
| // Visit children to process nested step functions | ||
| method.visit_mut_children_with(self); | ||
| } |
Copilot
AI
Jan 30, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When visiting instance method step bodies to transform nested steps, the transformer doesn’t appear to set current_parent_function_name (unlike visit_mut_fn_decl). This can cause nested step hoisting to generate self-referential aliases (e.g. const helper = helper;, which is a runtime ReferenceError). Consider setting current_parent_function_name while visiting class method children (e.g. to ClassName#methodName) so hoisted nested steps get a unique name.
| const helper = helper; | ||
| const doubled = await helper(input); |
Copilot
AI
Jan 30, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This generated code is not valid at runtime: const helper = helper; will throw (TDZ/self-reference) when process() executes. The nested-step hoisting for instance method steps needs to avoid shadowing the hoisted helper name (e.g. rename the hoisted function or remove/rename the local binding).
| const helper = helper; | |
| const doubled = await helper(input); | |
| const nestedHelper = helper; | |
| const doubled = await nestedHelper(input); |
| /** | ||
| * Instance method step: adds the given amount to the counter's value. | ||
| * The `this` context (the Counter instance) is serialized and passed | ||
| * to the step handler, then deserialized before the method is called. | ||
| */ | ||
| async add(amount: number): Promise<number> { | ||
| 'use step'; | ||
| return this.value + amount; | ||
| } |
Copilot
AI
Jan 30, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The docstring says this step "adds the given amount to the counter's value", but the implementation returns this.value + amount without updating this.value. Either update the comment to reflect that it’s a pure calculation, or update the method to mutate this.value if that’s what’s intended to be tested.
| class_decl.class.body.retain(|member| { | ||
| if let ClassMember::Method(method) = member { | ||
| if method.is_static { | ||
| if let PropName::Ident(ident) = &method.key { | ||
| let method_name = ident.sym.to_string(); | ||
| return !methods_to_strip.contains(&method_name); | ||
| if let PropName::Ident(ident) = &method.key { | ||
| let method_name = ident.sym.to_string(); | ||
| if method.is_static { | ||
| return !static_methods_to_strip.contains(&method_name); | ||
| } else { | ||
| return !instance_methods_to_strip.contains(&method_name); | ||
| } | ||
| } |
Copilot
AI
Jan 30, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The workflow-mode stripping logic only checks PropName::Ident for class methods. Instance (and static) methods with string keys (e.g. async 'foo-bar'() { 'use step' }) are supported earlier, but won’t be removed here, so the original method body can remain in the workflow bundle instead of being replaced by the step proxy. Extend this retain() filter to also handle PropName::Str (and keep the stripping key representation consistent with how method_name is collected).
| const helper = helper; | ||
| const doubled = await helper(input); |
Copilot
AI
Jan 30, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Callee is not a function: it has type undefined.
| const helper = helper; | |
| const doubled = await helper(input); | |
| const localHelper = helper; | |
| const doubled = await localHelper(input); |
ccc981c to
ab3dd44
Compare
334dd56 to
86c563c
Compare

Added support for
"use step"directive in class instance methods, allowing instance methods to be used as workflow steps.What changed?
ClassName.prototype.methodNamethiscontext across workflow/step boundariesHow to test?
The PR includes a new end-to-end test
instanceMethodStepWorkflowthat demonstrates the functionality:Counterclass with instance methods marked as stepsthiscontextWhy make this change?
This change enables a more natural object-oriented programming model when working with workflows. Previously, only static methods, standalone functions, and object methods could be marked as steps. Now, developers can create classes with instance methods that are steps, allowing for better encapsulation and more intuitive code organization. This is particularly useful for complex workflows that need to maintain state across multiple step invocations.