Skip to content

Commit 4f9ae4e

Browse files
authored
Remove step function transformation in client mode (#271)
1 parent 120ae06 commit 4f9ae4e

File tree

19 files changed

+185
-333
lines changed

19 files changed

+185
-333
lines changed

.changeset/floppy-symbols-check.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@workflow/swc-plugin": patch
3+
---
4+
5+
Remove step transformation in client mode

packages/core/e2e/e2e.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,4 +524,31 @@ describe('e2e', () => {
524524
expect(returnValue.retryableResult.duration).toBeGreaterThan(10_000);
525525
expect(returnValue.gotFatalError).toBe(true);
526526
});
527+
528+
test(
529+
'stepDirectCallWorkflow - calling step functions directly outside workflow context',
530+
{ timeout: 60_000 },
531+
async () => {
532+
// Call the API route that directly calls a step function (no workflow context)
533+
const url = new URL('/api/test-direct-step-call', deploymentUrl);
534+
const res = await fetch(url, {
535+
method: 'POST',
536+
headers: { 'Content-Type': 'application/json' },
537+
body: JSON.stringify({ x: 3, y: 5 }),
538+
});
539+
540+
if (!res.ok) {
541+
throw new Error(
542+
`Failed to call step function directly: ${res.url} ${
543+
res.status
544+
}: ${await res.text()}`
545+
);
546+
}
547+
548+
const { result } = await res.json();
549+
550+
// Expected: add(3, 5) = 8
551+
expect(result).toBe(8);
552+
}
553+
);
527554
});

packages/swc-plugin-workflow/transform/src/lib.rs

Lines changed: 8 additions & 204 deletions
Original file line numberDiff line numberDiff line change
@@ -680,55 +680,6 @@ impl StepTransform {
680680
}
681681
}
682682

683-
// Create a step run call for arrow functions (client mode)
684-
fn create_run_step_call_arrow(&self, fn_name: &str, params: &[Pat]) -> Expr {
685-
let args_array = Expr::Array(ArrayLit {
686-
span: DUMMY_SP,
687-
elems: params
688-
.iter()
689-
.map(|param| {
690-
// Check if this is a rest parameter
691-
let is_rest = matches!(param, Pat::Rest(_));
692-
Some(ExprOrSpread {
693-
spread: if is_rest { Some(DUMMY_SP) } else { None },
694-
expr: Box::new(self.pat_to_expr(param)),
695-
})
696-
})
697-
.collect(),
698-
});
699-
700-
Expr::Call(CallExpr {
701-
span: DUMMY_SP,
702-
ctxt: SyntaxContext::empty(),
703-
callee: Callee::Expr(Box::new(Expr::Ident(Ident::new(
704-
"__private_run_step".into(),
705-
DUMMY_SP,
706-
SyntaxContext::empty(),
707-
)))),
708-
args: vec![
709-
ExprOrSpread {
710-
spread: None,
711-
expr: Box::new(Expr::Lit(Lit::Str(Str {
712-
span: DUMMY_SP,
713-
value: fn_name.into(),
714-
raw: None,
715-
}))),
716-
},
717-
ExprOrSpread {
718-
spread: None,
719-
expr: Box::new(Expr::Object(ObjectLit {
720-
span: DUMMY_SP,
721-
props: vec![PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
722-
key: PropName::Ident(IdentName::new("arguments".into(), DUMMY_SP)),
723-
value: Box::new(args_array),
724-
})))],
725-
})),
726-
},
727-
],
728-
type_args: None,
729-
})
730-
}
731-
732683
// Generate the import for registerStepFunction (step mode)
733684
fn create_register_import(&self) -> ModuleItem {
734685
ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
@@ -754,41 +705,6 @@ impl StepTransform {
754705
}))
755706
}
756707

757-
// Generate the import for runStep function (client mode)
758-
fn create_run_step_import(&self) -> ModuleItem {
759-
let mut specifiers = Vec::new();
760-
761-
if !self.step_function_names.is_empty() {
762-
specifiers.push(ImportSpecifier::Named(ImportNamedSpecifier {
763-
span: DUMMY_SP,
764-
local: Ident::new(
765-
"__private_run_step".into(),
766-
DUMMY_SP,
767-
SyntaxContext::empty(),
768-
),
769-
imported: Some(ModuleExportName::Ident(Ident::new(
770-
"runStep".into(),
771-
DUMMY_SP,
772-
SyntaxContext::empty(),
773-
))),
774-
is_type_only: false,
775-
}));
776-
}
777-
778-
ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
779-
span: DUMMY_SP,
780-
specifiers,
781-
src: Box::new(Str {
782-
span: DUMMY_SP,
783-
value: "workflow/api".into(),
784-
raw: None,
785-
}),
786-
type_only: false,
787-
with: None,
788-
phase: ImportPhase::Evaluation,
789-
}))
790-
}
791-
792708
// Create a proxy call to globalThis[Symbol.for("WORKFLOW_USE_STEP")] (workflow mode)
793709
fn create_step_proxy(&self, step_id: &str) -> Expr {
794710
Expr::Call(CallExpr {
@@ -873,55 +789,6 @@ impl StepTransform {
873789
})
874790
}
875791

876-
// Create a step run call (client mode)
877-
fn create_run_step_call(&self, fn_name: &str, params: &[Param]) -> Expr {
878-
let args_array = Expr::Array(ArrayLit {
879-
span: DUMMY_SP,
880-
elems: params
881-
.iter()
882-
.map(|param| {
883-
// Check if this is a rest parameter
884-
let is_rest = matches!(param.pat, Pat::Rest(_));
885-
Some(ExprOrSpread {
886-
spread: if is_rest { Some(DUMMY_SP) } else { None },
887-
expr: Box::new(self.pat_to_expr(&param.pat)),
888-
})
889-
})
890-
.collect(),
891-
});
892-
893-
Expr::Call(CallExpr {
894-
span: DUMMY_SP,
895-
ctxt: SyntaxContext::empty(),
896-
callee: Callee::Expr(Box::new(Expr::Ident(Ident::new(
897-
"__private_run_step".into(),
898-
DUMMY_SP,
899-
SyntaxContext::empty(),
900-
)))),
901-
args: vec![
902-
ExprOrSpread {
903-
spread: None,
904-
expr: Box::new(Expr::Lit(Lit::Str(Str {
905-
span: DUMMY_SP,
906-
value: fn_name.into(),
907-
raw: None,
908-
}))),
909-
},
910-
ExprOrSpread {
911-
spread: None,
912-
expr: Box::new(Expr::Object(ObjectLit {
913-
span: DUMMY_SP,
914-
props: vec![PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
915-
key: PropName::Ident(IdentName::new("arguments".into(), DUMMY_SP)),
916-
value: Box::new(args_array),
917-
})))],
918-
})),
919-
},
920-
],
921-
type_args: None,
922-
})
923-
}
924-
925792
// Create a registration call for step mode
926793
fn create_registration_call(&mut self, name: &str, span: swc_core::common::Span) {
927794
// Only register each function once
@@ -1589,9 +1456,7 @@ impl VisitMut for StepTransform {
15891456
}
15901457
}
15911458
TransformMode::Client => {
1592-
if !self.step_function_names.is_empty() {
1593-
imports_to_add.push(self.create_run_step_import());
1594-
}
1459+
// No imports needed for client mode since step functions are not transformed
15951460
}
15961461
}
15971462

@@ -2274,17 +2139,8 @@ impl VisitMut for StepTransform {
22742139
}
22752140
}
22762141
TransformMode::Client => {
2277-
// Transform step function body to use step run call
2142+
// In client mode, just remove the directive and keep the function as-is
22782143
self.remove_use_step_directive(&mut fn_decl.function.body);
2279-
if let Some(body) = &mut fn_decl.function.body {
2280-
body.stmts = vec![Stmt::Return(ReturnStmt {
2281-
span: DUMMY_SP,
2282-
arg: Some(Box::new(self.create_run_step_call(
2283-
&fn_name,
2284-
&fn_decl.function.params,
2285-
))),
2286-
})];
2287-
}
22882144
stmt.visit_mut_children_with(self);
22892145
}
22902146
}
@@ -2414,17 +2270,8 @@ impl VisitMut for StepTransform {
24142270
}
24152271
}
24162272
TransformMode::Client => {
2417-
// Transform step function body to use run step call
2273+
// In client mode, just remove the directive and keep the function as-is
24182274
self.remove_use_step_directive(&mut fn_decl.function.body);
2419-
if let Some(body) = &mut fn_decl.function.body {
2420-
body.stmts = vec![Stmt::Return(ReturnStmt {
2421-
span: DUMMY_SP,
2422-
arg: Some(Box::new(self.create_run_step_call(
2423-
&fn_name,
2424-
&fn_decl.function.params,
2425-
))),
2426-
})];
2427-
}
24282275
export_decl.visit_mut_children_with(self);
24292276
}
24302277
}
@@ -2548,22 +2395,10 @@ impl VisitMut for StepTransform {
25482395
}
25492396
}
25502397
TransformMode::Client => {
2551-
// Transform step function body to use run step call
2398+
// In client mode, just remove the directive and keep the function as-is
25522399
self.remove_use_step_directive(
25532400
&mut fn_expr.function.body,
25542401
);
2555-
if let Some(body) = &mut fn_expr.function.body {
2556-
body.stmts =
2557-
vec![Stmt::Return(ReturnStmt {
2558-
span: DUMMY_SP,
2559-
arg: Some(Box::new(
2560-
self.create_run_step_call(
2561-
&name,
2562-
&fn_expr.function.params,
2563-
),
2564-
)),
2565-
})];
2566-
}
25672402
}
25682403
}
25692404
}
@@ -2669,17 +2504,10 @@ impl VisitMut for StepTransform {
26692504
);
26702505
}
26712506
TransformMode::Client => {
2672-
// Transform arrow function to use run step call
2507+
// In client mode, just remove the directive and keep the function as-is
26732508
self.remove_use_step_directive_arrow(
26742509
&mut arrow_expr.body,
26752510
);
2676-
arrow_expr.body =
2677-
Box::new(BlockStmtOrExpr::Expr(Box::new(
2678-
self.create_run_step_call_arrow(
2679-
&name,
2680-
&arrow_expr.params,
2681-
),
2682-
)));
26832511
}
26842512
}
26852513
}
@@ -2810,19 +2638,10 @@ impl VisitMut for StepTransform {
28102638
}
28112639
}
28122640
TransformMode::Client => {
2813-
// Transform step function body to use run step call
2641+
// In client mode, just remove the directive and keep the function as-is
28142642
self.remove_use_step_directive(
28152643
&mut fn_expr.function.body,
28162644
);
2817-
if let Some(body) = &mut fn_expr.function.body {
2818-
body.stmts = vec![Stmt::Return(ReturnStmt {
2819-
span: DUMMY_SP,
2820-
arg: Some(Box::new(self.create_run_step_call(
2821-
&name,
2822-
&fn_expr.function.params,
2823-
))),
2824-
})];
2825-
}
28262645
}
28272646
}
28282647
}
@@ -2940,16 +2759,10 @@ impl VisitMut for StepTransform {
29402759
));
29412760
}
29422761
TransformMode::Client => {
2943-
// Transform arrow function to use run step call
2762+
// In client mode, just remove the directive and keep the function as-is
29442763
self.remove_use_step_directive_arrow(
29452764
&mut arrow_expr.body,
29462765
);
2947-
arrow_expr.body = Box::new(BlockStmtOrExpr::Expr(
2948-
Box::new(self.create_run_step_call_arrow(
2949-
&name,
2950-
&arrow_expr.params,
2951-
)),
2952-
));
29532766
}
29542767
}
29552768
}
@@ -3392,17 +3205,8 @@ impl VisitMut for StepTransform {
33923205
}
33933206
}
33943207
TransformMode::Client => {
3395-
// Transform step function body to use step run call
3208+
// In client mode, just remove the directive and keep the function as-is
33963209
self.remove_use_step_directive(&mut fn_decl.function.body);
3397-
if let Some(body) = &mut fn_decl.function.body {
3398-
body.stmts = vec![Stmt::Return(ReturnStmt {
3399-
span: DUMMY_SP,
3400-
arg: Some(Box::new(self.create_run_step_call(
3401-
&fn_name,
3402-
&fn_decl.function.params,
3403-
))),
3404-
})];
3405-
}
34063210
}
34073211
}
34083212
}

packages/swc-plugin-workflow/transform/tests/errors/invalid-exports/output-client.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { runStep as __private_run_step } from "workflow/api";
21
/**__internal_workflows{"steps":{"input.js":{"validStep":{"stepId":"step//input.js//validStep"}}}}*/;
32
// These should all error - only async functions allowed
43
export const value = 42;
@@ -11,7 +10,5 @@ export class MyClass {
1110
export * from './other';
1211
// This is ok
1312
export async function validStep() {
14-
return __private_run_step("validStep", {
15-
arguments: []
16-
});
13+
return 'allowed';
1714
}

packages/swc-plugin-workflow/transform/tests/errors/misplaced-function-directive/output-client.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { runStep as __private_run_step } from "workflow/api";
21
/**__internal_workflows{"steps":{"input.js":{"badStep":{"stepId":"step//input.js//badStep"}}}}*/;
32
export async function badStep() {
4-
return __private_run_step("badStep", {
5-
arguments: []
6-
});
3+
const x = 42;
4+
// Error: directive must be at the top of function
5+
'use step';
6+
return x;
77
}
88
export const badWorkflow = async ()=>{
99
console.log('hello');

packages/swc-plugin-workflow/transform/tests/errors/non-async-functions/output-client.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
// Error: sync function with use step
2-
import { runStep as __private_run_step } from "workflow/api";
32
/**__internal_workflows{"workflows":{"input.js":{"validWorkflow":{"workflowId":"workflow//input.js//validWorkflow"}}},"steps":{"input.js":{"validStep":{"stepId":"step//input.js//validStep"}}}}*/;
43
export function syncStep() {
54
'use step';
@@ -19,9 +18,7 @@ const obj = {
1918
};
2019
// These are ok
2120
export async function validStep() {
22-
return __private_run_step("validStep", {
23-
arguments: []
24-
});
21+
return 42;
2522
}
2623
export const validWorkflow = async ()=>{
2724
throw new Error("You attempted to execute workflow validWorkflow function directly. To start a workflow, use start(validWorkflow) from workflow/api");

0 commit comments

Comments
 (0)