Skip to content

Commit dfdb280

Browse files
authored
Generate the webhook route in the static builder (#67)
1 parent c9b884e commit dfdb280

File tree

8 files changed

+61
-11
lines changed

8 files changed

+61
-11
lines changed

.changeset/light-rice-rush.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@workflow/cli": patch
3+
---
4+
5+
Generate the webhook route in the static builder mode

packages/cli/src/lib/builders/vercel-static.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export class VercelStaticBuilder extends BaseBuilder {
1414
};
1515
await this.buildStepsBundle(options);
1616
await this.buildWorkflowsBundle(options);
17+
await this.buildWebhookFunction();
1718

1819
await this.buildClientLibrary();
1920
}
@@ -77,4 +78,23 @@ export class VercelStaticBuilder extends BaseBuilder {
7778
tsPaths,
7879
});
7980
}
81+
82+
private async buildWebhookFunction(): Promise<void> {
83+
console.log(
84+
'Creating vercel API webhook bundle at',
85+
this.config.webhookBundlePath
86+
);
87+
88+
const webhookBundlePath = resolve(
89+
this.config.workingDir,
90+
this.config.webhookBundlePath
91+
);
92+
93+
// Ensure directory exists
94+
await mkdir(dirname(webhookBundlePath), { recursive: true });
95+
96+
await this.createWebhookBundle({
97+
outfile: webhookBundlePath,
98+
});
99+
}
80100
}

packages/cli/src/lib/builders/webhook-route.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ describe('Webhook route generation', () => {
2828
workingDir: testDir,
2929
stepsBundlePath: '',
3030
workflowsBundlePath: '',
31+
webhookBundlePath: '',
3132
watch: false,
3233
externalPackages: [],
3334
};
@@ -72,6 +73,7 @@ describe('Webhook route generation', () => {
7273
workingDir: testDir,
7374
stepsBundlePath: '',
7475
workflowsBundlePath: '',
76+
webhookBundlePath: '',
7577
watch: false,
7678
externalPackages: [],
7779
};
@@ -104,6 +106,7 @@ describe('Webhook route generation', () => {
104106
workingDir: testDir,
105107
stepsBundlePath: '',
106108
workflowsBundlePath: '',
109+
webhookBundlePath: '',
107110
watch: false,
108111
externalPackages: [],
109112
};
@@ -137,6 +140,7 @@ describe('Webhook route generation', () => {
137140
workingDir: testDir,
138141
stepsBundlePath: '',
139142
workflowsBundlePath: '',
143+
webhookBundlePath: '',
140144
watch: false,
141145
externalPackages: [],
142146
};
@@ -176,6 +180,7 @@ describe('Webhook route generation', () => {
176180
workingDir: testDir,
177181
stepsBundlePath: '',
178182
workflowsBundlePath: '',
183+
webhookBundlePath: '',
179184
watch: false,
180185
externalPackages: [],
181186
};
@@ -217,6 +222,7 @@ describe('Webhook route generation', () => {
217222
workingDir: testDir,
218223
stepsBundlePath: '',
219224
workflowsBundlePath: '',
225+
webhookBundlePath: '',
220226
watch: false,
221227
externalPackages: [],
222228
};
@@ -254,6 +260,7 @@ describe('Webhook route generation', () => {
254260
workingDir: testDir,
255261
stepsBundlePath: '',
256262
workflowsBundlePath: '',
263+
webhookBundlePath: '',
257264
watch: false,
258265
externalPackages: [],
259266
};

packages/cli/src/lib/config/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export interface WorkflowConfig {
2626
buildTarget: BuildTarget;
2727
stepsBundlePath: string;
2828
workflowsBundlePath: string;
29+
webhookBundlePath: string;
2930

3031
// Optionally generate a client library for workflow execution. The preferred
3132
// method of using workflow is to use a loader within a framework (like

packages/cli/src/lib/config/workflow-config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export const getWorkflowConfig = (
1717
buildTarget: buildTarget as BuildTarget,
1818
stepsBundlePath: './.well-known/workflow/v1/step.js',
1919
workflowsBundlePath: './.well-known/workflow/v1/flow.js',
20+
webhookBundlePath: './.well-known/workflow/v1/webhook.js',
2021
workflowManifestPath: workflowManifest,
2122

2223
// WIP: generate a client library to easily execute workflows/steps

packages/next/src/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,9 @@ export function withWorkflow({
9090
dirs: ['pages', 'app', 'src/pages', 'src/app'],
9191
workingDir: process.cwd(),
9292
buildTarget: 'next',
93-
workflowsBundlePath: '',
94-
stepsBundlePath: '',
93+
workflowsBundlePath: '', // not used in base
94+
stepsBundlePath: '', // not used in base
95+
webhookBundlePath: '', // node used in base
9596
externalPackages: [
9697
...require('next/dist/lib/server-external-packages.json'),
9798
...(nextConfig.serverExternalPackages || []),

packages/nitro/src/builders.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const CommonBuildOptions = {
99
buildTarget: 'next' as const, // unused in base
1010
stepsBundlePath: '', // unused in base
1111
workflowsBundlePath: '', // unused in base
12+
webhookBundlePath: '', // unused in base
1213
};
1314

1415
export class VercelBuilder extends VercelBuildOutputAPIBuilder {

packages/swc-plugin-workflow/spec.md

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ The swc plugin has 3 modes - 'step' mode, 'workflow' mode, and 'client' mode
66

77
## Step Mode
88

9-
When executed in 'step' mode, step definitions is kept as is and are simply registered using `registerStepFunction` from `@workflow/core/private`. The directives are removed. For example:
9+
When executed in 'step' mode, step definitions is kept as is and are simply registered using `registerStepFunction` from `workflow/internal/private`. The directives are removed. For example:
1010

1111
Input code:
1212

@@ -20,14 +20,18 @@ export async function add(a, b) {
2020
Output code
2121

2222
```
23-
import { registerStepFunction } from "@workflow/core/private"
23+
import { registerStepFunction } from "workflow/internal/private"
2424
2525
export async function add(a, b) {
2626
return a + b
2727
}
28-
registerStepFunction(add)
28+
registerStepFunction("step//input.js//add", add)
2929
```
3030

31+
**ID Generation:** Step IDs are generated using the format `step//{filepath}//{functionName}`, where:
32+
- `filepath` is the relative path to the file from the project root
33+
- `functionName` is the name of the step function
34+
3135
Workflow definitions are left untouched in step mode, including leaving the directives intact.
3236

3337
Upstream, a bundler will use this plugin in step mode to create a server bundle of multiple steps and serve it via an API endpoint at `.well-known/workflow/v1/step`
@@ -49,11 +53,13 @@ Output code
4953

5054
```
5155
export async function add(a, b) {
52-
return globalThis[Symbol.for("WORKFLOW_USE_STEP")]("add")(a, b);
56+
return globalThis[Symbol.for("WORKFLOW_USE_STEP")]("step//input.js//add")(a, b);
5357
}
5458
```
5559

56-
Workflow definitions are left untouched in workflow mode, aside from the directive itself being removed.
60+
**ID Generation:** The same step ID format `step//{filepath}//{functionName}` is used when replacing step function bodies.
61+
62+
Workflow definitions are left untouched in workflow mode, aside from the directive itself being removed and a `workflowId` property being attached.
5763

5864
Input code
5965

@@ -70,13 +76,16 @@ Output code
7076
export async function example(a, b) {
7177
return a + b;
7278
}
79+
example.workflowId = "workflow//input.js//example";
7380
```
7481

82+
**ID Generation:** Workflow IDs are generated using the format `workflow//{filepath}//{functionName}` and attached as a property to the function.
83+
7584
Upstream, a bundler will use this plugin in workflow mode to create a server bundle of all the workflows and serve it via an API endpoint at `.well-known/workflow/v1/flow`. The workflow endpoint handler encapsulates logic to execute the correct workflow using the function name, which is why nothing needs to be done to the workflows themselves. They just need to be exported.
7685

7786
## Client Mode
7887

79-
When executed in 'client' mode, step and workflow definitions have their bodies replaced with a call to `runStep` and `start` respectively. Both these helper functions are exported from the `@workflow/core` package. They effectively proxy the requests to execute steps and workflows on the server (using the bundles created in the other two modes).
88+
When executed in 'client' mode, step and workflow definitions have their bodies replaced with a call to `runStep` and throw statements respectively. `runStep` is exported from `workflow/api`. It effectively proxies the requests to execute steps on the server (using the bundles created in the other modes).
8089

8190
Input code
8291

@@ -97,18 +106,23 @@ Output code
97106

98107
```
99108
// workflow/main.js
100-
import { start as __private_workflow_start, runStep as __private_run_step } from "@workflow/core/runtime"
109+
import { runStep as __private_run_step } from "workflow/api"
101110
102111
export async function add(a, b) {
103112
return __private_run_step('add', { arguments: [a, b] })
104113
}
105114
106115
export async function workflow(a, b) {
107-
return __private_workflow_start('workflow', [a, b])
116+
throw new Error("You attempted to execute workflow workflow function directly. To start a workflow, use start(workflow) from workflow");
108117
}
118+
workflow.workflowId = "workflow//workflow/main.js//workflow";
109119
```
110120

111-
Upstream, this mode is typically used by a framework loader (like a NextJS/webpack loader) to JIT transform workflow executions into proxied start calls.
121+
**ID Generation:**
122+
- Step functions use `runStep` with the function name (not the full ID)
123+
- Workflow functions throw an error if called directly and have the `workflowId` property attached using the format `workflow//{filepath}//{functionName}`
124+
125+
Upstream, this mode is typically used by a framework loader (like a Next.js/webpack loader) to JIT transform workflow executions into proxied calls.
112126

113127
## Notes
114128

0 commit comments

Comments
 (0)