-
Notifications
You must be signed in to change notification settings - Fork 119
Apply workflow function transformation in "step" mode #420
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
Apply workflow function transformation in "step" mode #420
Conversation
🦋 Changeset detectedLatest commit: 0435e44 The changes in this PR will be included in the next version bump. This PR includes changesets to release 11 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.
|
This stack of pull requests is managed by Graphite. Learn more about stacking. |
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.
Additional Suggestion:
Default export workflow functions are not being transformed in step mode. They should have their bodies replaced with error throws like exported named workflow functions do.
View Details
📝 Patch Details
diff --git a/packages/swc-plugin-workflow/transform/src/lib.rs b/packages/swc-plugin-workflow/transform/src/lib.rs
index 5a054fe..b0a5a17 100644
--- a/packages/swc-plugin-workflow/transform/src/lib.rs
+++ b/packages/swc-plugin-workflow/transform/src/lib.rs
@@ -4531,7 +4531,14 @@ impl VisitMut for StepTransform {
match self.mode {
TransformMode::Step => {
- // Workflow functions are not processed in step mode
+ // In step mode, remove the directive for now
+ // We'll visit children to process nested steps,
+ // then replace the body with throw error afterwards
+ self.remove_use_workflow_directive(
+ &mut fn_expr.function.body,
+ );
+ self.workflow_functions_needing_id
+ .push((name.clone(), fn_expr.function.span));
}
TransformMode::Workflow => {
// In workflow mode, just remove the directive
@@ -5769,7 +5776,12 @@ impl VisitMut for StepTransform {
match self.mode {
TransformMode::Step => {
- // Workflow functions are not processed in step mode
+ // In step mode, remove the directive for now
+ // We'll visit children to process nested steps,
+ // then replace the body with throw error afterwards
+ self.remove_use_workflow_directive(&mut fn_expr.function.body);
+ self.workflow_functions_needing_id
+ .push((const_name.clone(), fn_expr.function.span));
}
TransformMode::Workflow => {
// In workflow mode, just remove the directive
@@ -5917,6 +5929,50 @@ impl VisitMut for StepTransform {
}
decl.visit_mut_children_with(self);
+
+ // After visiting, process the function again for cleanup
+ if let DefaultDecl::Fn(fn_expr) = &mut decl.decl {
+ let is_workflow = self.workflow_function_names.contains("default");
+
+ // In Step mode, replace workflow function body with throw after processing nested steps
+ if matches!(self.mode, TransformMode::Step) {
+ if is_workflow {
+ // Replace workflow function body with error throw
+ let const_name = self.workflow_export_to_const_name
+ .get("default")
+ .map(|s| s.to_string())
+ .unwrap_or_else(|| "default".to_string());
+ if let Some(body) = &mut fn_expr.function.body {
+ let error_msg = format!(
+ "You attempted to execute workflow {} function directly. To start a workflow, use start({}) from workflow/api",
+ const_name, const_name
+ );
+ let error_expr = Expr::New(NewExpr {
+ span: DUMMY_SP,
+ ctxt: SyntaxContext::empty(),
+ callee: Box::new(Expr::Ident(Ident::new(
+ "Error".into(),
+ DUMMY_SP,
+ SyntaxContext::empty(),
+ ))),
+ args: Some(vec![ExprOrSpread {
+ spread: None,
+ expr: Box::new(Expr::Lit(Lit::Str(Str {
+ span: DUMMY_SP,
+ value: error_msg.into(),
+ raw: None,
+ }))),
+ }]),
+ type_args: None,
+ });
+ body.stmts = vec![Stmt::Throw(ThrowStmt {
+ span: DUMMY_SP,
+ arg: Box::new(error_expr),
+ })];
+ }
+ }
+ }
+ }
}
_ => {
decl.visit_mut_children_with(self);
@@ -5942,7 +5998,12 @@ impl VisitMut for StepTransform {
match self.mode {
TransformMode::Step => {
- // Workflow functions are not processed in step mode
+ // In step mode, remove the directive for now
+ // We'll visit children to process nested steps,
+ // then replace the body with throw error afterwards
+ self.remove_use_workflow_directive(&mut fn_expr.function.body);
+ self.workflow_functions_needing_id
+ .push((const_name.clone(), fn_expr.function.span));
}
TransformMode::Workflow => {
// In workflow mode, convert to const declaration
@@ -6043,7 +6104,12 @@ impl VisitMut for StepTransform {
match self.mode {
TransformMode::Step => {
- // Workflow functions are not processed in step mode
+ // In step mode, remove the directive for now
+ // We'll visit children to process nested steps,
+ // then replace the body with throw error afterwards
+ self.remove_use_workflow_directive(&mut fn_expr.function.body);
+ self.workflow_functions_needing_id
+ .push((const_name.clone(), fn_expr.function.span));
}
TransformMode::Workflow => {
// In workflow mode, convert to const declaration
@@ -6137,6 +6203,88 @@ impl VisitMut for StepTransform {
}
expr.visit_mut_children_with(self);
+
+ // After visiting, process the function again for cleanup
+ if matches!(self.mode, TransformMode::Step) {
+ let is_workflow = self.workflow_function_names.contains("default");
+ if is_workflow {
+ match &mut *expr.expr {
+ Expr::Fn(fn_expr) => {
+ // Replace workflow function body with error throw
+ let const_name = self.workflow_export_to_const_name
+ .get("default")
+ .map(|s| s.to_string())
+ .unwrap_or_else(|| "default".to_string());
+ if let Some(body) = &mut fn_expr.function.body {
+ let error_msg = format!(
+ "You attempted to execute workflow {} function directly. To start a workflow, use start({}) from workflow/api",
+ const_name, const_name
+ );
+ let error_expr = Expr::New(NewExpr {
+ span: DUMMY_SP,
+ ctxt: SyntaxContext::empty(),
+ callee: Box::new(Expr::Ident(Ident::new(
+ "Error".into(),
+ DUMMY_SP,
+ SyntaxContext::empty(),
+ ))),
+ args: Some(vec![ExprOrSpread {
+ spread: None,
+ expr: Box::new(Expr::Lit(Lit::Str(Str {
+ span: DUMMY_SP,
+ value: error_msg.into(),
+ raw: None,
+ }))),
+ }]),
+ type_args: None,
+ });
+ body.stmts = vec![Stmt::Throw(ThrowStmt {
+ span: DUMMY_SP,
+ arg: Box::new(error_expr),
+ })];
+ }
+ }
+ Expr::Arrow(arrow_expr) => {
+ // Replace arrow body with throw error
+ let const_name = self.workflow_export_to_const_name
+ .get("default")
+ .map(|s| s.to_string())
+ .unwrap_or_else(|| "default".to_string());
+ let error_msg = format!(
+ "You attempted to execute workflow {} function directly. To start a workflow, use start({}) from workflow/api",
+ const_name, const_name
+ );
+ let error_expr = Expr::New(NewExpr {
+ span: DUMMY_SP,
+ ctxt: SyntaxContext::empty(),
+ callee: Box::new(Expr::Ident(Ident::new(
+ "Error".into(),
+ DUMMY_SP,
+ SyntaxContext::empty(),
+ ))),
+ args: Some(vec![ExprOrSpread {
+ spread: None,
+ expr: Box::new(Expr::Lit(Lit::Str(Str {
+ span: DUMMY_SP,
+ value: error_msg.into(),
+ raw: None,
+ }))),
+ }]),
+ type_args: None,
+ });
+ arrow_expr.body = Box::new(BlockStmtOrExpr::BlockStmt(BlockStmt {
+ span: DUMMY_SP,
+ ctxt: SyntaxContext::empty(),
+ stmts: vec![Stmt::Throw(ThrowStmt {
+ span: DUMMY_SP,
+ arg: Box::new(error_expr),
+ })],
+ }));
+ }
+ _ => {}
+ }
+ }
+ }
}
fn visit_mut_module_decl(&mut self, decl: &mut ModuleDecl) {
Analysis
Default export workflow functions not transformed in step mode
What fails: Workflow functions exported as default exports (e.g., export default async function defaultWorkflow() { 'use workflow'; ... }) are not having their bodies replaced with error throws in step mode, while named exports and const exports receive this transformation.
How to reproduce:
The issue is evident by comparing test fixture output files. The input file packages/swc-plugin-workflow/transform/tests/fixture/workflow-client-property/input.js contains:
export async function myWorkflow() {
'use workflow';
// ...
}
export default async function defaultWorkflow() {
'use workflow';
// ...
}When transformed in step mode, the named export myWorkflow correctly becomes:
export async function myWorkflow() {
throw new Error("You attempted to execute workflow myWorkflow function directly...");
}But the default export defaultWorkflow incorrectly remains:
export default async function defaultWorkflow() {
'use workflow';
// original body unchanged
}Expected behavior: The default export should also throw an error in step mode, matching the behavior of named exports. This was the intent of commit ef73dc7 "Apply workflow function transformation in 'step' mode" which updated the test fixture's expected output to show default exports throwing errors.
Root cause: The code in visit_mut_export_default_decl() (line 5771-5773) had Step mode handling that did nothing with a comment stating "Workflow functions are not processed in step mode". This was incorrect - the PR that introduced this function explicitly intended for workflow functions to throw errors in step mode. The named export handler visit_mut_export_decl() correctly implements this transformation, but the default export handler did not.
Fix implemented:
- Added Step mode handling in
visit_mut_export_default_declto remove the workflow directive and track the function for workflowId assignment - Added post-processing after visiting children to replace the function body with an error throw
- Applied the same fix to
visit_mut_export_default_exprfor arrow function default exports - Applied the same fix to variable declarations with workflow functions in
visit_mut_export_decl's Var handling - All fixes follow the same pattern used for named function exports
The transformation now properly handles all three forms of default exports:
export default async function name() { 'use workflow'; ... }export default async () => { 'use workflow'; ... }- Function expressions assigned to default export
All now correctly throw errors in step mode, consistent with named exports and const exports.
959fc02 to
f89f9f3
Compare
087de61 to
0435e44
Compare

Enhanced the SWC plugin to transform workflow functions in "step" mode, ensuring they throw appropriate errors when called directly.
This allows the
start()function to be used within step function to trigger new workflow runs.What changed?
This PR updates the SWC plugin to properly handle workflow functions in "step" mode:
start(functionName)insteadWhy make this change?
Previously, workflow functions in step mode weren't being transformed, which could lead to unexpected behavior when users tried to call them directly in a step function. This change ensures a consistent developer experience by providing clear error messages when workflow functions are called directly in a step function. The
workflowIdproperty is attached in "step" mode so that thestart()function may be used.