From 43fcd5c35e35e873fa77322dcf5cd7919cb6a9e2 Mon Sep 17 00:00:00 2001 From: Nathan Rajlich Date: Wed, 19 Nov 2025 17:20:12 -0800 Subject: [PATCH 1/9] Fix default export workflow function transformation in workflow mode Fixed a bug where default export workflow functions with names (e.g., `export default async function testWorkflow()`) would fail at runtime with `ReferenceError: $$default is not defined`. The SWC plugin now correctly keeps the default export as-is and simply adds the workflowId assignment after it, since the function name is available as a binding in the module scope. Fixes #367. --- .changeset/fix-default-export-workflow.md | 5 ++ .../swc-plugin-workflow/transform/src/lib.rs | 47 +++++-------------- .../output-workflow.js | 2 +- 3 files changed, 19 insertions(+), 35 deletions(-) create mode 100644 .changeset/fix-default-export-workflow.md diff --git a/.changeset/fix-default-export-workflow.md b/.changeset/fix-default-export-workflow.md new file mode 100644 index 0000000000..f0b90e4f95 --- /dev/null +++ b/.changeset/fix-default-export-workflow.md @@ -0,0 +1,5 @@ +--- +"@workflow/swc-plugin": patch +--- + +Fix default export workflow function transformation in workflow mode diff --git a/packages/swc-plugin-workflow/transform/src/lib.rs b/packages/swc-plugin-workflow/transform/src/lib.rs index 07a8df0d8d..93fede18e8 100644 --- a/packages/swc-plugin-workflow/transform/src/lib.rs +++ b/packages/swc-plugin-workflow/transform/src/lib.rs @@ -176,8 +176,6 @@ pub struct StepTransform { workflow_functions_needing_id: Vec<(String, swc_core::common::Span)>, // Track step function exports that need to be converted to const declarations in workflow mode step_exports_to_convert: Vec<(String, String, swc_core::common::Span)>, // (fn_name, step_id, span) - // Track default exports that need to be replaced with expressions - default_exports_to_replace: Vec<(String, Expr)>, // (export_name, replacement_expr) // Track object property step functions for hoisting in step mode // (parent_var_name, prop_name, arrow_expr, span) object_property_step_functions: Vec<(String, String, ArrowExpr, swc_core::common::Span)>, @@ -452,7 +450,6 @@ impl StepTransform { workflow_exports_to_expand: Vec::new(), workflow_functions_needing_id: Vec::new(), step_exports_to_convert: Vec::new(), - default_exports_to_replace: Vec::new(), object_property_step_functions: Vec::new(), nested_step_functions: Vec::new(), anonymous_fn_counter: 0, @@ -2781,30 +2778,6 @@ impl VisitMut for StepTransform { } } - // Replace default exports that need to be converted - let default_exports: Vec<_> = self.default_exports_to_replace.drain(..).collect(); - for (export_name, replacement_expr) in default_exports { - for item in items.iter_mut() { - if let ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(export_default)) = item - { - if let DefaultDecl::Fn(fn_expr) = &export_default.decl { - if let Some(ident) = &fn_expr.ident { - if ident.sym == export_name { - // Replace with export default expression - *item = ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr( - ExportDefaultExpr { - span: export_default.span, - expr: Box::new(replacement_expr.clone()), - }, - )); - break; - } - } - } - } - } - } - // Clear the workflow_functions_needing_id since we've already processed them self.workflow_functions_needing_id.clear(); @@ -4065,14 +4038,20 @@ impl VisitMut for StepTransform { // In workflow mode, just remove the directive self.remove_use_workflow_directive(&mut fn_expr.function.body); - // We'll need to handle the expansion differently for default exports - // For now, just track it - let actual_name = format!( - "$$default{}", - if fn_name == "default" { "Workflow" } else { "" } - ); + // For named default exports, we can simply add workflowId after: + // export default async function name() { ... } + // name.workflowId = "..."; + + // Use the actual function name if available, or "defaultWorkflow" for anonymous + let workflow_name = if fn_name == "default" { + "defaultWorkflow".to_string() + } else { + fn_name.clone() + }; + + // Track for workflowId assignment self.workflow_exports_to_expand.push(( - actual_name, + workflow_name, Expr::Fn(fn_expr.clone()), fn_expr.function.span, )); diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/workflow-client-property/output-workflow.js b/packages/swc-plugin-workflow/transform/tests/fixture/workflow-client-property/output-workflow.js index 1242ca444f..27047e0516 100644 --- a/packages/swc-plugin-workflow/transform/tests/fixture/workflow-client-property/output-workflow.js +++ b/packages/swc-plugin-workflow/transform/tests/fixture/workflow-client-property/output-workflow.js @@ -23,4 +23,4 @@ export function regularFunction() { } myWorkflow.workflowId = "workflow//input.js//myWorkflow"; arrowWorkflow.workflowId = "workflow//input.js//arrowWorkflow"; -$$default.workflowId = "workflow//input.js//$$default"; +defaultWorkflow.workflowId = "workflow//input.js//defaultWorkflow"; From 35bdf63ffabf0e74b2fb35754bc66dc43fbf074b Mon Sep 17 00:00:00 2001 From: Nathan Rajlich Date: Wed, 19 Nov 2025 18:28:09 -0800 Subject: [PATCH 2/9] . --- .../swc-plugin-workflow/transform/src/lib.rs | 414 +++++++++++++++++- .../anonymous-default-workflow/input.js | 7 + .../output-client.js | 5 + .../anonymous-default-workflow/output-step.js | 7 + .../output-workflow.js | 8 + .../fixture/default-arrow-workflow/input.js | 7 + .../default-arrow-workflow/output-client.js | 3 + .../default-arrow-workflow/output-step.js | 7 + .../default-arrow-workflow/output-workflow.js | 8 + 9 files changed, 450 insertions(+), 16 deletions(-) create mode 100644 packages/swc-plugin-workflow/transform/tests/fixture/anonymous-default-workflow/input.js create mode 100644 packages/swc-plugin-workflow/transform/tests/fixture/anonymous-default-workflow/output-client.js create mode 100644 packages/swc-plugin-workflow/transform/tests/fixture/anonymous-default-workflow/output-step.js create mode 100644 packages/swc-plugin-workflow/transform/tests/fixture/anonymous-default-workflow/output-workflow.js create mode 100644 packages/swc-plugin-workflow/transform/tests/fixture/default-arrow-workflow/input.js create mode 100644 packages/swc-plugin-workflow/transform/tests/fixture/default-arrow-workflow/output-client.js create mode 100644 packages/swc-plugin-workflow/transform/tests/fixture/default-arrow-workflow/output-step.js create mode 100644 packages/swc-plugin-workflow/transform/tests/fixture/default-arrow-workflow/output-workflow.js diff --git a/packages/swc-plugin-workflow/transform/src/lib.rs b/packages/swc-plugin-workflow/transform/src/lib.rs index 93fede18e8..4f64bf24ce 100644 --- a/packages/swc-plugin-workflow/transform/src/lib.rs +++ b/packages/swc-plugin-workflow/transform/src/lib.rs @@ -176,6 +176,12 @@ pub struct StepTransform { workflow_functions_needing_id: Vec<(String, swc_core::common::Span)>, // Track step function exports that need to be converted to const declarations in workflow mode step_exports_to_convert: Vec<(String, String, swc_core::common::Span)>, // (fn_name, step_id, span) + // Track default exports that need to be replaced with expressions + default_exports_to_replace: Vec<(String, Expr)>, // (export_name, replacement_expr) + // Track default workflow exports that need const declarations in workflow mode + default_workflow_exports: Vec<(String, Expr, swc_core::common::Span)>, // (const_name, expr, span) + // Track all declared identifiers in module scope to avoid collisions + declared_identifiers: HashSet, // Track object property step functions for hoisting in step mode // (parent_var_name, prop_name, arrow_expr, span) object_property_step_functions: Vec<(String, String, ArrowExpr, swc_core::common::Span)>, @@ -450,6 +456,9 @@ impl StepTransform { workflow_exports_to_expand: Vec::new(), workflow_functions_needing_id: Vec::new(), step_exports_to_convert: Vec::new(), + default_exports_to_replace: Vec::new(), + default_workflow_exports: Vec::new(), + declared_identifiers: HashSet::new(), object_property_step_functions: Vec::new(), nested_step_functions: Vec::new(), anonymous_fn_counter: 0, @@ -482,6 +491,133 @@ impl StepTransform { } } + // Generate a unique identifier that doesn't conflict with existing declarations + fn generate_unique_name(&self, base_name: &str) -> String { + let mut name = base_name.to_string(); + let mut counter = 0; + + while self.declared_identifiers.contains(&name) { + counter += 1; + name = format!("{}${}", base_name, counter); + } + + name + } + + // Collect all declared identifiers in the module to avoid naming collisions + fn collect_declared_identifiers(&mut self, items: &[ModuleItem]) { + for item in items { + match item { + ModuleItem::Stmt(Stmt::Decl(decl)) => { + match decl { + Decl::Fn(fn_decl) => { + self.declared_identifiers.insert(fn_decl.ident.sym.to_string()); + } + Decl::Var(var_decl) => { + for declarator in &var_decl.decls { + self.collect_idents_from_pat(&declarator.name); + } + } + Decl::Class(class_decl) => { + self.declared_identifiers.insert(class_decl.ident.sym.to_string()); + } + _ => {} + } + } + ModuleItem::ModuleDecl(module_decl) => { + match module_decl { + ModuleDecl::ExportDecl(export_decl) => { + match &export_decl.decl { + Decl::Fn(fn_decl) => { + self.declared_identifiers.insert(fn_decl.ident.sym.to_string()); + } + Decl::Var(var_decl) => { + for declarator in &var_decl.decls { + self.collect_idents_from_pat(&declarator.name); + } + } + Decl::Class(class_decl) => { + self.declared_identifiers.insert(class_decl.ident.sym.to_string()); + } + _ => {} + } + } + ModuleDecl::ExportDefaultDecl(default_decl) => { + match &default_decl.decl { + DefaultDecl::Fn(fn_expr) => { + if let Some(ident) = &fn_expr.ident { + self.declared_identifiers.insert(ident.sym.to_string()); + } + } + DefaultDecl::Class(class_expr) => { + if let Some(ident) = &class_expr.ident { + self.declared_identifiers.insert(ident.sym.to_string()); + } + } + _ => {} + } + } + ModuleDecl::Import(import_decl) => { + for specifier in &import_decl.specifiers { + match specifier { + ImportSpecifier::Named(named) => { + self.declared_identifiers.insert(named.local.sym.to_string()); + } + ImportSpecifier::Default(default) => { + self.declared_identifiers.insert(default.local.sym.to_string()); + } + ImportSpecifier::Namespace(namespace) => { + self.declared_identifiers.insert(namespace.local.sym.to_string()); + } + } + } + } + _ => {} + } + } + _ => {} + } + } + } + + // Helper to collect identifiers from patterns (for destructuring, etc.) + fn collect_idents_from_pat(&mut self, pat: &Pat) { + match pat { + Pat::Ident(ident) => { + self.declared_identifiers.insert(ident.id.sym.to_string()); + } + Pat::Array(array_pat) => { + for elem in &array_pat.elems { + if let Some(elem) = elem { + self.collect_idents_from_pat(elem); + } + } + } + Pat::Object(obj_pat) => { + for prop in &obj_pat.props { + match prop { + ObjectPatProp::KeyValue(kv) => { + self.collect_idents_from_pat(&kv.value); + } + ObjectPatProp::Assign(assign) => { + self.declared_identifiers.insert(assign.key.sym.to_string()); + } + ObjectPatProp::Rest(rest) => { + self.collect_idents_from_pat(&rest.arg); + } + } + } + } + Pat::Rest(rest_pat) => { + self.collect_idents_from_pat(&rest_pat.arg); + } + Pat::Assign(assign_pat) => { + self.collect_idents_from_pat(&assign_pat.left); + } + _ => {} + } + } + // Create an identifier for an object property step function // Used for functions defined as object properties, e.g., tool({ execute: async () => {...} }) fn create_object_property_id( @@ -2439,6 +2575,9 @@ impl VisitMut for StepTransform { } fn visit_mut_module_items(&mut self, items: &mut Vec) { + // Collect all declared identifiers to avoid naming collisions + self.collect_declared_identifiers(items); + // Check for file-level directives self.has_file_step_directive = self.check_module_directive(items); self.has_file_workflow_directive = self.check_module_workflow_directive(items); @@ -2778,6 +2917,73 @@ impl VisitMut for StepTransform { } } + // In workflow mode, add const declarations for default workflow exports + if self.mode == TransformMode::Workflow && !self.default_workflow_exports.is_empty() { + // Process default workflow exports to add const declarations and workflowId property + let default_workflows: Vec<_> = self.default_workflow_exports.drain(..).collect(); + + for (const_name, fn_expr, span) in default_workflows { + // Add const declaration: const defaultWorkflow = async function() { ... }; + items.push(ModuleItem::Stmt(Stmt::Decl(Decl::Var(Box::new(VarDecl { + span: DUMMY_SP, + ctxt: SyntaxContext::empty(), + kind: VarDeclKind::Const, + declare: false, + decls: vec![VarDeclarator { + span: DUMMY_SP, + name: Pat::Ident(BindingIdent { + id: Ident::new(const_name.clone().into(), DUMMY_SP, SyntaxContext::empty()), + type_ann: None, + }), + init: Some(Box::new(fn_expr)), + definite: false, + }], + }))))); + + // Add workflowId assignment after the const declaration + items.push(ModuleItem::Stmt( + self.create_workflow_id_assignment(&const_name, span), + )); + } + } + + // Replace default exports that need to be converted + let default_exports: Vec<_> = self.default_exports_to_replace.drain(..).collect(); + for (export_name, replacement_expr) in default_exports { + for item in items.iter_mut() { + match item { + ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(export_default)) => { + // Check if this is the default export we want to replace + // For anonymous arrow functions, we're replacing the expression with an identifier + if export_name == "default" { + // Replace with the new expression (identifier) + *item = ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr( + ExportDefaultExpr { + span: export_default.span, + expr: Box::new(replacement_expr.clone()), + }, + )); + break; + } + } + ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(export_default)) => { + // For anonymous functions in default exports + if export_name == "default" { + // Replace with export default expression (identifier) + *item = ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr( + ExportDefaultExpr { + span: export_default.span, + expr: Box::new(replacement_expr.clone()), + }, + )); + break; + } + } + _ => {} + } + } + } + // Clear the workflow_functions_needing_id since we've already processed them self.workflow_functions_needing_id.clear(); @@ -4038,23 +4244,37 @@ impl VisitMut for StepTransform { // In workflow mode, just remove the directive self.remove_use_workflow_directive(&mut fn_expr.function.body); - // For named default exports, we can simply add workflowId after: - // export default async function name() { ... } - // name.workflowId = "..."; - - // Use the actual function name if available, or "defaultWorkflow" for anonymous - let workflow_name = if fn_name == "default" { - "defaultWorkflow".to_string() + if fn_name == "default" { + // Anonymous default export: convert to const declaration + // Generate unique name to avoid collisions + let unique_name = self.generate_unique_name("defaultWorkflow"); + + // Track for const declaration and workflowId assignment + self.default_workflow_exports.push(( + unique_name.clone(), + Expr::Fn(fn_expr.clone()), + fn_expr.function.span, + )); + + // Track for replacement with identifier + self.default_exports_to_replace.push(( + fn_name.clone(), + Expr::Ident(Ident::new( + unique_name.into(), + DUMMY_SP, + SyntaxContext::empty(), + )), + )); } else { - fn_name.clone() - }; - - // Track for workflowId assignment - self.workflow_exports_to_expand.push(( - workflow_name, - Expr::Fn(fn_expr.clone()), - fn_expr.function.span, - )); + // Named default export: can reference by name + // export default async function name() { ... } + // name.workflowId = "..."; + self.workflow_exports_to_expand.push(( + fn_name.clone(), + Expr::Fn(fn_expr.clone()), + fn_expr.function.span, + )); + } } TransformMode::Client => { // In client mode, replace workflow function body with error throw @@ -4157,6 +4377,168 @@ impl VisitMut for StepTransform { } } + // Handle export default expressions (anonymous functions and arrow functions) + fn visit_mut_export_default_expr(&mut self, expr: &mut ExportDefaultExpr) { + match &mut *expr.expr { + Expr::Fn(fn_expr) => { + // Anonymous function: export default async function() { ... } + if self.should_transform_workflow_function(&fn_expr.function, true) { + if self.validate_async_function(&fn_expr.function, fn_expr.function.span) { + self.workflow_function_names.insert("default".to_string()); + + match self.mode { + TransformMode::Step => { + // Workflow functions are not processed in step mode + } + TransformMode::Workflow => { + // In workflow mode, convert to const declaration + self.remove_use_workflow_directive(&mut fn_expr.function.body); + + // Generate unique name to avoid collisions + let unique_name = self.generate_unique_name("defaultWorkflow"); + + // Track for const declaration and workflowId assignment + self.default_workflow_exports.push(( + unique_name.clone(), + Expr::Fn(fn_expr.clone()), + fn_expr.function.span, + )); + + // Track for replacement with identifier + self.default_exports_to_replace.push(( + "default".to_string(), + Expr::Ident(Ident::new( + unique_name.into(), + DUMMY_SP, + SyntaxContext::empty(), + )), + )); + } + TransformMode::Client => { + // In client mode, replace workflow function body with error throw + self.remove_use_workflow_directive(&mut fn_expr.function.body); + if let Some(body) = &mut fn_expr.function.body { + let error_msg = "You attempted to execute workflow defaultWorkflow function directly. To start a workflow, use start(defaultWorkflow) from workflow/api"; + 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), + })]; + } + self.workflow_functions_needing_id + .push(("defaultWorkflow".to_string(), fn_expr.function.span)); + } + } + } + } else if self.should_transform_function(&fn_expr.function, true) { + // Handle step functions + if self.validate_async_function(&fn_expr.function, fn_expr.function.span) { + self.step_function_names.insert("default".to_string()); + // Similar logic for steps... + } + } + } + Expr::Arrow(arrow_expr) => { + // Arrow function: export default async () => { ... } + if self.has_workflow_directive_arrow(arrow_expr, true) { + if !arrow_expr.is_async { + emit_error(WorkflowErrorKind::NonAsyncFunction { + span: arrow_expr.span, + directive: "use workflow", + }); + } else { + self.workflow_function_names.insert("default".to_string()); + + match self.mode { + TransformMode::Step => { + // Workflow functions are not processed in step mode + } + TransformMode::Workflow => { + // In workflow mode, convert to const declaration + self.remove_use_workflow_directive_arrow(&mut arrow_expr.body); + + // Generate unique name to avoid collisions + let unique_name = self.generate_unique_name("defaultWorkflow"); + + // Track for const declaration and workflowId assignment + self.default_workflow_exports.push(( + unique_name.clone(), + Expr::Arrow(arrow_expr.clone()), + arrow_expr.span, + )); + + // Track for replacement with identifier + self.default_exports_to_replace.push(( + "default".to_string(), + Expr::Ident(Ident::new( + unique_name.into(), + DUMMY_SP, + SyntaxContext::empty(), + )), + )); + } + TransformMode::Client => { + // In client mode, replace workflow function body with error throw + self.remove_use_workflow_directive_arrow(&mut arrow_expr.body); + let error_msg = "You attempted to execute workflow defaultWorkflow function directly. To start a workflow, use start(defaultWorkflow) from workflow/api"; + 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::Expr(Box::new(error_expr))); + } + } + } + } else if self.has_step_directive_arrow(arrow_expr, true) { + // Handle step arrow functions + if !arrow_expr.is_async { + emit_error(WorkflowErrorKind::NonAsyncFunction { + span: arrow_expr.span, + directive: "use step", + }); + } else { + self.step_function_names.insert("default".to_string()); + // Similar logic for steps... + } + } + } + _ => {} + } + + expr.visit_mut_children_with(self); + } + fn visit_mut_module_decl(&mut self, decl: &mut ModuleDecl) { // ExportDecl is fully handled by visit_mut_export_decl, so just delegate // to default visitor which will call visit_mut_export_decl diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/anonymous-default-workflow/input.js b/packages/swc-plugin-workflow/transform/tests/fixture/anonymous-default-workflow/input.js new file mode 100644 index 0000000000..717913bb8e --- /dev/null +++ b/packages/swc-plugin-workflow/transform/tests/fixture/anonymous-default-workflow/input.js @@ -0,0 +1,7 @@ +// Test anonymous default export workflow +export default async function() { + 'use workflow'; + const result = await someStep(); + return result; +} + diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/anonymous-default-workflow/output-client.js b/packages/swc-plugin-workflow/transform/tests/fixture/anonymous-default-workflow/output-client.js new file mode 100644 index 0000000000..ee4381b9f6 --- /dev/null +++ b/packages/swc-plugin-workflow/transform/tests/fixture/anonymous-default-workflow/output-client.js @@ -0,0 +1,5 @@ +// Test anonymous default export workflow +/**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//default"}}}}*/; +export default async function() { + throw new Error("You attempted to execute workflow defaultWorkflow function directly. To start a workflow, use start(defaultWorkflow) from workflow/api"); +} diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/anonymous-default-workflow/output-step.js b/packages/swc-plugin-workflow/transform/tests/fixture/anonymous-default-workflow/output-step.js new file mode 100644 index 0000000000..596d6b8e43 --- /dev/null +++ b/packages/swc-plugin-workflow/transform/tests/fixture/anonymous-default-workflow/output-step.js @@ -0,0 +1,7 @@ +// Test anonymous default export workflow +/**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//default"}}}}*/; +export default async function() { + 'use workflow'; + const result = await someStep(); + return result; +} diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/anonymous-default-workflow/output-workflow.js b/packages/swc-plugin-workflow/transform/tests/fixture/anonymous-default-workflow/output-workflow.js new file mode 100644 index 0000000000..f96984acca --- /dev/null +++ b/packages/swc-plugin-workflow/transform/tests/fixture/anonymous-default-workflow/output-workflow.js @@ -0,0 +1,8 @@ +// Test anonymous default export workflow +/**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//default"}}}}*/; +export default defaultWorkflow; +const defaultWorkflow = async function() { + const result = await someStep(); + return result; +}; +defaultWorkflow.workflowId = "workflow//input.js//defaultWorkflow"; diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/default-arrow-workflow/input.js b/packages/swc-plugin-workflow/transform/tests/fixture/default-arrow-workflow/input.js new file mode 100644 index 0000000000..d4fa519abd --- /dev/null +++ b/packages/swc-plugin-workflow/transform/tests/fixture/default-arrow-workflow/input.js @@ -0,0 +1,7 @@ +// Test default export arrow workflow +export default async (data) => { + 'use workflow'; + const processed = await processData(data); + return processed; +}; + diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/default-arrow-workflow/output-client.js b/packages/swc-plugin-workflow/transform/tests/fixture/default-arrow-workflow/output-client.js new file mode 100644 index 0000000000..64b6c3c6bc --- /dev/null +++ b/packages/swc-plugin-workflow/transform/tests/fixture/default-arrow-workflow/output-client.js @@ -0,0 +1,3 @@ +// Test default export arrow workflow +/**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//default"}}}}*/; +export default (async (data)=>new Error("You attempted to execute workflow defaultWorkflow function directly. To start a workflow, use start(defaultWorkflow) from workflow/api")); diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/default-arrow-workflow/output-step.js b/packages/swc-plugin-workflow/transform/tests/fixture/default-arrow-workflow/output-step.js new file mode 100644 index 0000000000..ea5ee1fac0 --- /dev/null +++ b/packages/swc-plugin-workflow/transform/tests/fixture/default-arrow-workflow/output-step.js @@ -0,0 +1,7 @@ +// Test default export arrow workflow +/**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//default"}}}}*/; +export default (async (data)=>{ + 'use workflow'; + const processed = await processData(data); + return processed; +}); diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/default-arrow-workflow/output-workflow.js b/packages/swc-plugin-workflow/transform/tests/fixture/default-arrow-workflow/output-workflow.js new file mode 100644 index 0000000000..60c2894772 --- /dev/null +++ b/packages/swc-plugin-workflow/transform/tests/fixture/default-arrow-workflow/output-workflow.js @@ -0,0 +1,8 @@ +// Test default export arrow workflow +/**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//default"}}}}*/; +export default defaultWorkflow; +const defaultWorkflow = async (data)=>{ + const processed = await processData(data); + return processed; +}; +defaultWorkflow.workflowId = "workflow//input.js//defaultWorkflow"; From 0ce10dc2cfc4643da1be238ed721874015d17451 Mon Sep 17 00:00:00 2001 From: Nathan Rajlich Date: Wed, 19 Nov 2025 18:31:31 -0800 Subject: [PATCH 3/9] . --- .../swc-plugin-workflow/transform/src/lib.rs | 136 +++++++++++------- .../output-workflow.js | 2 +- .../default-arrow-workflow/output-workflow.js | 2 +- 3 files changed, 83 insertions(+), 57 deletions(-) diff --git a/packages/swc-plugin-workflow/transform/src/lib.rs b/packages/swc-plugin-workflow/transform/src/lib.rs index 4f64bf24ce..67fa64310a 100644 --- a/packages/swc-plugin-workflow/transform/src/lib.rs +++ b/packages/swc-plugin-workflow/transform/src/lib.rs @@ -2917,69 +2917,95 @@ impl VisitMut for StepTransform { } } - // In workflow mode, add const declarations for default workflow exports + // In workflow mode, handle default workflow exports + // We need to: 1) find the export default position, 2) replace it with const declaration, + // 3) add workflowId assignment, 4) add export default at the end if self.mode == TransformMode::Workflow && !self.default_workflow_exports.is_empty() { - // Process default workflow exports to add const declarations and workflowId property let default_workflows: Vec<_> = self.default_workflow_exports.drain(..).collect(); + let default_exports: Vec<_> = self.default_exports_to_replace.drain(..).collect(); - for (const_name, fn_expr, span) in default_workflows { - // Add const declaration: const defaultWorkflow = async function() { ... }; - items.push(ModuleItem::Stmt(Stmt::Decl(Decl::Var(Box::new(VarDecl { - span: DUMMY_SP, - ctxt: SyntaxContext::empty(), - kind: VarDeclKind::Const, - declare: false, - decls: vec![VarDeclarator { - span: DUMMY_SP, - name: Pat::Ident(BindingIdent { - id: Ident::new(const_name.clone().into(), DUMMY_SP, SyntaxContext::empty()), - type_ann: None, - }), - init: Some(Box::new(fn_expr)), - definite: false, - }], - }))))); - - // Add workflowId assignment after the const declaration - items.push(ModuleItem::Stmt( - self.create_workflow_id_assignment(&const_name, span), - )); + // Find and remove the original export default, note its position + let mut export_position = None; + for (i, item) in items.iter().enumerate() { + match item { + ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(_)) | + ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(_)) => { + export_position = Some(i); + break; + } + _ => {} + } } - } - // Replace default exports that need to be converted - let default_exports: Vec<_> = self.default_exports_to_replace.drain(..).collect(); - for (export_name, replacement_expr) in default_exports { - for item in items.iter_mut() { - match item { - ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(export_default)) => { - // Check if this is the default export we want to replace - // For anonymous arrow functions, we're replacing the expression with an identifier - if export_name == "default" { - // Replace with the new expression (identifier) - *item = ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr( - ExportDefaultExpr { - span: export_default.span, - expr: Box::new(replacement_expr.clone()), - }, - )); - break; - } + if let Some(pos) = export_position { + // Remove the original export default + items.remove(pos); + + // Insert in correct order: const, workflowId, export default + for (const_name, fn_expr, span) in default_workflows { + // Insert const declaration at the original export position + items.insert(pos, ModuleItem::Stmt(Stmt::Decl(Decl::Var(Box::new(VarDecl { + span: DUMMY_SP, + ctxt: SyntaxContext::empty(), + kind: VarDeclKind::Const, + declare: false, + decls: vec![VarDeclarator { + span: DUMMY_SP, + name: Pat::Ident(BindingIdent { + id: Ident::new(const_name.clone().into(), DUMMY_SP, SyntaxContext::empty()), + type_ann: None, + }), + init: Some(Box::new(fn_expr)), + definite: false, + }], + }))))); + + // Insert workflowId assignment after const + items.insert(pos + 1, ModuleItem::Stmt( + self.create_workflow_id_assignment(&const_name, span), + )); + + // Insert export default at the end (after workflowId) + for (_export_name, replacement_expr) in &default_exports { + items.insert(pos + 2, ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr( + ExportDefaultExpr { + span: DUMMY_SP, + expr: Box::new(replacement_expr.clone()), + }, + ))); } - ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(export_default)) => { - // For anonymous functions in default exports - if export_name == "default" { - // Replace with export default expression (identifier) - *item = ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr( - ExportDefaultExpr { - span: export_default.span, - expr: Box::new(replacement_expr.clone()), - }, - )); - break; + } + } + } else { + // Handle cases where default exports need to be converted but no const declaration + let default_exports: Vec<_> = self.default_exports_to_replace.drain(..).collect(); + for (export_name, replacement_expr) in default_exports { + for item in items.iter_mut() { + match item { + ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(export_default)) => { + if export_name == "default" { + *item = ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr( + ExportDefaultExpr { + span: export_default.span, + expr: Box::new(replacement_expr.clone()), + }, + )); + break; + } } + ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(export_default)) => { + if export_name == "default" { + *item = ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr( + ExportDefaultExpr { + span: export_default.span, + expr: Box::new(replacement_expr.clone()), + }, + )); + break; + } + } + _ => {} } - _ => {} } } } diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/anonymous-default-workflow/output-workflow.js b/packages/swc-plugin-workflow/transform/tests/fixture/anonymous-default-workflow/output-workflow.js index f96984acca..008fe0648e 100644 --- a/packages/swc-plugin-workflow/transform/tests/fixture/anonymous-default-workflow/output-workflow.js +++ b/packages/swc-plugin-workflow/transform/tests/fixture/anonymous-default-workflow/output-workflow.js @@ -1,8 +1,8 @@ // Test anonymous default export workflow /**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//default"}}}}*/; -export default defaultWorkflow; const defaultWorkflow = async function() { const result = await someStep(); return result; }; defaultWorkflow.workflowId = "workflow//input.js//defaultWorkflow"; +export default defaultWorkflow; diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/default-arrow-workflow/output-workflow.js b/packages/swc-plugin-workflow/transform/tests/fixture/default-arrow-workflow/output-workflow.js index 60c2894772..2c53916f01 100644 --- a/packages/swc-plugin-workflow/transform/tests/fixture/default-arrow-workflow/output-workflow.js +++ b/packages/swc-plugin-workflow/transform/tests/fixture/default-arrow-workflow/output-workflow.js @@ -1,8 +1,8 @@ // Test default export arrow workflow /**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//default"}}}}*/; -export default defaultWorkflow; const defaultWorkflow = async (data)=>{ const processed = await processData(data); return processed; }; defaultWorkflow.workflowId = "workflow//input.js//defaultWorkflow"; +export default defaultWorkflow; From aa3b0062d6c8e6025b98b83cfc551e9eb402caf5 Mon Sep 17 00:00:00 2001 From: Nathan Rajlich Date: Wed, 19 Nov 2025 18:38:41 -0800 Subject: [PATCH 4/9] . --- .../swc-plugin-workflow/transform/src/lib.rs | 66 +++++++++++++++++-- .../output-client.js | 6 +- .../default-arrow-workflow/output-client.js | 6 +- 3 files changed, 69 insertions(+), 9 deletions(-) diff --git a/packages/swc-plugin-workflow/transform/src/lib.rs b/packages/swc-plugin-workflow/transform/src/lib.rs index 67fa64310a..5fc7e690d6 100644 --- a/packages/swc-plugin-workflow/transform/src/lib.rs +++ b/packages/swc-plugin-workflow/transform/src/lib.rs @@ -2917,10 +2917,11 @@ impl VisitMut for StepTransform { } } - // In workflow mode, handle default workflow exports + // Handle default workflow exports (workflow and client modes) // We need to: 1) find the export default position, 2) replace it with const declaration, // 3) add workflowId assignment, 4) add export default at the end - if self.mode == TransformMode::Workflow && !self.default_workflow_exports.is_empty() { + if (self.mode == TransformMode::Workflow || self.mode == TransformMode::Client) + && !self.default_workflow_exports.is_empty() { let default_workflows: Vec<_> = self.default_workflow_exports.drain(..).collect(); let default_exports: Vec<_> = self.default_exports_to_replace.drain(..).collect(); @@ -4338,8 +4339,33 @@ impl VisitMut for StepTransform { arg: Box::new(error_expr), })]; } - self.workflow_functions_needing_id - .push((actual_name, fn_expr.function.span)); + + // For anonymous functions, convert to const declaration so we can assign workflowId + if fn_name == "default" { + // Generate unique name to avoid collisions + let unique_name = self.generate_unique_name("defaultWorkflow"); + + // Track for const declaration and workflowId assignment + self.default_workflow_exports.push(( + unique_name.clone(), + Expr::Fn(fn_expr.clone()), + fn_expr.function.span, + )); + + // Track for replacement with identifier + self.default_exports_to_replace.push(( + fn_name.clone(), + Expr::Ident(Ident::new( + unique_name.into(), + DUMMY_SP, + SyntaxContext::empty(), + )), + )); + } else { + // Named function can be referenced directly + self.workflow_functions_needing_id + .push((actual_name, fn_expr.function.span)); + } } } } @@ -4521,7 +4547,7 @@ impl VisitMut for StepTransform { )); } TransformMode::Client => { - // In client mode, replace workflow function body with error throw + // In client mode, convert to const declaration so we can assign workflowId self.remove_use_workflow_directive_arrow(&mut arrow_expr.body); let error_msg = "You attempted to execute workflow defaultWorkflow function directly. To start a workflow, use start(defaultWorkflow) from workflow/api"; let error_expr = Expr::New(NewExpr { @@ -4542,7 +4568,35 @@ impl VisitMut for StepTransform { }]), type_args: None, }); - arrow_expr.body = Box::new(BlockStmtOrExpr::Expr(Box::new(error_expr))); + // Replace arrow body with block containing throw statement + 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), + })], + })); + + // Generate unique name to avoid collisions + let unique_name = self.generate_unique_name("defaultWorkflow"); + + // Track for const declaration and workflowId assignment + self.default_workflow_exports.push(( + unique_name.clone(), + Expr::Arrow(arrow_expr.clone()), + arrow_expr.span, + )); + + // Track for replacement with identifier + self.default_exports_to_replace.push(( + "default".to_string(), + Expr::Ident(Ident::new( + unique_name.into(), + DUMMY_SP, + SyntaxContext::empty(), + )), + )); } } } diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/anonymous-default-workflow/output-client.js b/packages/swc-plugin-workflow/transform/tests/fixture/anonymous-default-workflow/output-client.js index ee4381b9f6..a0f42ecf57 100644 --- a/packages/swc-plugin-workflow/transform/tests/fixture/anonymous-default-workflow/output-client.js +++ b/packages/swc-plugin-workflow/transform/tests/fixture/anonymous-default-workflow/output-client.js @@ -1,5 +1,7 @@ // Test anonymous default export workflow /**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//default"}}}}*/; -export default async function() { +const defaultWorkflow = async function() { throw new Error("You attempted to execute workflow defaultWorkflow function directly. To start a workflow, use start(defaultWorkflow) from workflow/api"); -} +}; +defaultWorkflow.workflowId = "workflow//input.js//defaultWorkflow"; +export default defaultWorkflow; diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/default-arrow-workflow/output-client.js b/packages/swc-plugin-workflow/transform/tests/fixture/default-arrow-workflow/output-client.js index 64b6c3c6bc..5650e92df5 100644 --- a/packages/swc-plugin-workflow/transform/tests/fixture/default-arrow-workflow/output-client.js +++ b/packages/swc-plugin-workflow/transform/tests/fixture/default-arrow-workflow/output-client.js @@ -1,3 +1,7 @@ // Test default export arrow workflow /**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//default"}}}}*/; -export default (async (data)=>new Error("You attempted to execute workflow defaultWorkflow function directly. To start a workflow, use start(defaultWorkflow) from workflow/api")); +const defaultWorkflow = async (data)=>{ + throw new Error("You attempted to execute workflow defaultWorkflow function directly. To start a workflow, use start(defaultWorkflow) from workflow/api"); +}; +defaultWorkflow.workflowId = "workflow//input.js//defaultWorkflow"; +export default defaultWorkflow; From 1785b4d5517cd6b2cebeb72d84dea75be6707b50 Mon Sep 17 00:00:00 2001 From: Nathan Rajlich Date: Wed, 19 Nov 2025 23:41:16 -0800 Subject: [PATCH 5/9] . --- .../swc-plugin-workflow/transform/src/lib.rs | 111 +++++++++++------- .../output-client.js | 2 +- .../anonymous-default-workflow/output-step.js | 2 +- .../output-workflow.js | 2 +- .../default-arrow-workflow/output-client.js | 2 +- .../default-arrow-workflow/output-step.js | 2 +- .../default-arrow-workflow/output-workflow.js | 2 +- .../workflow-client-property/output-client.js | 3 +- .../workflow-client-property/output-step.js | 2 +- .../output-workflow.js | 2 +- 10 files changed, 80 insertions(+), 50 deletions(-) diff --git a/packages/swc-plugin-workflow/transform/src/lib.rs b/packages/swc-plugin-workflow/transform/src/lib.rs index 5fc7e690d6..abf031265b 100644 --- a/packages/swc-plugin-workflow/transform/src/lib.rs +++ b/packages/swc-plugin-workflow/transform/src/lib.rs @@ -1,7 +1,7 @@ mod naming; use serde::Deserialize; -use std::collections::HashSet; +use std::collections::{HashSet, HashMap}; use swc_core::{ common::{DUMMY_SP, SyntaxContext, errors::HANDLER}, ecma::{ @@ -158,6 +158,8 @@ pub struct StepTransform { step_function_names: HashSet, // Set of function names that are workflow functions workflow_function_names: HashSet, + // Map from export name to actual const name for default exports (e.g., "default" -> "defaultWorkflow") + workflow_export_to_const_name: std::collections::HashMap, // Set of function names that have been registered (to avoid duplicates) registered_functions: HashSet, // Collect registration calls for step mode @@ -445,6 +447,7 @@ impl StepTransform { has_file_workflow_directive: false, step_function_names: HashSet::new(), workflow_function_names: HashSet::new(), + workflow_export_to_const_name: HashMap::new(), registered_functions: HashSet::new(), registration_calls: Vec::new(), names: Vec::new(), @@ -1952,8 +1955,14 @@ impl StepTransform { let workflow_entries: Vec = sorted_workflow_names .into_iter() .map(|fn_name| { - let workflow_id = self.create_id(Some(fn_name), DUMMY_SP, true); - format!("\"{}\":{{\"workflowId\":\"{}\"}}", fn_name, workflow_id) + // Check if this export name has a different const name (e.g., "default" -> "defaultWorkflow") + let fn_name_str: &str = fn_name; + let actual_name = self.workflow_export_to_const_name + .get(fn_name_str) + .map(|s| s.as_str()) + .unwrap_or(fn_name_str); + let workflow_id = self.create_id(Some(actual_name), DUMMY_SP, true); + format!("\"{}\":{{\"workflowId\":\"{}\"}}", fn_name_str, workflow_id) }) .collect(); @@ -4261,7 +4270,20 @@ impl VisitMut for StepTransform { if self.should_transform_workflow_function(&fn_expr.function, true) { if self.validate_async_function(&fn_expr.function, fn_expr.function.span) { - self.workflow_function_names.insert(fn_name.clone()); + // For ALL default exports, track mapping from "default" to actual const name + let const_name = if fn_name == "default" { + // Anonymous: generate unique name + let unique_name = self.generate_unique_name("defaultWorkflow"); + self.workflow_export_to_const_name.insert("default".to_string(), unique_name.clone()); + unique_name + } else { + // Named: use the function name + self.workflow_export_to_const_name.insert("default".to_string(), fn_name.clone()); + fn_name.clone() + }; + + // Always use "default" as the metadata key for default exports + self.workflow_function_names.insert("default".to_string()); match self.mode { TransformMode::Step => { @@ -4273,12 +4295,9 @@ impl VisitMut for StepTransform { if fn_name == "default" { // Anonymous default export: convert to const declaration - // Generate unique name to avoid collisions - let unique_name = self.generate_unique_name("defaultWorkflow"); - // Track for const declaration and workflowId assignment self.default_workflow_exports.push(( - unique_name.clone(), + const_name.clone(), Expr::Fn(fn_expr.clone()), fn_expr.function.span, )); @@ -4287,7 +4306,7 @@ impl VisitMut for StepTransform { self.default_exports_to_replace.push(( fn_name.clone(), Expr::Ident(Ident::new( - unique_name.into(), + const_name.into(), DUMMY_SP, SyntaxContext::empty(), )), @@ -4297,7 +4316,7 @@ impl VisitMut for StepTransform { // export default async function name() { ... } // name.workflowId = "..."; self.workflow_exports_to_expand.push(( - fn_name.clone(), + const_name, Expr::Fn(fn_expr.clone()), fn_expr.function.span, )); @@ -4306,16 +4325,12 @@ impl VisitMut for StepTransform { TransformMode::Client => { // In client mode, replace workflow function body with error throw self.remove_use_workflow_directive(&mut fn_expr.function.body); - let actual_name = fn_expr - .ident - .as_ref() - .map(|i| i.sym.to_string()) - .unwrap_or_else(|| "defaultWorkflow".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 + ); 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", - actual_name, actual_name - ); let error_expr = Expr::New(NewExpr { span: DUMMY_SP, ctxt: SyntaxContext::empty(), @@ -4342,12 +4357,9 @@ impl VisitMut for StepTransform { // For anonymous functions, convert to const declaration so we can assign workflowId if fn_name == "default" { - // Generate unique name to avoid collisions - let unique_name = self.generate_unique_name("defaultWorkflow"); - // Track for const declaration and workflowId assignment self.default_workflow_exports.push(( - unique_name.clone(), + const_name.clone(), Expr::Fn(fn_expr.clone()), fn_expr.function.span, )); @@ -4356,7 +4368,7 @@ impl VisitMut for StepTransform { self.default_exports_to_replace.push(( fn_name.clone(), Expr::Ident(Ident::new( - unique_name.into(), + const_name.into(), DUMMY_SP, SyntaxContext::empty(), )), @@ -4364,7 +4376,7 @@ impl VisitMut for StepTransform { } else { // Named function can be referenced directly self.workflow_functions_needing_id - .push((actual_name, fn_expr.function.span)); + .push((const_name, fn_expr.function.span)); } } } @@ -4436,7 +4448,9 @@ impl VisitMut for StepTransform { // Anonymous function: export default async function() { ... } if self.should_transform_workflow_function(&fn_expr.function, true) { if self.validate_async_function(&fn_expr.function, fn_expr.function.span) { - self.workflow_function_names.insert("default".to_string()); + // Generate unique name first so we can use it in workflow_function_names + let unique_name = self.generate_unique_name("defaultWorkflow"); + self.workflow_function_names.insert(unique_name.clone()); match self.mode { TransformMode::Step => { @@ -4446,9 +4460,6 @@ impl VisitMut for StepTransform { // In workflow mode, convert to const declaration self.remove_use_workflow_directive(&mut fn_expr.function.body); - // Generate unique name to avoid collisions - let unique_name = self.generate_unique_name("defaultWorkflow"); - // Track for const declaration and workflowId assignment self.default_workflow_exports.push(( unique_name.clone(), @@ -4469,8 +4480,11 @@ impl VisitMut for StepTransform { TransformMode::Client => { // In client mode, replace workflow function body with error throw self.remove_use_workflow_directive(&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", + unique_name, unique_name + ); if let Some(body) = &mut fn_expr.function.body { - let error_msg = "You attempted to execute workflow defaultWorkflow function directly. To start a workflow, use start(defaultWorkflow) from workflow/api"; let error_expr = Expr::New(NewExpr { span: DUMMY_SP, ctxt: SyntaxContext::empty(), @@ -4494,8 +4508,23 @@ impl VisitMut for StepTransform { arg: Box::new(error_expr), })]; } - self.workflow_functions_needing_id - .push(("defaultWorkflow".to_string(), fn_expr.function.span)); + + // Track for const declaration and workflowId assignment + self.default_workflow_exports.push(( + unique_name.clone(), + Expr::Fn(fn_expr.clone()), + fn_expr.function.span, + )); + + // Track for replacement with identifier + self.default_exports_to_replace.push(( + "default".to_string(), + Expr::Ident(Ident::new( + unique_name.into(), + DUMMY_SP, + SyntaxContext::empty(), + )), + )); } } } @@ -4516,8 +4545,13 @@ impl VisitMut for StepTransform { directive: "use workflow", }); } else { + // For arrow function default exports, generate unique name and track mapping + let unique_name = self.generate_unique_name("defaultWorkflow"); + self.workflow_export_to_const_name.insert("default".to_string(), unique_name.clone()); + + // Always use "default" as the metadata key for default exports self.workflow_function_names.insert("default".to_string()); - + match self.mode { TransformMode::Step => { // Workflow functions are not processed in step mode @@ -4526,9 +4560,6 @@ impl VisitMut for StepTransform { // In workflow mode, convert to const declaration self.remove_use_workflow_directive_arrow(&mut arrow_expr.body); - // Generate unique name to avoid collisions - let unique_name = self.generate_unique_name("defaultWorkflow"); - // Track for const declaration and workflowId assignment self.default_workflow_exports.push(( unique_name.clone(), @@ -4549,7 +4580,10 @@ impl VisitMut for StepTransform { TransformMode::Client => { // In client mode, convert to const declaration so we can assign workflowId self.remove_use_workflow_directive_arrow(&mut arrow_expr.body); - let error_msg = "You attempted to execute workflow defaultWorkflow function directly. To start a workflow, use start(defaultWorkflow) from workflow/api"; + let error_msg = format!( + "You attempted to execute workflow {} function directly. To start a workflow, use start({}) from workflow/api", + unique_name, unique_name + ); let error_expr = Expr::New(NewExpr { span: DUMMY_SP, ctxt: SyntaxContext::empty(), @@ -4577,9 +4611,6 @@ impl VisitMut for StepTransform { arg: Box::new(error_expr), })], })); - - // Generate unique name to avoid collisions - let unique_name = self.generate_unique_name("defaultWorkflow"); // Track for const declaration and workflowId assignment self.default_workflow_exports.push(( diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/anonymous-default-workflow/output-client.js b/packages/swc-plugin-workflow/transform/tests/fixture/anonymous-default-workflow/output-client.js index a0f42ecf57..962d57c09a 100644 --- a/packages/swc-plugin-workflow/transform/tests/fixture/anonymous-default-workflow/output-client.js +++ b/packages/swc-plugin-workflow/transform/tests/fixture/anonymous-default-workflow/output-client.js @@ -1,5 +1,5 @@ // Test anonymous default export workflow -/**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//default"}}}}*/; +/**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//defaultWorkflow"}}}}*/; const defaultWorkflow = async function() { throw new Error("You attempted to execute workflow defaultWorkflow function directly. To start a workflow, use start(defaultWorkflow) from workflow/api"); }; diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/anonymous-default-workflow/output-step.js b/packages/swc-plugin-workflow/transform/tests/fixture/anonymous-default-workflow/output-step.js index 596d6b8e43..da6517878c 100644 --- a/packages/swc-plugin-workflow/transform/tests/fixture/anonymous-default-workflow/output-step.js +++ b/packages/swc-plugin-workflow/transform/tests/fixture/anonymous-default-workflow/output-step.js @@ -1,5 +1,5 @@ // Test anonymous default export workflow -/**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//default"}}}}*/; +/**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//defaultWorkflow"}}}}*/; export default async function() { 'use workflow'; const result = await someStep(); diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/anonymous-default-workflow/output-workflow.js b/packages/swc-plugin-workflow/transform/tests/fixture/anonymous-default-workflow/output-workflow.js index 008fe0648e..458c473a28 100644 --- a/packages/swc-plugin-workflow/transform/tests/fixture/anonymous-default-workflow/output-workflow.js +++ b/packages/swc-plugin-workflow/transform/tests/fixture/anonymous-default-workflow/output-workflow.js @@ -1,5 +1,5 @@ // Test anonymous default export workflow -/**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//default"}}}}*/; +/**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//defaultWorkflow"}}}}*/; const defaultWorkflow = async function() { const result = await someStep(); return result; diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/default-arrow-workflow/output-client.js b/packages/swc-plugin-workflow/transform/tests/fixture/default-arrow-workflow/output-client.js index 5650e92df5..f4283a1b9a 100644 --- a/packages/swc-plugin-workflow/transform/tests/fixture/default-arrow-workflow/output-client.js +++ b/packages/swc-plugin-workflow/transform/tests/fixture/default-arrow-workflow/output-client.js @@ -1,5 +1,5 @@ // Test default export arrow workflow -/**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//default"}}}}*/; +/**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//defaultWorkflow"}}}}*/; const defaultWorkflow = async (data)=>{ throw new Error("You attempted to execute workflow defaultWorkflow function directly. To start a workflow, use start(defaultWorkflow) from workflow/api"); }; diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/default-arrow-workflow/output-step.js b/packages/swc-plugin-workflow/transform/tests/fixture/default-arrow-workflow/output-step.js index ea5ee1fac0..9e295e382e 100644 --- a/packages/swc-plugin-workflow/transform/tests/fixture/default-arrow-workflow/output-step.js +++ b/packages/swc-plugin-workflow/transform/tests/fixture/default-arrow-workflow/output-step.js @@ -1,5 +1,5 @@ // Test default export arrow workflow -/**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//default"}}}}*/; +/**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//defaultWorkflow"}}}}*/; export default (async (data)=>{ 'use workflow'; const processed = await processData(data); diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/default-arrow-workflow/output-workflow.js b/packages/swc-plugin-workflow/transform/tests/fixture/default-arrow-workflow/output-workflow.js index 2c53916f01..38945f2687 100644 --- a/packages/swc-plugin-workflow/transform/tests/fixture/default-arrow-workflow/output-workflow.js +++ b/packages/swc-plugin-workflow/transform/tests/fixture/default-arrow-workflow/output-workflow.js @@ -1,5 +1,5 @@ // Test default export arrow workflow -/**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//default"}}}}*/; +/**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//defaultWorkflow"}}}}*/; const defaultWorkflow = async (data)=>{ const processed = await processData(data); return processed; diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/workflow-client-property/output-client.js b/packages/swc-plugin-workflow/transform/tests/fixture/workflow-client-property/output-client.js index 32024d9160..50958bd1aa 100644 --- a/packages/swc-plugin-workflow/transform/tests/fixture/workflow-client-property/output-client.js +++ b/packages/swc-plugin-workflow/transform/tests/fixture/workflow-client-property/output-client.js @@ -1,5 +1,5 @@ // Test workflow functions in client mode -/**__internal_workflows{"workflows":{"input.js":{"arrowWorkflow":{"workflowId":"workflow//input.js//arrowWorkflow"},"defaultWorkflow":{"workflowId":"workflow//input.js//defaultWorkflow"},"internalWorkflow":{"workflowId":"workflow//input.js//internalWorkflow"},"myWorkflow":{"workflowId":"workflow//input.js//myWorkflow"}}}}*/; +/**__internal_workflows{"workflows":{"input.js":{"arrowWorkflow":{"workflowId":"workflow//input.js//arrowWorkflow"},"default":{"workflowId":"workflow//input.js//defaultWorkflow"},"internalWorkflow":{"workflowId":"workflow//input.js//internalWorkflow"},"myWorkflow":{"workflowId":"workflow//input.js//myWorkflow"}}}}*/; export async function myWorkflow() { throw new Error("You attempted to execute workflow myWorkflow function directly. To start a workflow, use start(myWorkflow) from workflow/api"); } @@ -11,7 +11,6 @@ arrowWorkflow.workflowId = "workflow//input.js//arrowWorkflow"; export default async function defaultWorkflow() { throw new Error("You attempted to execute workflow defaultWorkflow function directly. To start a workflow, use start(defaultWorkflow) from workflow/api"); } -defaultWorkflow.workflowId = "workflow//input.js//defaultWorkflow"; // Non-export workflow function async function internalWorkflow() { throw new Error("You attempted to execute workflow internalWorkflow function directly. To start a workflow, use start(internalWorkflow) from workflow/api"); diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/workflow-client-property/output-step.js b/packages/swc-plugin-workflow/transform/tests/fixture/workflow-client-property/output-step.js index 0feaef5c2f..b68c25e659 100644 --- a/packages/swc-plugin-workflow/transform/tests/fixture/workflow-client-property/output-step.js +++ b/packages/swc-plugin-workflow/transform/tests/fixture/workflow-client-property/output-step.js @@ -1,5 +1,5 @@ // Test workflow functions in client mode -/**__internal_workflows{"workflows":{"input.js":{"arrowWorkflow":{"workflowId":"workflow//input.js//arrowWorkflow"},"defaultWorkflow":{"workflowId":"workflow//input.js//defaultWorkflow"},"internalWorkflow":{"workflowId":"workflow//input.js//internalWorkflow"},"myWorkflow":{"workflowId":"workflow//input.js//myWorkflow"}}}}*/; +/**__internal_workflows{"workflows":{"input.js":{"arrowWorkflow":{"workflowId":"workflow//input.js//arrowWorkflow"},"default":{"workflowId":"workflow//input.js//defaultWorkflow"},"internalWorkflow":{"workflowId":"workflow//input.js//internalWorkflow"},"myWorkflow":{"workflowId":"workflow//input.js//myWorkflow"}}}}*/; export async function myWorkflow() { 'use workflow'; const result = await someStep(); diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/workflow-client-property/output-workflow.js b/packages/swc-plugin-workflow/transform/tests/fixture/workflow-client-property/output-workflow.js index 27047e0516..50e333a180 100644 --- a/packages/swc-plugin-workflow/transform/tests/fixture/workflow-client-property/output-workflow.js +++ b/packages/swc-plugin-workflow/transform/tests/fixture/workflow-client-property/output-workflow.js @@ -1,5 +1,5 @@ // Test workflow functions in client mode -/**__internal_workflows{"workflows":{"input.js":{"arrowWorkflow":{"workflowId":"workflow//input.js//arrowWorkflow"},"defaultWorkflow":{"workflowId":"workflow//input.js//defaultWorkflow"},"internalWorkflow":{"workflowId":"workflow//input.js//internalWorkflow"},"myWorkflow":{"workflowId":"workflow//input.js//myWorkflow"}}}}*/; +/**__internal_workflows{"workflows":{"input.js":{"arrowWorkflow":{"workflowId":"workflow//input.js//arrowWorkflow"},"default":{"workflowId":"workflow//input.js//defaultWorkflow"},"internalWorkflow":{"workflowId":"workflow//input.js//internalWorkflow"},"myWorkflow":{"workflowId":"workflow//input.js//myWorkflow"}}}}*/; export async function myWorkflow() { const result = await someStep(); return result; From 69f2826a75706ca68aba6e014a9220dfd6699eb4 Mon Sep 17 00:00:00 2001 From: Nathan Rajlich Date: Thu, 20 Nov 2025 09:36:05 -0800 Subject: [PATCH 6/9] Apply Vade suggestion --- packages/swc-plugin-workflow/transform/src/lib.rs | 6 +++++- .../tests/fixture/expr-fn-default-workflow/input.js | 5 +++++ .../fixture/expr-fn-default-workflow/output-client.js | 6 ++++++ .../tests/fixture/expr-fn-default-workflow/output-step.js | 6 ++++++ .../fixture/expr-fn-default-workflow/output-workflow.js | 7 +++++++ 5 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 packages/swc-plugin-workflow/transform/tests/fixture/expr-fn-default-workflow/input.js create mode 100644 packages/swc-plugin-workflow/transform/tests/fixture/expr-fn-default-workflow/output-client.js create mode 100644 packages/swc-plugin-workflow/transform/tests/fixture/expr-fn-default-workflow/output-step.js create mode 100644 packages/swc-plugin-workflow/transform/tests/fixture/expr-fn-default-workflow/output-workflow.js diff --git a/packages/swc-plugin-workflow/transform/src/lib.rs b/packages/swc-plugin-workflow/transform/src/lib.rs index abf031265b..98cce4cc97 100644 --- a/packages/swc-plugin-workflow/transform/src/lib.rs +++ b/packages/swc-plugin-workflow/transform/src/lib.rs @@ -4450,7 +4450,11 @@ impl VisitMut for StepTransform { if self.validate_async_function(&fn_expr.function, fn_expr.function.span) { // Generate unique name first so we can use it in workflow_function_names let unique_name = self.generate_unique_name("defaultWorkflow"); - self.workflow_function_names.insert(unique_name.clone()); + // For function expression default exports, track mapping from "default" to actual const name + self.workflow_export_to_const_name.insert("default".to_string(), unique_name.clone()); + + // Always use "default" as the metadata key for default exports + self.workflow_function_names.insert("default".to_string()); match self.mode { TransformMode::Step => { diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/expr-fn-default-workflow/input.js b/packages/swc-plugin-workflow/transform/tests/fixture/expr-fn-default-workflow/input.js new file mode 100644 index 0000000000..3792efce31 --- /dev/null +++ b/packages/swc-plugin-workflow/transform/tests/fixture/expr-fn-default-workflow/input.js @@ -0,0 +1,5 @@ +export default async function() { + 'use workflow'; + const result = await someStep(); + return result; +} diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/expr-fn-default-workflow/output-client.js b/packages/swc-plugin-workflow/transform/tests/fixture/expr-fn-default-workflow/output-client.js new file mode 100644 index 0000000000..253b6532e6 --- /dev/null +++ b/packages/swc-plugin-workflow/transform/tests/fixture/expr-fn-default-workflow/output-client.js @@ -0,0 +1,6 @@ +/**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//defaultWorkflow"}}}}*/; +const defaultWorkflow = async function() { + throw new Error("You attempted to execute workflow defaultWorkflow function directly. To start a workflow, use start(defaultWorkflow) from workflow/api"); +}; +defaultWorkflow.workflowId = "workflow//input.js//defaultWorkflow"; +export default defaultWorkflow; diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/expr-fn-default-workflow/output-step.js b/packages/swc-plugin-workflow/transform/tests/fixture/expr-fn-default-workflow/output-step.js new file mode 100644 index 0000000000..cee7e378e6 --- /dev/null +++ b/packages/swc-plugin-workflow/transform/tests/fixture/expr-fn-default-workflow/output-step.js @@ -0,0 +1,6 @@ +/**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//defaultWorkflow"}}}}*/; +export default async function() { + 'use workflow'; + const result = await someStep(); + return result; +} diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/expr-fn-default-workflow/output-workflow.js b/packages/swc-plugin-workflow/transform/tests/fixture/expr-fn-default-workflow/output-workflow.js new file mode 100644 index 0000000000..5237174c52 --- /dev/null +++ b/packages/swc-plugin-workflow/transform/tests/fixture/expr-fn-default-workflow/output-workflow.js @@ -0,0 +1,7 @@ +/**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//defaultWorkflow"}}}}*/; +const defaultWorkflow = async function() { + const result = await someStep(); + return result; +}; +defaultWorkflow.workflowId = "workflow//input.js//defaultWorkflow"; +export default defaultWorkflow; From 3243a7b2b59f0320603235646abda1734facd886 Mon Sep 17 00:00:00 2001 From: Nathan Rajlich Date: Thu, 20 Nov 2025 09:44:44 -0800 Subject: [PATCH 7/9] . --- .../swc-plugin-workflow/transform/src/lib.rs | 28 ++++++++----------- .../workflow-client-property/output-client.js | 1 + 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/packages/swc-plugin-workflow/transform/src/lib.rs b/packages/swc-plugin-workflow/transform/src/lib.rs index 98cce4cc97..b70a920eff 100644 --- a/packages/swc-plugin-workflow/transform/src/lib.rs +++ b/packages/swc-plugin-workflow/transform/src/lib.rs @@ -2843,9 +2843,13 @@ impl VisitMut for StepTransform { } ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(default_decl)) => { if let DefaultDecl::Fn(fn_expr) = &default_decl.decl { - if let Some(ident) = &fn_expr.ident { - let fn_name = ident.sym.to_string(); - if self.workflow_function_names.contains(&fn_name) { + // Check if this is a workflow function by checking for "default" key + if self.workflow_function_names.contains("default") { + // Only add workflowId for named default exports + // Anonymous ones are handled by default_workflow_exports + if let Some(ident) = &fn_expr.ident { + // Named default export: use the function name + let fn_name = ident.sym.to_string(); items_to_insert.push(( i + 1, ModuleItem::Stmt(self.create_workflow_id_assignment( @@ -2854,18 +2858,7 @@ impl VisitMut for StepTransform { )), )); } - } else { - // Default export without name, use "defaultWorkflow" - let fn_name = "defaultWorkflow"; - if self.workflow_function_names.contains(fn_name) { - items_to_insert.push(( - i + 1, - ModuleItem::Stmt(self.create_workflow_id_assignment( - fn_name, - fn_expr.function.span, - )), - )); - } + // Anonymous default exports will have workflowId added by default_workflow_exports processing } } } @@ -4368,13 +4361,14 @@ impl VisitMut for StepTransform { self.default_exports_to_replace.push(( fn_name.clone(), Expr::Ident(Ident::new( - const_name.into(), + const_name.clone().into(), DUMMY_SP, SyntaxContext::empty(), )), )); + // workflowId assignment is handled by default_workflow_exports processing } else { - // Named function can be referenced directly + // Named function can be referenced directly, just add workflowId self.workflow_functions_needing_id .push((const_name, fn_expr.function.span)); } diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/workflow-client-property/output-client.js b/packages/swc-plugin-workflow/transform/tests/fixture/workflow-client-property/output-client.js index 50958bd1aa..1c6087fbcb 100644 --- a/packages/swc-plugin-workflow/transform/tests/fixture/workflow-client-property/output-client.js +++ b/packages/swc-plugin-workflow/transform/tests/fixture/workflow-client-property/output-client.js @@ -11,6 +11,7 @@ arrowWorkflow.workflowId = "workflow//input.js//arrowWorkflow"; export default async function defaultWorkflow() { throw new Error("You attempted to execute workflow defaultWorkflow function directly. To start a workflow, use start(defaultWorkflow) from workflow/api"); } +defaultWorkflow.workflowId = "workflow//input.js//defaultWorkflow"; // Non-export workflow function async function internalWorkflow() { throw new Error("You attempted to execute workflow internalWorkflow function directly. To start a workflow, use start(internalWorkflow) from workflow/api"); From 56bd0c83010b0f1a0bea49ff994ca6bccf3e5815 Mon Sep 17 00:00:00 2001 From: Nathan Rajlich Date: Thu, 20 Nov 2025 12:01:52 -0800 Subject: [PATCH 8/9] Add test fixture for naming conflict --- .../fixture/default-workflow-collision/input.js | 13 +++++++++++++ .../default-workflow-collision/output-client.js | 10 ++++++++++ .../default-workflow-collision/output-step.js | 11 +++++++++++ .../default-workflow-collision/output-workflow.js | 11 +++++++++++ 4 files changed, 45 insertions(+) create mode 100644 packages/swc-plugin-workflow/transform/tests/fixture/default-workflow-collision/input.js create mode 100644 packages/swc-plugin-workflow/transform/tests/fixture/default-workflow-collision/output-client.js create mode 100644 packages/swc-plugin-workflow/transform/tests/fixture/default-workflow-collision/output-step.js create mode 100644 packages/swc-plugin-workflow/transform/tests/fixture/default-workflow-collision/output-workflow.js diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/default-workflow-collision/input.js b/packages/swc-plugin-workflow/transform/tests/fixture/default-workflow-collision/input.js new file mode 100644 index 0000000000..83f29aafb6 --- /dev/null +++ b/packages/swc-plugin-workflow/transform/tests/fixture/default-workflow-collision/input.js @@ -0,0 +1,13 @@ +// Existing variable named defaultWorkflow +const defaultWorkflow = "existing variable"; + +// Use it to avoid unused variable +console.log(defaultWorkflow); + +// Anonymous default export should get unique name +export default async function() { + 'use workflow'; + const result = await someStep(); + return result; +} + diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/default-workflow-collision/output-client.js b/packages/swc-plugin-workflow/transform/tests/fixture/default-workflow-collision/output-client.js new file mode 100644 index 0000000000..6a9100b4e4 --- /dev/null +++ b/packages/swc-plugin-workflow/transform/tests/fixture/default-workflow-collision/output-client.js @@ -0,0 +1,10 @@ +// Existing variable named defaultWorkflow +/**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//defaultWorkflow$1"}}}}*/; +const defaultWorkflow = "existing variable"; +// Use it to avoid unused variable +console.log(defaultWorkflow); +const defaultWorkflow$1 = async function() { + throw new Error("You attempted to execute workflow defaultWorkflow$1 function directly. To start a workflow, use start(defaultWorkflow$1) from workflow/api"); +}; +defaultWorkflow$1.workflowId = "workflow//input.js//defaultWorkflow$1"; +export default defaultWorkflow$1; diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/default-workflow-collision/output-step.js b/packages/swc-plugin-workflow/transform/tests/fixture/default-workflow-collision/output-step.js new file mode 100644 index 0000000000..6073c67dff --- /dev/null +++ b/packages/swc-plugin-workflow/transform/tests/fixture/default-workflow-collision/output-step.js @@ -0,0 +1,11 @@ +// Existing variable named defaultWorkflow +/**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//defaultWorkflow$1"}}}}*/; +const defaultWorkflow = "existing variable"; +// Use it to avoid unused variable +console.log(defaultWorkflow); +// Anonymous default export should get unique name +export default async function() { + 'use workflow'; + const result = await someStep(); + return result; +} diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/default-workflow-collision/output-workflow.js b/packages/swc-plugin-workflow/transform/tests/fixture/default-workflow-collision/output-workflow.js new file mode 100644 index 0000000000..bbe9a63e0e --- /dev/null +++ b/packages/swc-plugin-workflow/transform/tests/fixture/default-workflow-collision/output-workflow.js @@ -0,0 +1,11 @@ +// Existing variable named defaultWorkflow +/**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//defaultWorkflow$1"}}}}*/; +const defaultWorkflow = "existing variable"; +// Use it to avoid unused variable +console.log(defaultWorkflow); +const defaultWorkflow$1 = async function() { + const result = await someStep(); + return result; +}; +defaultWorkflow$1.workflowId = "workflow//input.js//defaultWorkflow$1"; +export default defaultWorkflow$1; From dfa411250e97f66b4a9b4e57874ce6fa7d57d1b7 Mon Sep 17 00:00:00 2001 From: Nathan Rajlich Date: Thu, 20 Nov 2025 12:16:49 -0800 Subject: [PATCH 9/9] defaultWorkflow -> __default --- .../swc-plugin-workflow/transform/src/lib.rs | 10 +++++----- .../anonymous-default-workflow/output-client.js | 10 +++++----- .../anonymous-default-workflow/output-step.js | 2 +- .../output-workflow.js | 8 ++++---- .../default-arrow-workflow/output-client.js | 10 +++++----- .../default-arrow-workflow/output-step.js | 2 +- .../default-arrow-workflow/output-workflow.js | 8 ++++---- .../fixture/default-workflow-collision/input.js | 8 ++++---- .../default-workflow-collision/output-client.js | 16 ++++++++-------- .../default-workflow-collision/output-step.js | 10 +++++----- .../output-workflow.js | 14 +++++++------- .../expr-fn-default-workflow/output-client.js | 10 +++++----- .../expr-fn-default-workflow/output-step.js | 2 +- .../expr-fn-default-workflow/output-workflow.js | 8 ++++---- 14 files changed, 59 insertions(+), 59 deletions(-) diff --git a/packages/swc-plugin-workflow/transform/src/lib.rs b/packages/swc-plugin-workflow/transform/src/lib.rs index b70a920eff..ac490f47ae 100644 --- a/packages/swc-plugin-workflow/transform/src/lib.rs +++ b/packages/swc-plugin-workflow/transform/src/lib.rs @@ -158,7 +158,7 @@ pub struct StepTransform { step_function_names: HashSet, // Set of function names that are workflow functions workflow_function_names: HashSet, - // Map from export name to actual const name for default exports (e.g., "default" -> "defaultWorkflow") + // Map from export name to actual const name for default exports (e.g., "default" -> "__default") workflow_export_to_const_name: std::collections::HashMap, // Set of function names that have been registered (to avoid duplicates) registered_functions: HashSet, @@ -1955,7 +1955,7 @@ impl StepTransform { let workflow_entries: Vec = sorted_workflow_names .into_iter() .map(|fn_name| { - // Check if this export name has a different const name (e.g., "default" -> "defaultWorkflow") + // Check if this export name has a different const name (e.g., "default" -> "__default") let fn_name_str: &str = fn_name; let actual_name = self.workflow_export_to_const_name .get(fn_name_str) @@ -4266,7 +4266,7 @@ impl VisitMut for StepTransform { // For ALL default exports, track mapping from "default" to actual const name let const_name = if fn_name == "default" { // Anonymous: generate unique name - let unique_name = self.generate_unique_name("defaultWorkflow"); + let unique_name = self.generate_unique_name("__default"); self.workflow_export_to_const_name.insert("default".to_string(), unique_name.clone()); unique_name } else { @@ -4443,7 +4443,7 @@ impl VisitMut for StepTransform { if self.should_transform_workflow_function(&fn_expr.function, true) { if self.validate_async_function(&fn_expr.function, fn_expr.function.span) { // Generate unique name first so we can use it in workflow_function_names - let unique_name = self.generate_unique_name("defaultWorkflow"); + let unique_name = self.generate_unique_name("__default"); // For function expression default exports, track mapping from "default" to actual const name self.workflow_export_to_const_name.insert("default".to_string(), unique_name.clone()); @@ -4544,7 +4544,7 @@ impl VisitMut for StepTransform { }); } else { // For arrow function default exports, generate unique name and track mapping - let unique_name = self.generate_unique_name("defaultWorkflow"); + let unique_name = self.generate_unique_name("__default"); self.workflow_export_to_const_name.insert("default".to_string(), unique_name.clone()); // Always use "default" as the metadata key for default exports diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/anonymous-default-workflow/output-client.js b/packages/swc-plugin-workflow/transform/tests/fixture/anonymous-default-workflow/output-client.js index 962d57c09a..56e4f7406d 100644 --- a/packages/swc-plugin-workflow/transform/tests/fixture/anonymous-default-workflow/output-client.js +++ b/packages/swc-plugin-workflow/transform/tests/fixture/anonymous-default-workflow/output-client.js @@ -1,7 +1,7 @@ // Test anonymous default export workflow -/**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//defaultWorkflow"}}}}*/; -const defaultWorkflow = async function() { - throw new Error("You attempted to execute workflow defaultWorkflow function directly. To start a workflow, use start(defaultWorkflow) from workflow/api"); +/**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//__default"}}}}*/; +const __default = async function() { + throw new Error("You attempted to execute workflow __default function directly. To start a workflow, use start(__default) from workflow/api"); }; -defaultWorkflow.workflowId = "workflow//input.js//defaultWorkflow"; -export default defaultWorkflow; +__default.workflowId = "workflow//input.js//__default"; +export default __default; diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/anonymous-default-workflow/output-step.js b/packages/swc-plugin-workflow/transform/tests/fixture/anonymous-default-workflow/output-step.js index da6517878c..b077c5b143 100644 --- a/packages/swc-plugin-workflow/transform/tests/fixture/anonymous-default-workflow/output-step.js +++ b/packages/swc-plugin-workflow/transform/tests/fixture/anonymous-default-workflow/output-step.js @@ -1,5 +1,5 @@ // Test anonymous default export workflow -/**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//defaultWorkflow"}}}}*/; +/**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//__default"}}}}*/; export default async function() { 'use workflow'; const result = await someStep(); diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/anonymous-default-workflow/output-workflow.js b/packages/swc-plugin-workflow/transform/tests/fixture/anonymous-default-workflow/output-workflow.js index 458c473a28..aef7b24c0f 100644 --- a/packages/swc-plugin-workflow/transform/tests/fixture/anonymous-default-workflow/output-workflow.js +++ b/packages/swc-plugin-workflow/transform/tests/fixture/anonymous-default-workflow/output-workflow.js @@ -1,8 +1,8 @@ // Test anonymous default export workflow -/**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//defaultWorkflow"}}}}*/; -const defaultWorkflow = async function() { +/**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//__default"}}}}*/; +const __default = async function() { const result = await someStep(); return result; }; -defaultWorkflow.workflowId = "workflow//input.js//defaultWorkflow"; -export default defaultWorkflow; +__default.workflowId = "workflow//input.js//__default"; +export default __default; diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/default-arrow-workflow/output-client.js b/packages/swc-plugin-workflow/transform/tests/fixture/default-arrow-workflow/output-client.js index f4283a1b9a..8e26bc6cb2 100644 --- a/packages/swc-plugin-workflow/transform/tests/fixture/default-arrow-workflow/output-client.js +++ b/packages/swc-plugin-workflow/transform/tests/fixture/default-arrow-workflow/output-client.js @@ -1,7 +1,7 @@ // Test default export arrow workflow -/**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//defaultWorkflow"}}}}*/; -const defaultWorkflow = async (data)=>{ - throw new Error("You attempted to execute workflow defaultWorkflow function directly. To start a workflow, use start(defaultWorkflow) from workflow/api"); +/**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//__default"}}}}*/; +const __default = async (data)=>{ + throw new Error("You attempted to execute workflow __default function directly. To start a workflow, use start(__default) from workflow/api"); }; -defaultWorkflow.workflowId = "workflow//input.js//defaultWorkflow"; -export default defaultWorkflow; +__default.workflowId = "workflow//input.js//__default"; +export default __default; diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/default-arrow-workflow/output-step.js b/packages/swc-plugin-workflow/transform/tests/fixture/default-arrow-workflow/output-step.js index 9e295e382e..e2a524628f 100644 --- a/packages/swc-plugin-workflow/transform/tests/fixture/default-arrow-workflow/output-step.js +++ b/packages/swc-plugin-workflow/transform/tests/fixture/default-arrow-workflow/output-step.js @@ -1,5 +1,5 @@ // Test default export arrow workflow -/**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//defaultWorkflow"}}}}*/; +/**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//__default"}}}}*/; export default (async (data)=>{ 'use workflow'; const processed = await processData(data); diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/default-arrow-workflow/output-workflow.js b/packages/swc-plugin-workflow/transform/tests/fixture/default-arrow-workflow/output-workflow.js index 38945f2687..f79827ce06 100644 --- a/packages/swc-plugin-workflow/transform/tests/fixture/default-arrow-workflow/output-workflow.js +++ b/packages/swc-plugin-workflow/transform/tests/fixture/default-arrow-workflow/output-workflow.js @@ -1,8 +1,8 @@ // Test default export arrow workflow -/**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//defaultWorkflow"}}}}*/; -const defaultWorkflow = async (data)=>{ +/**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//__default"}}}}*/; +const __default = async (data)=>{ const processed = await processData(data); return processed; }; -defaultWorkflow.workflowId = "workflow//input.js//defaultWorkflow"; -export default defaultWorkflow; +__default.workflowId = "workflow//input.js//__default"; +export default __default; diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/default-workflow-collision/input.js b/packages/swc-plugin-workflow/transform/tests/fixture/default-workflow-collision/input.js index 83f29aafb6..d1f3cca101 100644 --- a/packages/swc-plugin-workflow/transform/tests/fixture/default-workflow-collision/input.js +++ b/packages/swc-plugin-workflow/transform/tests/fixture/default-workflow-collision/input.js @@ -1,10 +1,10 @@ -// Existing variable named defaultWorkflow -const defaultWorkflow = "existing variable"; +// Existing variable named __default +const __default = "existing variable"; // Use it to avoid unused variable -console.log(defaultWorkflow); +console.log(__default); -// Anonymous default export should get unique name +// Anonymous default export should get unique name (__default$1) export default async function() { 'use workflow'; const result = await someStep(); diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/default-workflow-collision/output-client.js b/packages/swc-plugin-workflow/transform/tests/fixture/default-workflow-collision/output-client.js index 6a9100b4e4..bcd09ef340 100644 --- a/packages/swc-plugin-workflow/transform/tests/fixture/default-workflow-collision/output-client.js +++ b/packages/swc-plugin-workflow/transform/tests/fixture/default-workflow-collision/output-client.js @@ -1,10 +1,10 @@ -// Existing variable named defaultWorkflow -/**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//defaultWorkflow$1"}}}}*/; -const defaultWorkflow = "existing variable"; +// Existing variable named __default +/**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//__default$1"}}}}*/; +const __default = "existing variable"; // Use it to avoid unused variable -console.log(defaultWorkflow); -const defaultWorkflow$1 = async function() { - throw new Error("You attempted to execute workflow defaultWorkflow$1 function directly. To start a workflow, use start(defaultWorkflow$1) from workflow/api"); +console.log(__default); +const __default$1 = async function() { + throw new Error("You attempted to execute workflow __default$1 function directly. To start a workflow, use start(__default$1) from workflow/api"); }; -defaultWorkflow$1.workflowId = "workflow//input.js//defaultWorkflow$1"; -export default defaultWorkflow$1; +__default$1.workflowId = "workflow//input.js//__default$1"; +export default __default$1; diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/default-workflow-collision/output-step.js b/packages/swc-plugin-workflow/transform/tests/fixture/default-workflow-collision/output-step.js index 6073c67dff..ef284f7a6c 100644 --- a/packages/swc-plugin-workflow/transform/tests/fixture/default-workflow-collision/output-step.js +++ b/packages/swc-plugin-workflow/transform/tests/fixture/default-workflow-collision/output-step.js @@ -1,9 +1,9 @@ -// Existing variable named defaultWorkflow -/**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//defaultWorkflow$1"}}}}*/; -const defaultWorkflow = "existing variable"; +// Existing variable named __default +/**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//__default$1"}}}}*/; +const __default = "existing variable"; // Use it to avoid unused variable -console.log(defaultWorkflow); -// Anonymous default export should get unique name +console.log(__default); +// Anonymous default export should get unique name (__default$1) export default async function() { 'use workflow'; const result = await someStep(); diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/default-workflow-collision/output-workflow.js b/packages/swc-plugin-workflow/transform/tests/fixture/default-workflow-collision/output-workflow.js index bbe9a63e0e..a384d1b4e8 100644 --- a/packages/swc-plugin-workflow/transform/tests/fixture/default-workflow-collision/output-workflow.js +++ b/packages/swc-plugin-workflow/transform/tests/fixture/default-workflow-collision/output-workflow.js @@ -1,11 +1,11 @@ -// Existing variable named defaultWorkflow -/**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//defaultWorkflow$1"}}}}*/; -const defaultWorkflow = "existing variable"; +// Existing variable named __default +/**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//__default$1"}}}}*/; +const __default = "existing variable"; // Use it to avoid unused variable -console.log(defaultWorkflow); -const defaultWorkflow$1 = async function() { +console.log(__default); +const __default$1 = async function() { const result = await someStep(); return result; }; -defaultWorkflow$1.workflowId = "workflow//input.js//defaultWorkflow$1"; -export default defaultWorkflow$1; +__default$1.workflowId = "workflow//input.js//__default$1"; +export default __default$1; diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/expr-fn-default-workflow/output-client.js b/packages/swc-plugin-workflow/transform/tests/fixture/expr-fn-default-workflow/output-client.js index 253b6532e6..224a09903c 100644 --- a/packages/swc-plugin-workflow/transform/tests/fixture/expr-fn-default-workflow/output-client.js +++ b/packages/swc-plugin-workflow/transform/tests/fixture/expr-fn-default-workflow/output-client.js @@ -1,6 +1,6 @@ -/**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//defaultWorkflow"}}}}*/; -const defaultWorkflow = async function() { - throw new Error("You attempted to execute workflow defaultWorkflow function directly. To start a workflow, use start(defaultWorkflow) from workflow/api"); +/**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//__default"}}}}*/; +const __default = async function() { + throw new Error("You attempted to execute workflow __default function directly. To start a workflow, use start(__default) from workflow/api"); }; -defaultWorkflow.workflowId = "workflow//input.js//defaultWorkflow"; -export default defaultWorkflow; +__default.workflowId = "workflow//input.js//__default"; +export default __default; diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/expr-fn-default-workflow/output-step.js b/packages/swc-plugin-workflow/transform/tests/fixture/expr-fn-default-workflow/output-step.js index cee7e378e6..5a3b5959f7 100644 --- a/packages/swc-plugin-workflow/transform/tests/fixture/expr-fn-default-workflow/output-step.js +++ b/packages/swc-plugin-workflow/transform/tests/fixture/expr-fn-default-workflow/output-step.js @@ -1,4 +1,4 @@ -/**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//defaultWorkflow"}}}}*/; +/**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//__default"}}}}*/; export default async function() { 'use workflow'; const result = await someStep(); diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/expr-fn-default-workflow/output-workflow.js b/packages/swc-plugin-workflow/transform/tests/fixture/expr-fn-default-workflow/output-workflow.js index 5237174c52..06a92baef9 100644 --- a/packages/swc-plugin-workflow/transform/tests/fixture/expr-fn-default-workflow/output-workflow.js +++ b/packages/swc-plugin-workflow/transform/tests/fixture/expr-fn-default-workflow/output-workflow.js @@ -1,7 +1,7 @@ -/**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//defaultWorkflow"}}}}*/; -const defaultWorkflow = async function() { +/**__internal_workflows{"workflows":{"input.js":{"default":{"workflowId":"workflow//input.js//__default"}}}}*/; +const __default = async function() { const result = await someStep(); return result; }; -defaultWorkflow.workflowId = "workflow//input.js//defaultWorkflow"; -export default defaultWorkflow; +__default.workflowId = "workflow//input.js//__default"; +export default __default;