diff --git a/.changeset/sixty-carpets-join.md b/.changeset/sixty-carpets-join.md new file mode 100644 index 000000000..0848f71a6 --- /dev/null +++ b/.changeset/sixty-carpets-join.md @@ -0,0 +1,5 @@ +--- +"@workflow/swc-plugin": patch +--- + +Support nested "use step" declarations in non-workflow functions diff --git a/packages/swc-plugin-workflow/transform/src/lib.rs b/packages/swc-plugin-workflow/transform/src/lib.rs index 62f8e2371..934a6e79d 100644 --- a/packages/swc-plugin-workflow/transform/src/lib.rs +++ b/packages/swc-plugin-workflow/transform/src/lib.rs @@ -174,6 +174,8 @@ pub struct StepTransform { in_workflow_function: bool, // Track the current workflow function name (for nested step naming) current_workflow_function_name: Option, + // Track the current parent function name (for all functions, not just workflows) + current_parent_function_name: Option, // Track workflow functions that need to be expanded into multiple exports workflow_exports_to_expand: Vec<(String, Expr, swc_core::common::Span)>, // Track workflow functions that need workflowId property in client mode @@ -191,7 +193,14 @@ pub struct StepTransform { object_property_step_functions: Vec<(String, String, ArrowExpr, swc_core::common::Span)>, // Track nested step functions inside workflow functions for hoisting in step mode // (fn_name, fn_expr, span, closure_vars, was_arrow, parent_workflow_name) - nested_step_functions: Vec<(String, FnExpr, swc_core::common::Span, Vec, bool, String)>, + nested_step_functions: Vec<( + String, + FnExpr, + swc_core::common::Span, + Vec, + bool, + String, + )>, // Counter for anonymous function names #[allow(dead_code)] anonymous_fn_counter: usize, @@ -698,7 +707,7 @@ impl StepTransform { if self.validate_async_function(&fn_decl.function, fn_decl.function.span) { self.step_function_names.insert(fn_name.clone()); - if self.in_workflow_function { + if !self.in_module_level { match self.mode { TransformMode::Step => { // Clone the function and remove the directive before hoisting @@ -722,14 +731,54 @@ impl StepTransform { fn_decl.function.span, closure_vars, false, // Regular function, not arrow - self.current_workflow_function_name.clone().unwrap_or_default(), + self.current_parent_function_name + .clone() + .unwrap_or_default(), )); - *stmt = Stmt::Empty(EmptyStmt { span: DUMMY_SP }); + + // Replace with const declaration referencing the hoisted function + let hoisted_name = + if let Some(parent) = &self.current_parent_function_name { + if !parent.is_empty() { + format!("{}${}", parent, fn_name) + } else { + fn_name.clone() + } + } else { + fn_name.clone() + }; + + let var_decl = Decl::Var(Box::new(VarDecl { + span: DUMMY_SP, + ctxt: SyntaxContext::empty(), + kind: VarDeclKind::Const, + decls: vec![VarDeclarator { + span: DUMMY_SP, + name: Pat::Ident(BindingIdent { + id: Ident::new( + fn_name.clone().into(), + DUMMY_SP, + SyntaxContext::empty(), + ), + type_ann: None, + }), + init: Some(Box::new(Expr::Ident(Ident::new( + hoisted_name.into(), + DUMMY_SP, + SyntaxContext::empty(), + )))), + definite: false, + }], + declare: false, + })); + *stmt = Stmt::Decl(var_decl); return; } TransformMode::Workflow => { // Include parent workflow name in step ID - let step_fn_name = if let Some(parent) = &self.current_workflow_function_name { + let step_fn_name = if let Some(parent) = + &self.current_workflow_function_name + { format!("{}/{}", parent, fn_name) } else { fn_name.clone() @@ -773,7 +822,8 @@ impl StepTransform { return; } TransformMode::Client => { - *stmt = Stmt::Empty(EmptyStmt { span: DUMMY_SP }); + // In client mode, just remove the directive and keep the function + self.remove_use_step_directive(&mut fn_decl.function.body); return; } } @@ -879,7 +929,7 @@ impl StepTransform { if let Some(init) = &mut declarator.init { if let Pat::Ident(binding) = &declarator.name { let name = binding.id.sym.to_string(); - + // Check if the initializer is an arrow function with object literal body if let Expr::Arrow(arrow_expr) = &mut **init { match &mut *arrow_expr.body { @@ -896,9 +946,11 @@ impl StepTransform { } _ => None, }; - + if let Some(obj_lit) = obj_lit_mut { - self.process_object_properties_for_step_functions(obj_lit, &name); + self.process_object_properties_for_step_functions( + obj_lit, &name, + ); } } _ => {} @@ -932,6 +984,7 @@ impl StepTransform { in_step_function: false, in_workflow_function: false, current_workflow_function_name: None, + current_parent_function_name: None, workflow_exports_to_expand: Vec::new(), workflow_functions_needing_id: Vec::new(), step_exports_to_convert: Vec::new(), @@ -2239,13 +2292,6 @@ impl StepTransform { ModuleItem::Stmt(Stmt::Decl(Decl::Var(var_decl))) => { // Check if all variables in this declaration are unused var_decl.decls.iter().all(|declarator| { - // Don't remove if the initializer contains functions (which might have directives) - if let Some(init) = &declarator.init { - if self.contains_function_expr(init) { - return false; - } - } - match &declarator.name { Pat::Ident(binding) => { let name = binding.id.sym.to_string(); @@ -2340,20 +2386,6 @@ impl StepTransform { } } - // Helper to check if an expression contains a function expression or object with methods - fn contains_function_expr(&self, expr: &Expr) -> bool { - match expr { - Expr::Fn(_) | Expr::Arrow(_) => true, - Expr::Object(obj_lit) => { - // Check if object contains any method properties - obj_lit.props.iter().any( - |prop| matches!(prop, PropOrSpread::Prop(p) if matches!(&**p, Prop::Method(_))), - ) - } - _ => false, - } - } - // Helper to check if all bindings in an array pattern are unused fn all_bindings_unused( &self, @@ -2680,6 +2712,11 @@ impl<'a> VisitMut for ComprehensiveUsageCollector<'a> { let prev_function = self.current_function.clone(); self.current_function = Some(fn_name.clone()); + // Visit function parameters (which can contain default values that use other identifiers) + for param in &mut fn_decl.function.params { + param.visit_mut_with(self); + } + // Visit the function content to find used identifiers (but don't mark the function name itself as used) if let Some(body) = &mut fn_decl.function.body { body.visit_mut_with(self); @@ -2824,8 +2861,16 @@ impl VisitMut for StepTransform { // Process nested step functions FIRST (they typically appear earlier in source) let nested_functions: Vec<_> = self.nested_step_functions.drain(..).collect(); - - for (fn_name, mut fn_expr, span, closure_vars, was_arrow, parent_workflow_name) in nested_functions { + + for ( + fn_name, + mut fn_expr, + span, + closure_vars, + was_arrow, + parent_workflow_name, + ) in nested_functions + { // Generate hoisted name including parent workflow function name let hoisted_name = if parent_workflow_name.is_empty() { fn_name.clone() @@ -3960,7 +4005,14 @@ impl VisitMut for StepTransform { } } + // Track parent function name for nested step hoisting + let old_parent_name = self.current_parent_function_name.clone(); + self.current_parent_function_name = Some(fn_name); + fn_decl.visit_mut_children_with(self); + + // Restore parent function name + self.current_parent_function_name = old_parent_name; } fn visit_mut_stmt(&mut self, stmt: &mut Stmt) { @@ -4496,8 +4548,12 @@ impl VisitMut for StepTransform { match &mut **init { Expr::Fn(fn_expr) => { + let has_step = self.has_step_directive(&fn_expr.function, false); + let has_workflow = + self.has_workflow_directive(&fn_expr.function, false); + // Check for step directive first - if self.has_step_directive(&fn_expr.function, false) { + if has_step { // Validate that it's async - emit error if not if !fn_expr.function.is_async { emit_error(WorkflowErrorKind::NonAsyncFunction { @@ -4568,7 +4624,7 @@ impl VisitMut for StepTransform { } } } - } else if self.has_workflow_directive(&fn_expr.function, false) { + } else if has_workflow { // Validate that it's async - emit error if not if !fn_expr.function.is_async { emit_error(WorkflowErrorKind::NonAsyncFunction { @@ -4628,11 +4684,24 @@ impl VisitMut for StepTransform { } } } + } else { + // Regular function expression (not step/workflow) - track parent context for nested steps + let old_parent = self.current_parent_function_name.clone(); + let old_in_module = self.in_module_level; + self.current_parent_function_name = Some(name.clone()); + self.in_module_level = false; + fn_expr.visit_mut_children_with(self); + self.current_parent_function_name = old_parent; + self.in_module_level = old_in_module; + // Continue to next declarator (don't return early) } } Expr::Arrow(arrow_expr) => { + let has_step = self.has_step_directive_arrow(arrow_expr, false); + let has_workflow = self.has_workflow_directive_arrow(arrow_expr, false); + // Check for step directive first - if self.has_step_directive_arrow(arrow_expr, false) { + if has_step { // Validate that it's async - emit error if not if !arrow_expr.is_async { emit_error(WorkflowErrorKind::NonAsyncFunction { @@ -4643,8 +4712,8 @@ impl VisitMut for StepTransform { // It's valid - proceed with transformation self.step_function_names.insert(name.clone()); - // Check if we're inside a workflow function - if self.in_workflow_function { + // Check if we're inside any function (nested), not just workflow functions + if !self.in_module_level { match self.mode { TransformMode::Step => { // Hoist arrow function to module scope @@ -4711,18 +4780,35 @@ impl VisitMut for StepTransform { arrow_expr.span, closure_vars, true, // Was an arrow function - self.current_workflow_function_name.clone().unwrap_or_default(), + self.current_parent_function_name + .clone() + .unwrap_or_default(), )); - // Mark the entire var declarator for removal by nulling out the init - *init = Box::new(Expr::Invalid(Invalid { - span: DUMMY_SP, - })); + // Replace with identifier reference to the hoisted function + let hoisted_name = if let Some(parent) = + &self.current_parent_function_name + { + if !parent.is_empty() { + format!("{}${}", parent, name) + } else { + name + } + } else { + name + }; + *init = Box::new(Expr::Ident(Ident::new( + hoisted_name.into(), + DUMMY_SP, + SyntaxContext::empty(), + ))); } TransformMode::Workflow => { // Replace with proxy reference (not a function call) // Include parent workflow name in step ID - let step_fn_name = if let Some(parent) = &self.current_workflow_function_name { + let step_fn_name = if let Some(parent) = + &self.current_workflow_function_name + { format!("{}/{}", parent, name) } else { name.clone() @@ -4741,14 +4827,14 @@ impl VisitMut for StepTransform { )); } TransformMode::Client => { - // In client mode, remove the nested step - *init = Box::new(Expr::Invalid(Invalid { - span: DUMMY_SP, - })); + // In client mode, just remove the directive and keep the function + self.remove_use_step_directive_arrow( + &mut arrow_expr.body, + ); } } } else { - // Not in a workflow function - handle normally + // At module level - handle normally match self.mode { TransformMode::Step => { self.remove_use_step_directive_arrow( @@ -4806,7 +4892,7 @@ impl VisitMut for StepTransform { } } } - } else if self.has_workflow_directive_arrow(arrow_expr, false) { + } else if has_workflow { // Validate that it's async - emit error if not if !arrow_expr.is_async { emit_error(WorkflowErrorKind::NonAsyncFunction { @@ -4869,6 +4955,16 @@ impl VisitMut for StepTransform { } } } + } else { + // Regular arrow function (not step/workflow) - track parent context for nested steps + let old_parent = self.current_parent_function_name.clone(); + let old_in_module = self.in_module_level; + self.current_parent_function_name = Some(name.clone()); + self.in_module_level = false; + arrow_expr.visit_mut_children_with(self); + self.current_parent_function_name = old_parent; + self.in_module_level = old_in_module; + // Continue to next declarator (don't return early) } } Expr::Object(obj_lit) => { @@ -4977,8 +5073,9 @@ impl VisitMut for StepTransform { assign.visit_mut_children_with(self); } - // Override visit_mut_expr to track closure variables + // Override visit_mut_expr to track closure variables and handle step functions fn visit_mut_expr(&mut self, expr: &mut Expr) { + // Track closure variables first if !self.in_module_level && self.should_track_names { if let Ok(name) = Name::try_from(&*expr) { if self.in_callee { @@ -4989,6 +5086,236 @@ impl VisitMut for StepTransform { } } + // Handle step functions that appear in expressions (e.g., return statements) + // but are not in var declarators (those are handled in visit_mut_var_decl) + match expr { + Expr::Fn(fn_expr) => { + if self.has_step_directive(&fn_expr.function, false) { + if !fn_expr.function.is_async { + emit_error(WorkflowErrorKind::NonAsyncFunction { + span: fn_expr.function.span, + directive: "use step", + }); + } else if !self.in_module_level { + // Nested step function in an expression (e.g., return statement) + let name = fn_expr + .ident + .as_ref() + .map(|i| i.sym.to_string()) + .unwrap_or_else(|| { + // Generate a name for anonymous functions + let name = format!("_anonymousStep{}", self.anonymous_fn_counter); + self.anonymous_fn_counter += 1; + name + }); + + if fn_expr.ident.is_some() { + // Only increment if we didn't use it above + // (the closure above already incremented) + } + + self.step_function_names.insert(name.clone()); + + match self.mode { + TransformMode::Step => { + // Hoist to module scope + let mut cloned_function = fn_expr.function.clone(); + self.remove_use_step_directive(&mut cloned_function.body); + + let closure_vars = ClosureVariableCollector::collect_from_function( + &cloned_function, + &self.module_imports, + ); + + let hoisted_fn_expr = FnExpr { + ident: Some(Ident::new( + name.clone().into(), + DUMMY_SP, + SyntaxContext::empty(), + )), + function: cloned_function, + }; + + self.nested_step_functions.push(( + name.clone(), + hoisted_fn_expr, + fn_expr.function.span, + closure_vars, + false, // Not an arrow function + self.current_parent_function_name + .clone() + .unwrap_or_default(), + )); + + // Replace with identifier reference + let hoisted_name = + if let Some(parent) = &self.current_parent_function_name { + if !parent.is_empty() { + format!("{}${}", parent, name) + } else { + name + } + } else { + name + }; + *expr = Expr::Ident(Ident::new( + hoisted_name.into(), + DUMMY_SP, + SyntaxContext::empty(), + )); + return; // Don't visit children since we replaced the expr + } + TransformMode::Workflow => { + // Replace with proxy reference + let step_fn_name = + if let Some(parent) = &self.current_workflow_function_name { + format!("{}/{}", parent, name) + } else { + name.clone() + }; + let step_id = self.create_id( + Some(&step_fn_name), + fn_expr.function.span, + false, + ); + + let closure_vars = ClosureVariableCollector::collect_from_function( + &fn_expr.function, + &self.module_imports, + ); + *expr = self.create_step_proxy_reference(&step_id, &closure_vars); + return; // Don't visit children since we replaced the expr + } + TransformMode::Client => { + // In client mode, just remove the directive and keep the function + self.remove_use_step_directive(&mut fn_expr.function.body); + } + } + } + } + } + Expr::Arrow(arrow_expr) => { + if self.has_step_directive_arrow(arrow_expr, false) { + if !arrow_expr.is_async { + emit_error(WorkflowErrorKind::NonAsyncFunction { + span: arrow_expr.span, + directive: "use step", + }); + } else if !self.in_module_level { + // Nested step arrow function in an expression (e.g., return statement) + let name = format!("_anonymousStep{}", self.anonymous_fn_counter); + self.anonymous_fn_counter += 1; + self.step_function_names.insert(name.clone()); + + match self.mode { + TransformMode::Step => { + // Hoist to module scope + let mut cloned_arrow = arrow_expr.clone(); + self.remove_use_step_directive_arrow(&mut cloned_arrow.body); + + let closure_vars = + ClosureVariableCollector::collect_from_arrow_expr( + &cloned_arrow, + &self.module_imports, + ); + + // Convert to function expression for hoisting + let fn_expr = FnExpr { + ident: Some(Ident::new( + name.clone().into(), + DUMMY_SP, + SyntaxContext::empty(), + )), + function: Box::new(Function { + params: cloned_arrow + .params + .iter() + .map(|pat| Param { + span: DUMMY_SP, + decorators: vec![], + pat: pat.clone(), + }) + .collect(), + decorators: vec![], + span: cloned_arrow.span, + ctxt: SyntaxContext::empty(), + body: match *cloned_arrow.body { + BlockStmtOrExpr::BlockStmt(block) => Some(block), + BlockStmtOrExpr::Expr(expr) => Some(BlockStmt { + span: DUMMY_SP, + ctxt: SyntaxContext::empty(), + stmts: vec![Stmt::Return(ReturnStmt { + span: DUMMY_SP, + arg: Some(expr), + })], + }), + }, + is_generator: false, + is_async: cloned_arrow.is_async, + type_params: cloned_arrow.type_params.clone(), + return_type: cloned_arrow.return_type.clone(), + }), + }; + + self.nested_step_functions.push(( + name.clone(), + fn_expr, + arrow_expr.span, + closure_vars, + true, // Was an arrow function + self.current_parent_function_name + .clone() + .unwrap_or_default(), + )); + + // Replace with identifier reference + let hoisted_name = + if let Some(parent) = &self.current_parent_function_name { + if !parent.is_empty() { + format!("{}${}", parent, name) + } else { + name + } + } else { + name + }; + *expr = Expr::Ident(Ident::new( + hoisted_name.into(), + DUMMY_SP, + SyntaxContext::empty(), + )); + return; // Don't visit children since we replaced the expr + } + TransformMode::Workflow => { + // Replace with proxy reference + let step_fn_name = + if let Some(parent) = &self.current_workflow_function_name { + format!("{}/{}", parent, name) + } else { + name.clone() + }; + let step_id = + self.create_id(Some(&step_fn_name), arrow_expr.span, false); + + let closure_vars = + ClosureVariableCollector::collect_from_arrow_expr( + arrow_expr, + &self.module_imports, + ); + *expr = self.create_step_proxy_reference(&step_id, &closure_vars); + return; // Don't visit children since we replaced the expr + } + TransformMode::Client => { + // In client mode, just remove the directive and keep the function + self.remove_use_step_directive_arrow(&mut arrow_expr.body); + } + } + } + } + } + _ => {} + } + expr.visit_mut_children_with(self); } @@ -5519,7 +5846,9 @@ impl VisitMut for StepTransform { arrow_expr.span, closure_vars, true, // Was an arrow function - self.current_workflow_function_name.clone().unwrap_or_default(), + self.current_workflow_function_name + .clone() + .unwrap_or_default(), )); // Replace with identifier reference @@ -5535,7 +5864,9 @@ impl VisitMut for StepTransform { &mut arrow_expr.body, ); // Include parent workflow name in step ID - let step_fn_name = if let Some(parent) = &self.current_workflow_function_name { + let step_fn_name = if let Some(parent) = + &self.current_workflow_function_name + { format!("{}/{}", parent, generated_name) } else { generated_name.clone() @@ -5607,7 +5938,9 @@ impl VisitMut for StepTransform { fn_expr.function.span, closure_vars, false, // Was a function expression - self.current_workflow_function_name.clone().unwrap_or_default(), + self.current_workflow_function_name + .clone() + .unwrap_or_default(), )); // Replace with identifier reference @@ -5623,7 +5956,9 @@ impl VisitMut for StepTransform { &mut fn_expr.function.body, ); // Include parent workflow name in step ID - let step_fn_name = if let Some(parent) = &self.current_workflow_function_name { + let step_fn_name = if let Some(parent) = + &self.current_workflow_function_name + { format!("{}/{}", parent, generated_name) } else { generated_name.clone() @@ -5702,7 +6037,9 @@ impl VisitMut for StepTransform { method_prop.function.span, closure_vars, false, // Was a method - self.current_workflow_function_name.clone().unwrap_or_default(), + self.current_workflow_function_name + .clone() + .unwrap_or_default(), )); // Replace method with property pointing to identifier @@ -5722,7 +6059,9 @@ impl VisitMut for StepTransform { &mut method_prop.function.body, ); // Include parent workflow name in step ID - let step_fn_name = if let Some(parent) = &self.current_workflow_function_name { + let step_fn_name = if let Some(parent) = + &self.current_workflow_function_name + { format!("{}/{}", parent, generated_name) } else { generated_name.clone() diff --git a/packages/swc-plugin-workflow/transform/tests/errors/non-async-functions/output-client.js b/packages/swc-plugin-workflow/transform/tests/errors/non-async-functions/output-client.js index a516a9646..a85b08d5e 100644 --- a/packages/swc-plugin-workflow/transform/tests/errors/non-async-functions/output-client.js +++ b/packages/swc-plugin-workflow/transform/tests/errors/non-async-functions/output-client.js @@ -9,13 +9,6 @@ export const syncWorkflow = ()=>{ 'use workflow'; return 'test'; }; -// Error: sync method with use step -const obj = { - syncMethod () { - 'use step'; - return true; - } -}; // These are ok export async function validStep() { return 42; diff --git a/packages/swc-plugin-workflow/transform/tests/errors/non-async-functions/output-workflow.js b/packages/swc-plugin-workflow/transform/tests/errors/non-async-functions/output-workflow.js index 78ce02efe..a8d4f0071 100644 --- a/packages/swc-plugin-workflow/transform/tests/errors/non-async-functions/output-workflow.js +++ b/packages/swc-plugin-workflow/transform/tests/errors/non-async-functions/output-workflow.js @@ -9,13 +9,6 @@ export const syncWorkflow = ()=>{ 'use workflow'; return 'test'; }; -// Error: sync method with use step -const obj = { - syncMethod () { - 'use step'; - return true; - } -}; // These are ok export var validStep = globalThis[Symbol.for("WORKFLOW_USE_STEP")]("step//input.js//validStep"); export const validWorkflow = async ()=>{ diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/default-parameter-usage/input.js b/packages/swc-plugin-workflow/transform/tests/fixture/default-parameter-usage/input.js new file mode 100644 index 000000000..b5e93fdae --- /dev/null +++ b/packages/swc-plugin-workflow/transform/tests/fixture/default-parameter-usage/input.js @@ -0,0 +1,32 @@ +// Test case for functions used in default parameter values +// The createDefaultDownloadFunction should NOT be removed by DCE + +const createDefaultDownloadFunction = (download = defaultDownload) => (requestedDownloads) => + Promise.all(requestedDownloads.map(async (r) => r.isUrlSupportedByModel ? null : download(r))); + +async function defaultDownload(request) { + return fetch(request.url); +} + +// This function uses createDefaultDownloadFunction in a default parameter value +// DCE must NOT remove createDefaultDownloadFunction +async function convertToLanguageModelPrompt({ + prompt, + supportedUrls, + download = createDefaultDownloadFunction() +}) { + return { prompt, supportedUrls, download }; +} + +export async function myWorkflow(input) { + 'use workflow'; + + const result = await convertToLanguageModelPrompt({ + prompt: input.prompt, + supportedUrls: {}, + download: undefined + }); + + return result; +} + diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/default-parameter-usage/output-client.js b/packages/swc-plugin-workflow/transform/tests/fixture/default-parameter-usage/output-client.js new file mode 100644 index 000000000..edf54c6d1 --- /dev/null +++ b/packages/swc-plugin-workflow/transform/tests/fixture/default-parameter-usage/output-client.js @@ -0,0 +1,7 @@ +// Test case for functions used in default parameter values +// The createDefaultDownloadFunction should NOT be removed by DCE +/**__internal_workflows{"workflows":{"input.js":{"myWorkflow":{"workflowId":"workflow//input.js//myWorkflow"}}}}*/; +export async function myWorkflow(input) { + throw new Error("You attempted to execute workflow myWorkflow function directly. To start a workflow, use start(myWorkflow) from workflow/api"); +} +myWorkflow.workflowId = "workflow//input.js//myWorkflow"; diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/default-parameter-usage/output-step.js b/packages/swc-plugin-workflow/transform/tests/fixture/default-parameter-usage/output-step.js new file mode 100644 index 000000000..609fd0bbd --- /dev/null +++ b/packages/swc-plugin-workflow/transform/tests/fixture/default-parameter-usage/output-step.js @@ -0,0 +1,25 @@ +// Test case for functions used in default parameter values +// The createDefaultDownloadFunction should NOT be removed by DCE +/**__internal_workflows{"workflows":{"input.js":{"myWorkflow":{"workflowId":"workflow//input.js//myWorkflow"}}}}*/; +const createDefaultDownloadFunction = (download = defaultDownload)=>(requestedDownloads)=>Promise.all(requestedDownloads.map(async (r)=>r.isUrlSupportedByModel ? null : download(r))); +async function defaultDownload(request) { + return fetch(request.url); +} +// This function uses createDefaultDownloadFunction in a default parameter value +// DCE must NOT remove createDefaultDownloadFunction +async function convertToLanguageModelPrompt({ prompt, supportedUrls, download = createDefaultDownloadFunction() }) { + return { + prompt, + supportedUrls, + download + }; +} +export async function myWorkflow(input) { + 'use workflow'; + const result = await convertToLanguageModelPrompt({ + prompt: input.prompt, + supportedUrls: {}, + download: undefined + }); + return result; +} diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/default-parameter-usage/output-workflow.js b/packages/swc-plugin-workflow/transform/tests/fixture/default-parameter-usage/output-workflow.js new file mode 100644 index 000000000..00898bd11 --- /dev/null +++ b/packages/swc-plugin-workflow/transform/tests/fixture/default-parameter-usage/output-workflow.js @@ -0,0 +1,25 @@ +// Test case for functions used in default parameter values +// The createDefaultDownloadFunction should NOT be removed by DCE +/**__internal_workflows{"workflows":{"input.js":{"myWorkflow":{"workflowId":"workflow//input.js//myWorkflow"}}}}*/; +const createDefaultDownloadFunction = (download = defaultDownload)=>(requestedDownloads)=>Promise.all(requestedDownloads.map(async (r)=>r.isUrlSupportedByModel ? null : download(r))); +async function defaultDownload(request) { + return fetch(request.url); +} +// This function uses createDefaultDownloadFunction in a default parameter value +// DCE must NOT remove createDefaultDownloadFunction +async function convertToLanguageModelPrompt({ prompt, supportedUrls, download = createDefaultDownloadFunction() }) { + return { + prompt, + supportedUrls, + download + }; +} +export async function myWorkflow(input) { + const result = await convertToLanguageModelPrompt({ + prompt: input.prompt, + supportedUrls: {}, + download: undefined + }); + return result; +} +myWorkflow.workflowId = "workflow//input.js//myWorkflow"; diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/module-level-step/output-client.js b/packages/swc-plugin-workflow/transform/tests/fixture/module-level-step/output-client.js index 96f29584f..ef3f001d8 100644 --- a/packages/swc-plugin-workflow/transform/tests/fixture/module-level-step/output-client.js +++ b/packages/swc-plugin-workflow/transform/tests/fixture/module-level-step/output-client.js @@ -1,7 +1,4 @@ /**__internal_workflows{"steps":{"input.js":{"step":{"stepId":"step//input.js//step"},"stepArrow":{"stepId":"step//input.js//stepArrow"}}}}*/; -const localArrow = async (input)=>{ - return input.bar; -}; export async function step(input) { return input.foo; } diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/module-level-step/output-workflow.js b/packages/swc-plugin-workflow/transform/tests/fixture/module-level-step/output-workflow.js index 2f80e80e1..1bcf1a2f7 100644 --- a/packages/swc-plugin-workflow/transform/tests/fixture/module-level-step/output-workflow.js +++ b/packages/swc-plugin-workflow/transform/tests/fixture/module-level-step/output-workflow.js @@ -1,7 +1,4 @@ /**__internal_workflows{"steps":{"input.js":{"step":{"stepId":"step//input.js//step"},"stepArrow":{"stepId":"step//input.js//stepArrow"}}}}*/; 'use step'; -const localArrow = async (input)=>{ - return input.bar; -}; export var step = globalThis[Symbol.for("WORKFLOW_USE_STEP")]("step//input.js//step"); export const stepArrow = globalThis[Symbol.for("WORKFLOW_USE_STEP")]("step//input.js//stepArrow"); diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/module-level-workflow/output-client.js b/packages/swc-plugin-workflow/transform/tests/fixture/module-level-workflow/output-client.js index f1d02d66f..2170d88bd 100644 --- a/packages/swc-plugin-workflow/transform/tests/fixture/module-level-workflow/output-client.js +++ b/packages/swc-plugin-workflow/transform/tests/fixture/module-level-workflow/output-client.js @@ -1,7 +1,4 @@ /**__internal_workflows{"workflows":{"input.js":{"arrowWorkflow":{"workflowId":"workflow//input.js//arrowWorkflow"},"workflow":{"workflowId":"workflow//input.js//workflow"}}}}*/; -const localArrow = async (input)=>{ - return input.bar; -}; export async function workflow(input) { return input.foo; } diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/module-level-workflow/output-workflow.js b/packages/swc-plugin-workflow/transform/tests/fixture/module-level-workflow/output-workflow.js index b74dafd3c..984d303fc 100644 --- a/packages/swc-plugin-workflow/transform/tests/fixture/module-level-workflow/output-workflow.js +++ b/packages/swc-plugin-workflow/transform/tests/fixture/module-level-workflow/output-workflow.js @@ -1,7 +1,4 @@ /**__internal_workflows{"workflows":{"input.js":{"arrowWorkflow":{"workflowId":"workflow//input.js//arrowWorkflow"},"workflow":{"workflowId":"workflow//input.js//workflow"}}}}*/; -const localArrow = async (input)=>{ - return input.bar; -}; export async function workflow(input) { return input.foo; } diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/multiple-const-step-functions/input.js b/packages/swc-plugin-workflow/transform/tests/fixture/multiple-const-step-functions/input.js index 7c949e74d..bd6d31e0d 100644 --- a/packages/swc-plugin-workflow/transform/tests/fixture/multiple-const-step-functions/input.js +++ b/packages/swc-plugin-workflow/transform/tests/fixture/multiple-const-step-functions/input.js @@ -14,3 +14,16 @@ export const fn3 = async () => { return 4; }; +// Test case: regular function BEFORE step function in same declaration +// This verifies that processing doesn't skip the step function +const regularArrow = () => 1, stepAfterRegular = async () => { + 'use step'; + return 5; +}; + +// Test case: regular function expression BEFORE step function +const regularFn = function() { return 2; }, stepAfterRegularFn = async function() { + 'use step'; + return 6; +}; + diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/multiple-const-step-functions/output-client.js b/packages/swc-plugin-workflow/transform/tests/fixture/multiple-const-step-functions/output-client.js index 50c51b420..e0e53d5f0 100644 --- a/packages/swc-plugin-workflow/transform/tests/fixture/multiple-const-step-functions/output-client.js +++ b/packages/swc-plugin-workflow/transform/tests/fixture/multiple-const-step-functions/output-client.js @@ -1,4 +1,4 @@ -/**__internal_workflows{"steps":{"input.js":{"fn1":{"stepId":"step//input.js//fn1"},"fn2":{"stepId":"step//input.js//fn2"},"fn3":{"stepId":"step//input.js//fn3"},"fn4":{"stepId":"step//input.js//fn4"}}}}*/; +/**__internal_workflows{"steps":{"input.js":{"fn1":{"stepId":"step//input.js//fn1"},"fn2":{"stepId":"step//input.js//fn2"},"fn3":{"stepId":"step//input.js//fn3"},"fn4":{"stepId":"step//input.js//fn4"},"stepAfterRegular":{"stepId":"step//input.js//stepAfterRegular"},"stepAfterRegularFn":{"stepId":"step//input.js//stepAfterRegularFn"}}}}*/; const fn1 = async ()=>{ return 1; }, fn2 = async ()=>{ @@ -9,3 +9,14 @@ export const fn3 = async ()=>{ }, fn4 = async ()=>{ return 4; }; +// Test case: regular function BEFORE step function in same declaration +// This verifies that processing doesn't skip the step function +const regularArrow = ()=>1, stepAfterRegular = async ()=>{ + return 5; +}; +// Test case: regular function expression BEFORE step function +const regularFn = function() { + return 2; +}, stepAfterRegularFn = async function() { + return 6; +}; diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/multiple-const-step-functions/output-step.js b/packages/swc-plugin-workflow/transform/tests/fixture/multiple-const-step-functions/output-step.js index 6773f6390..5c3f9147f 100644 --- a/packages/swc-plugin-workflow/transform/tests/fixture/multiple-const-step-functions/output-step.js +++ b/packages/swc-plugin-workflow/transform/tests/fixture/multiple-const-step-functions/output-step.js @@ -1,5 +1,5 @@ import { registerStepFunction } from "workflow/internal/private"; -/**__internal_workflows{"steps":{"input.js":{"fn1":{"stepId":"step//input.js//fn1"},"fn2":{"stepId":"step//input.js//fn2"},"fn3":{"stepId":"step//input.js//fn3"},"fn4":{"stepId":"step//input.js//fn4"}}}}*/; +/**__internal_workflows{"steps":{"input.js":{"fn1":{"stepId":"step//input.js//fn1"},"fn2":{"stepId":"step//input.js//fn2"},"fn3":{"stepId":"step//input.js//fn3"},"fn4":{"stepId":"step//input.js//fn4"},"stepAfterRegular":{"stepId":"step//input.js//stepAfterRegular"},"stepAfterRegularFn":{"stepId":"step//input.js//stepAfterRegularFn"}}}}*/; const fn1 = async ()=>{ return 1; }, fn2 = async ()=>{ @@ -10,7 +10,20 @@ export const fn3 = async ()=>{ }, fn4 = async ()=>{ return 4; }; +// Test case: regular function BEFORE step function in same declaration +// This verifies that processing doesn't skip the step function +const regularArrow = ()=>1, stepAfterRegular = async ()=>{ + return 5; +}; +// Test case: regular function expression BEFORE step function +const regularFn = function() { + return 2; +}, stepAfterRegularFn = async function() { + return 6; +}; registerStepFunction("step//input.js//fn1", fn1); registerStepFunction("step//input.js//fn2", fn2); registerStepFunction("step//input.js//fn3", fn3); registerStepFunction("step//input.js//fn4", fn4); +registerStepFunction("step//input.js//stepAfterRegular", stepAfterRegular); +registerStepFunction("step//input.js//stepAfterRegularFn", stepAfterRegularFn); diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/multiple-const-step-functions/output-workflow.js b/packages/swc-plugin-workflow/transform/tests/fixture/multiple-const-step-functions/output-workflow.js index 1e9030e34..ded9f96c4 100644 --- a/packages/swc-plugin-workflow/transform/tests/fixture/multiple-const-step-functions/output-workflow.js +++ b/packages/swc-plugin-workflow/transform/tests/fixture/multiple-const-step-functions/output-workflow.js @@ -1,3 +1,10 @@ -/**__internal_workflows{"steps":{"input.js":{"fn1":{"stepId":"step//input.js//fn1"},"fn2":{"stepId":"step//input.js//fn2"},"fn3":{"stepId":"step//input.js//fn3"},"fn4":{"stepId":"step//input.js//fn4"}}}}*/; +/**__internal_workflows{"steps":{"input.js":{"fn1":{"stepId":"step//input.js//fn1"},"fn2":{"stepId":"step//input.js//fn2"},"fn3":{"stepId":"step//input.js//fn3"},"fn4":{"stepId":"step//input.js//fn4"},"stepAfterRegular":{"stepId":"step//input.js//stepAfterRegular"},"stepAfterRegularFn":{"stepId":"step//input.js//stepAfterRegularFn"}}}}*/; const fn1 = globalThis[Symbol.for("WORKFLOW_USE_STEP")]("step//input.js//fn1"), fn2 = globalThis[Symbol.for("WORKFLOW_USE_STEP")]("step//input.js//fn2"); export const fn3 = globalThis[Symbol.for("WORKFLOW_USE_STEP")]("step//input.js//fn3"), fn4 = globalThis[Symbol.for("WORKFLOW_USE_STEP")]("step//input.js//fn4"); +// Test case: regular function BEFORE step function in same declaration +// This verifies that processing doesn't skip the step function +const regularArrow = ()=>1, stepAfterRegular = globalThis[Symbol.for("WORKFLOW_USE_STEP")]("step//input.js//stepAfterRegular"); +// Test case: regular function expression BEFORE step function +const regularFn = function() { + return 2; +}, stepAfterRegularFn = globalThis[Symbol.for("WORKFLOW_USE_STEP")]("step//input.js//stepAfterRegularFn"); diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/nested-step-in-workflow/output-step.js b/packages/swc-plugin-workflow/transform/tests/fixture/nested-step-in-workflow/output-step.js index 594a8196a..91207f3b3 100644 --- a/packages/swc-plugin-workflow/transform/tests/fixture/nested-step-in-workflow/output-step.js +++ b/packages/swc-plugin-workflow/transform/tests/fixture/nested-step-in-workflow/output-step.js @@ -11,6 +11,14 @@ var helpers$objectStep = async (x, y)=>{ return x + y + 10; }; export async function example(a, b) { + "use workflow"; + const step = example$step; + // Arrow function with const + const arrowStep = example$arrowStep; + // Arrow function with let + let letArrowStep = example$letArrowStep; + // Arrow function with var + var varArrowStep = example$varArrowStep; // Object with step method const helpers = { objectStep: helpers$objectStep diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/nested-step-with-closure/input.js b/packages/swc-plugin-workflow/transform/tests/fixture/nested-step-with-closure/input.js index d209a5188..dfbe476b0 100644 --- a/packages/swc-plugin-workflow/transform/tests/fixture/nested-step-with-closure/input.js +++ b/packages/swc-plugin-workflow/transform/tests/fixture/nested-step-with-closure/input.js @@ -1,6 +1,67 @@ import { DurableAgent } from '@workflow/ai/agent'; import { gateway } from 'ai'; +function stepWrapperReturnArrowFunctionVar(a, b, c) { + const fn = async () => { + 'use step'; + return a + b + c; + }; + return fn; +} + +function stepWrapperReturnNamedFunction(a, b, c) { + return async function f() { + 'use step'; + return a + b + c; + }; +} + +function stepWrapperReturnArrowFunction(a, b, c) { + return async () => { + 'use step'; + return a + b + c; + }; +} + +function stepWrapperReturnNamedFunctionVar(a, b, c) { + async function fn() { + 'use step'; + return a + b + c; + } + return fn; +} + +const arrowWrapperReturnArrowFunctionVar = (a, b, c) => { + const fn = async () => { + 'use step'; + return a + b + c; + }; + return fn; +} + +const arrowWrapperReturnNamedFunction = (a, b, c) => { + return async function f() { + 'use step'; + return a + b + c; + }; +} + +const arrowWrapperReturnArrowFunction = (a, b, c) => { + return async () => { + 'use step'; + return a + b + c; + }; +} + +const arrowWrapperReturnNamedFunctionVar = (a, b, c) => { + async function fn() { + 'use step'; + return a + b + c; + } + return fn; +} + + export async function wflow() { 'use workflow'; let count = 42; @@ -27,4 +88,13 @@ export async function wflow() { console.log('count', count); }, }); + + await stepWrapperReturnArrowFunctionVar(1, 2, 3)(); + await stepWrapperReturnNamedFunction(1, 2, 3)(); + await stepWrapperReturnArrowFunction(1, 2, 3)(); + await stepWrapperReturnNamedFunctionVar(1, 2, 3)(); + await arrowWrapperReturnArrowFunctionVar(1, 2, 3)(); + await arrowWrapperReturnNamedFunction(1, 2, 3)(); + await arrowWrapperReturnArrowFunction(1, 2, 3)(); + await arrowWrapperReturnNamedFunctionVar(1, 2, 3)(); } diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/nested-step-with-closure/output-client.js b/packages/swc-plugin-workflow/transform/tests/fixture/nested-step-with-closure/output-client.js index 043e0ec09..14e392ad6 100644 --- a/packages/swc-plugin-workflow/transform/tests/fixture/nested-step-with-closure/output-client.js +++ b/packages/swc-plugin-workflow/transform/tests/fixture/nested-step-with-closure/output-client.js @@ -1,4 +1,4 @@ -/**__internal_workflows{"workflows":{"input.js":{"wflow":{"workflowId":"workflow//input.js//wflow"}}}}*/; +/**__internal_workflows{"workflows":{"input.js":{"wflow":{"workflowId":"workflow//input.js//wflow"}}},"steps":{"input.js":{"_anonymousStep0":{"stepId":"step//input.js//_anonymousStep0"},"_anonymousStep1":{"stepId":"step//input.js//_anonymousStep1"},"f":{"stepId":"step//input.js//f"},"fn":{"stepId":"step//input.js//fn"}}}}*/; export async function wflow() { throw new Error("You attempted to execute workflow wflow function directly. To start a workflow, use start(wflow) from workflow/api"); } diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/nested-step-with-closure/output-step.js b/packages/swc-plugin-workflow/transform/tests/fixture/nested-step-with-closure/output-step.js index 531693db2..24155b2c0 100644 --- a/packages/swc-plugin-workflow/transform/tests/fixture/nested-step-with-closure/output-step.js +++ b/packages/swc-plugin-workflow/transform/tests/fixture/nested-step-with-closure/output-step.js @@ -1,33 +1,111 @@ import { __private_getClosureVars, registerStepFunction } from "workflow/internal/private"; import { DurableAgent } from '@workflow/ai/agent'; import { gateway } from 'ai'; -/**__internal_workflows{"workflows":{"input.js":{"wflow":{"workflowId":"workflow//input.js//wflow"}}},"steps":{"input.js":{"_anonymousStep0":{"stepId":"step//input.js//_anonymousStep0"},"_anonymousStep1":{"stepId":"step//input.js//_anonymousStep1"},"_anonymousStep2":{"stepId":"step//input.js//_anonymousStep2"},"namedStepWithClosureVars":{"stepId":"step//input.js//namedStepWithClosureVars"}}}}*/; +/**__internal_workflows{"workflows":{"input.js":{"wflow":{"workflowId":"workflow//input.js//wflow"}}},"steps":{"input.js":{"_anonymousStep0":{"stepId":"step//input.js//_anonymousStep0"},"_anonymousStep1":{"stepId":"step//input.js//_anonymousStep1"},"_anonymousStep2":{"stepId":"step//input.js//_anonymousStep2"},"_anonymousStep3":{"stepId":"step//input.js//_anonymousStep3"},"_anonymousStep4":{"stepId":"step//input.js//_anonymousStep4"},"f":{"stepId":"step//input.js//f"},"fn":{"stepId":"step//input.js//fn"},"namedStepWithClosureVars":{"stepId":"step//input.js//namedStepWithClosureVars"}}}}*/; +var stepWrapperReturnArrowFunctionVar$fn = async ()=>{ + const { a, b, c } = __private_getClosureVars(); + return a + b + c; +}; +async function stepWrapperReturnNamedFunction$f() { + const { a, b, c } = __private_getClosureVars(); + return a + b + c; +} +var stepWrapperReturnArrowFunction$_anonymousStep0 = async ()=>{ + const { a, b, c } = __private_getClosureVars(); + return a + b + c; +}; +async function stepWrapperReturnNamedFunctionVar$fn() { + const { a, b, c } = __private_getClosureVars(); + return a + b + c; +} +var arrowWrapperReturnArrowFunctionVar$fn = async ()=>{ + const { a, b, c } = __private_getClosureVars(); + return a + b + c; +}; +async function arrowWrapperReturnNamedFunction$f() { + const { a, b, c } = __private_getClosureVars(); + return a + b + c; +} +var arrowWrapperReturnArrowFunction$_anonymousStep1 = async ()=>{ + const { a, b, c } = __private_getClosureVars(); + return a + b + c; +}; +async function arrowWrapperReturnNamedFunctionVar$fn() { + const { a, b, c } = __private_getClosureVars(); + return a + b + c; +} async function wflow$namedStepWithClosureVars() { const { count } = __private_getClosureVars(); console.log('count', count); } -var wflow$_anonymousStep0 = async ()=>{ +var wflow$_anonymousStep2 = async ()=>{ const { count } = __private_getClosureVars(); console.log('count', count); return gateway('openai/gpt-5'); }; -async function wflow$_anonymousStep1() { +async function wflow$_anonymousStep3() { const { count } = __private_getClosureVars(); console.log('count', count); } -async function wflow$_anonymousStep2() { +async function wflow$_anonymousStep4() { const { count } = __private_getClosureVars(); console.log('count', count); } +function stepWrapperReturnArrowFunctionVar(a, b, c) { + const fn = stepWrapperReturnArrowFunctionVar$fn; + return fn; +} +function stepWrapperReturnNamedFunction(a, b, c) { + return stepWrapperReturnNamedFunction$f; +} +function stepWrapperReturnArrowFunction(a, b, c) { + return stepWrapperReturnArrowFunction$_anonymousStep0; +} +function stepWrapperReturnNamedFunctionVar(a, b, c) { + const fn = stepWrapperReturnNamedFunctionVar$fn; + return fn; +} +const arrowWrapperReturnArrowFunctionVar = (a, b, c)=>{ + const fn = arrowWrapperReturnArrowFunctionVar$fn; + return fn; +}; +const arrowWrapperReturnNamedFunction = (a, b, c)=>{ + return arrowWrapperReturnNamedFunction$f; +}; +const arrowWrapperReturnArrowFunction = (a, b, c)=>{ + return arrowWrapperReturnArrowFunction$_anonymousStep1; +}; +const arrowWrapperReturnNamedFunctionVar = (a, b, c)=>{ + const fn = arrowWrapperReturnNamedFunctionVar$fn; + return fn; +}; export async function wflow() { + 'use workflow'; let count = 42; + const namedStepWithClosureVars = wflow$namedStepWithClosureVars; const agent = new DurableAgent({ - arrowFunctionWithClosureVars: _anonymousStep0, - namedFunctionWithClosureVars: _anonymousStep1, - methodWithClosureVars: _anonymousStep2 + arrowFunctionWithClosureVars: _anonymousStep2, + namedFunctionWithClosureVars: _anonymousStep3, + methodWithClosureVars: _anonymousStep4 }); + await stepWrapperReturnArrowFunctionVar(1, 2, 3)(); + await stepWrapperReturnNamedFunction(1, 2, 3)(); + await stepWrapperReturnArrowFunction(1, 2, 3)(); + await stepWrapperReturnNamedFunctionVar(1, 2, 3)(); + await arrowWrapperReturnArrowFunctionVar(1, 2, 3)(); + await arrowWrapperReturnNamedFunction(1, 2, 3)(); + await arrowWrapperReturnArrowFunction(1, 2, 3)(); + await arrowWrapperReturnNamedFunctionVar(1, 2, 3)(); } +registerStepFunction("step//input.js//stepWrapperReturnArrowFunctionVar/fn", stepWrapperReturnArrowFunctionVar$fn); +registerStepFunction("step//input.js//stepWrapperReturnNamedFunction/f", stepWrapperReturnNamedFunction$f); +registerStepFunction("step//input.js//stepWrapperReturnArrowFunction/_anonymousStep0", stepWrapperReturnArrowFunction$_anonymousStep0); +registerStepFunction("step//input.js//stepWrapperReturnNamedFunctionVar/fn", stepWrapperReturnNamedFunctionVar$fn); +registerStepFunction("step//input.js//arrowWrapperReturnArrowFunctionVar/fn", arrowWrapperReturnArrowFunctionVar$fn); +registerStepFunction("step//input.js//arrowWrapperReturnNamedFunction/f", arrowWrapperReturnNamedFunction$f); +registerStepFunction("step//input.js//arrowWrapperReturnArrowFunction/_anonymousStep1", arrowWrapperReturnArrowFunction$_anonymousStep1); +registerStepFunction("step//input.js//arrowWrapperReturnNamedFunctionVar/fn", arrowWrapperReturnNamedFunctionVar$fn); registerStepFunction("step//input.js//wflow/namedStepWithClosureVars", wflow$namedStepWithClosureVars); -registerStepFunction("step//input.js//wflow/_anonymousStep0", wflow$_anonymousStep0); -registerStepFunction("step//input.js//wflow/_anonymousStep1", wflow$_anonymousStep1); registerStepFunction("step//input.js//wflow/_anonymousStep2", wflow$_anonymousStep2); +registerStepFunction("step//input.js//wflow/_anonymousStep3", wflow$_anonymousStep3); +registerStepFunction("step//input.js//wflow/_anonymousStep4", wflow$_anonymousStep4); diff --git a/packages/swc-plugin-workflow/transform/tests/fixture/nested-step-with-closure/output-workflow.js b/packages/swc-plugin-workflow/transform/tests/fixture/nested-step-with-closure/output-workflow.js index 6e768914f..ac871dab5 100644 --- a/packages/swc-plugin-workflow/transform/tests/fixture/nested-step-with-closure/output-workflow.js +++ b/packages/swc-plugin-workflow/transform/tests/fixture/nested-step-with-closure/output-workflow.js @@ -1,20 +1,88 @@ import { DurableAgent } from '@workflow/ai/agent'; -/**__internal_workflows{"workflows":{"input.js":{"wflow":{"workflowId":"workflow//input.js//wflow"}}},"steps":{"input.js":{"_anonymousStep0":{"stepId":"step//input.js//_anonymousStep0"},"_anonymousStep1":{"stepId":"step//input.js//_anonymousStep1"},"_anonymousStep2":{"stepId":"step//input.js//_anonymousStep2"},"namedStepWithClosureVars":{"stepId":"step//input.js//namedStepWithClosureVars"}}}}*/; +/**__internal_workflows{"workflows":{"input.js":{"wflow":{"workflowId":"workflow//input.js//wflow"}}},"steps":{"input.js":{"_anonymousStep0":{"stepId":"step//input.js//_anonymousStep0"},"_anonymousStep1":{"stepId":"step//input.js//_anonymousStep1"},"_anonymousStep2":{"stepId":"step//input.js//_anonymousStep2"},"_anonymousStep3":{"stepId":"step//input.js//_anonymousStep3"},"_anonymousStep4":{"stepId":"step//input.js//_anonymousStep4"},"f":{"stepId":"step//input.js//f"},"fn":{"stepId":"step//input.js//fn"},"namedStepWithClosureVars":{"stepId":"step//input.js//namedStepWithClosureVars"}}}}*/; +function stepWrapperReturnArrowFunctionVar(a, b, c) { + const fn = globalThis[Symbol.for("WORKFLOW_USE_STEP")]("step//input.js//fn", ()=>({ + a, + b, + c + })); + return fn; +} +function stepWrapperReturnNamedFunction(a, b, c) { + return globalThis[Symbol.for("WORKFLOW_USE_STEP")]("step//input.js//f", ()=>({ + a, + b, + c + })); +} +function stepWrapperReturnArrowFunction(a, b, c) { + return globalThis[Symbol.for("WORKFLOW_USE_STEP")]("step//input.js//_anonymousStep0", ()=>({ + a, + b, + c + })); +} +function stepWrapperReturnNamedFunctionVar(a, b, c) { + var fn = globalThis[Symbol.for("WORKFLOW_USE_STEP")]("step//input.js//fn", ()=>({ + a, + b, + c + })); + return fn; +} +const arrowWrapperReturnArrowFunctionVar = (a, b, c)=>{ + const fn = globalThis[Symbol.for("WORKFLOW_USE_STEP")]("step//input.js//fn", ()=>({ + a, + b, + c + })); + return fn; +}; +const arrowWrapperReturnNamedFunction = (a, b, c)=>{ + return globalThis[Symbol.for("WORKFLOW_USE_STEP")]("step//input.js//f", ()=>({ + a, + b, + c + })); +}; +const arrowWrapperReturnArrowFunction = (a, b, c)=>{ + return globalThis[Symbol.for("WORKFLOW_USE_STEP")]("step//input.js//_anonymousStep1", ()=>({ + a, + b, + c + })); +}; +const arrowWrapperReturnNamedFunctionVar = (a, b, c)=>{ + var fn = globalThis[Symbol.for("WORKFLOW_USE_STEP")]("step//input.js//fn", ()=>({ + a, + b, + c + })); + return fn; +}; export async function wflow() { let count = 42; var namedStepWithClosureVars = globalThis[Symbol.for("WORKFLOW_USE_STEP")]("step//input.js//wflow/namedStepWithClosureVars", ()=>({ count })); const agent = new DurableAgent({ - arrowFunctionWithClosureVars: globalThis[Symbol.for("WORKFLOW_USE_STEP")]("step//input.js//wflow/_anonymousStep0", ()=>({ + arrowFunctionWithClosureVars: globalThis[Symbol.for("WORKFLOW_USE_STEP")]("step//input.js//wflow/_anonymousStep2", ()=>({ count })), - namedFunctionWithClosureVars: globalThis[Symbol.for("WORKFLOW_USE_STEP")]("step//input.js//wflow/_anonymousStep1", ()=>({ + namedFunctionWithClosureVars: globalThis[Symbol.for("WORKFLOW_USE_STEP")]("step//input.js//wflow/_anonymousStep3", ()=>({ count })), - methodWithClosureVars: globalThis[Symbol.for("WORKFLOW_USE_STEP")]("step//input.js//wflow/_anonymousStep2", ()=>({ + methodWithClosureVars: globalThis[Symbol.for("WORKFLOW_USE_STEP")]("step//input.js//wflow/_anonymousStep4", ()=>({ count })) }); + await stepWrapperReturnArrowFunctionVar(1, 2, 3)(); + await stepWrapperReturnNamedFunction(1, 2, 3)(); + await stepWrapperReturnArrowFunction(1, 2, 3)(); + await stepWrapperReturnNamedFunctionVar(1, 2, 3)(); + await arrowWrapperReturnArrowFunctionVar(1, 2, 3)(); + await arrowWrapperReturnNamedFunction(1, 2, 3)(); + await arrowWrapperReturnArrowFunction(1, 2, 3)(); + await arrowWrapperReturnNamedFunctionVar(1, 2, 3)(); } wflow.workflowId = "workflow//input.js//wflow";