Skip to content

Commit 8096b5f

Browse files
create cloudflare add workflows-starter to templates (#9104)
* add workflows-starter to create cloudflare templates * add missing workflows-starter to template.ts * fix c3.ts config * fix typo on template * add missing case * make Workflow template id consistent * remove docs-tag from wrangler.jsonc * update workflows-starter template description * add changeset * add e2e tests * add dev script to workflows template * add correct argv to workflows-starter test * Update packages/create-cloudflare/templates/workflows-starter/ts/package.json remove start script Co-authored-by: James Opstad <[email protected]> * Update packages/create-cloudflare/templates/workflows-starter/ts/package.json make package.json name to TBD Co-authored-by: James Opstad <[email protected]> * Update packages/create-cloudflare/templates/workflows-starter/ts/package.json change version to 0.0.0 Co-authored-by: James Opstad <[email protected]> * Update packages/create-cloudflare/templates/workflows-starter/ts/package.json remove worker types package Co-authored-by: James Opstad <[email protected]> * Update packages/create-cloudflare/templates/workflows-starter/ts/src/index.ts remove env type Co-authored-by: James Opstad <[email protected]> * Update packages/create-cloudflare/templates/workflows-starter/ts/package.json Co-authored-by: James Opstad <[email protected]> * Update packages/create-cloudflare/templates/workflows-starter/ts/src/examples.ts Co-authored-by: James Opstad <[email protected]> * Make changest more specific * change workflows-starter to hello-world-workflows, correct small typo like mistakes * update e2e tests to use hello-world-workflows * remove trailing comma * remove examples.ts * add workflows-starter to create cloudflare templates * add missing workflows-starter to template.ts * fix c3.ts config * fix typo on template * add missing case * make Workflow template id consistent * remove docs-tag from wrangler.jsonc * update workflows-starter template description * add changeset * add e2e tests * add dev script to workflows template * add correct argv to workflows-starter test * Update packages/create-cloudflare/templates/workflows-starter/ts/package.json remove start script Co-authored-by: James Opstad <[email protected]> * Update packages/create-cloudflare/templates/workflows-starter/ts/package.json make package.json name to TBD Co-authored-by: James Opstad <[email protected]> * Update packages/create-cloudflare/templates/workflows-starter/ts/package.json change version to 0.0.0 Co-authored-by: James Opstad <[email protected]> * Update packages/create-cloudflare/templates/workflows-starter/ts/package.json remove worker types package Co-authored-by: James Opstad <[email protected]> * Update packages/create-cloudflare/templates/workflows-starter/ts/src/index.ts remove env type Co-authored-by: James Opstad <[email protected]> * Update packages/create-cloudflare/templates/workflows-starter/ts/package.json Co-authored-by: James Opstad <[email protected]> * Update packages/create-cloudflare/templates/workflows-starter/ts/src/examples.ts Co-authored-by: James Opstad <[email protected]> * Make changest more specific * change workflows-starter to hello-world-workflows, correct small typo like mistakes * update e2e tests to use hello-world-workflows * remove trailing comma * remove examples.ts * remove docs tags * add javascript variant * fix wrangler.jsonc * remove ts types from js template * Update packages/create-cloudflare/e2e-tests/cli.test.ts Co-authored-by: James Opstad <[email protected]> * Update .changeset/dirty-grapes-mate.md Co-authored-by: James Opstad <[email protected]> * Update packages/create-cloudflare/templates/hello-world-workflows/c3.ts Co-authored-by: James Opstad <[email protected]> * Update packages/create-cloudflare/templates/hello-world-workflows/js/package.json Co-authored-by: James Opstad <[email protected]> * Update packages/create-cloudflare/templates/hello-world-workflows/js/src/index.js Co-authored-by: James Opstad <[email protected]> * Update packages/create-cloudflare/templates/hello-world-workflows/ts/src/index.ts Co-authored-by: James Opstad <[email protected]> * Update packages/create-cloudflare/templates/hello-world-workflows/js/src/index.js Co-authored-by: James Opstad <[email protected]> * Update packages/create-cloudflare/templates/hello-world-workflows/js/src/index.js Co-authored-by: James Opstad <[email protected]> * Update packages/create-cloudflare/templates/hello-world-workflows/js/src/index.js Co-authored-by: James Opstad <[email protected]> * Update packages/create-cloudflare/templates/hello-world-workflows/ts/src/index.ts Co-authored-by: James Opstad <[email protected]> * Update packages/create-cloudflare/templates/hello-world-workflows/js/src/index.js Co-authored-by: James Opstad <[email protected]> * Update packages/create-cloudflare/templates/hello-world-workflows/js/src/index.js Co-authored-by: James Opstad <[email protected]> * Update packages/create-cloudflare/templates/hello-world-workflows/ts/src/index.ts Co-authored-by: James Opstad <[email protected]> * remove trailing comma --------- Co-authored-by: James Opstad <[email protected]>
1 parent 00ebdd9 commit 8096b5f

File tree

12 files changed

+387
-0
lines changed

12 files changed

+387
-0
lines changed

.changeset/dirty-grapes-mate.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"create-cloudflare": minor
3+
---
4+
5+
Add Hello World Workflows template.
6+
7+
For multi-step applications that automatically retry, persist state, and run for minutes, hours, days or weeks.

packages/create-cloudflare/e2e-tests/cli.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,8 @@ describe.skipIf(frameworkToTest || isQuarantineMode())("help text", () => {
565565
For multiplayer apps using WebSockets, or when you need synchronization
566566
hello-world-durable-object-with-assets
567567
For full-stack applications requiring static assets, an API, and real-time coordination
568+
hello-world-workflows
569+
For multi-step applications that automatically retry, persist state, and run for minutes, hours, days or weeks
568570
common
569571
Create a Worker to route and forward requests to other services
570572
scheduled

packages/create-cloudflare/e2e-tests/workers.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,19 @@ function getWorkerTests(opts: { experimental: boolean }): WorkerTestConfig[] {
139139
verifyPreview: null,
140140
argv: ["--category", "hello-world"],
141141
},
142+
{
143+
template: "hello-world-workflows",
144+
argv: ["--category", "hello-world"],
145+
variants: ["ts", "js"],
146+
verifyDeploy: {
147+
route: "/",
148+
expectedText: "details",
149+
},
150+
verifyPreview: {
151+
route: "/",
152+
expectedText: "details",
153+
},
154+
},
142155
{
143156
template: "common",
144157
variants: ["ts", "js"],

packages/create-cloudflare/src/templates.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import assetsOnlyTemplate from "templates/hello-world-assets-only/c3";
2929
import helloWorldWithDurableObjectAssetsTemplate from "templates/hello-world-durable-object-with-assets/c3";
3030
import helloWorldDurableObjectTemplate from "templates/hello-world-durable-object/c3";
3131
import helloWorldWithAssetsTemplate from "templates/hello-world-with-assets/c3";
32+
import workflowsTemplate from "templates/hello-world-workflows/c3";
3233
import helloWorldWorkerTemplate from "templates/hello-world/c3";
3334
import honoTemplate from "templates/hono/c3";
3435
import nextTemplate from "templates/next/c3";
@@ -239,6 +240,7 @@ export function getHelloWorldTemplateMap({
239240
"hello-world-durable-object": helloWorldDurableObjectTemplate,
240241
"hello-world-durable-object-with-assets":
241242
helloWorldWithDurableObjectAssetsTemplate,
243+
"hello-world-workflows": workflowsTemplate,
242244
common: commonTemplate,
243245
scheduled: scheduledTemplate,
244246
queues: queuesTemplate,
@@ -270,6 +272,7 @@ export const deriveCorrelatedArgs = (args: Partial<C3Args>) => {
270272
switch (args.type) {
271273
case "hello-world":
272274
case "hello-world-durable-object":
275+
case "hello-world-workflows":
273276
args.category ??= "hello-world";
274277
break;
275278
case "hello-world-python":
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import type { TemplateConfig } from "../../src/templates";
2+
3+
export default {
4+
configVersion: 1,
5+
id: "hello-world-workflows",
6+
displayName: "Workflow",
7+
description:
8+
"For multi-step applications that automatically retry, persist state, and run for minutes, hours, days or weeks",
9+
platform: "workers",
10+
copyFiles: {
11+
variants: {
12+
js: {
13+
path: "./js",
14+
},
15+
ts: {
16+
path: "./ts",
17+
},
18+
},
19+
},
20+
} satisfies TemplateConfig;
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"name": "<TBD>",
3+
"version": "0.0.0",
4+
"private": true,
5+
"scripts": {
6+
"deploy": "wrangler deploy",
7+
"start": "wrangler dev",
8+
"dev": "wrangler dev"
9+
},
10+
"devDependencies": {
11+
"wrangler": "^4.6.0"
12+
}
13+
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import { WorkflowEntrypoint } from "cloudflare:workers";
2+
3+
/**
4+
* Welcome to Cloudflare Workers! This is your first Workflows application.
5+
*
6+
* - Run `npm run dev` in your terminal to start a development server
7+
* - Open a browser tab at http://localhost:8787/ to see your Workflow in action
8+
* - Run `npm run deploy` to publish your application
9+
*
10+
* Learn more at https://developers.cloudflare.com/workflows
11+
*/
12+
13+
/**
14+
* @typedef {Object} Env
15+
* @property {Workflow} MY_WORKFLOW
16+
*/
17+
18+
/**
19+
* @typedef {Object} Params
20+
* @property {string} email
21+
* @property {Record<string, string>} metadata
22+
*/
23+
24+
export class MyWorkflow extends WorkflowEntrypoint {
25+
/**
26+
* @param {Env} env
27+
*/
28+
constructor(env) {
29+
this.env = env;
30+
}
31+
32+
/**
33+
* @param {WorkflowEvent<Params>} event
34+
* @param {WorkflowStep} step
35+
*/
36+
async run(event, step) {
37+
// Can access bindings on `this.env`
38+
// Can access params on `event.payload`
39+
40+
const files = await step.do("my first step", async () => {
41+
// Fetch a list of files from $SOME_SERVICE
42+
return {
43+
inputParams: event,
44+
files: [
45+
"doc_7392_rev3.pdf",
46+
"report_x29_final.pdf",
47+
"memo_2024_05_12.pdf",
48+
"file_089_update.pdf",
49+
"proj_alpha_v2.pdf",
50+
"data_analysis_q2.pdf",
51+
"notes_meeting_52.pdf",
52+
"summary_fy24_draft.pdf",
53+
],
54+
};
55+
});
56+
57+
// You can optionally have a Workflow wait for additional data,
58+
// human approval or an external webhook or HTTP request before progressing.
59+
// You can submit data via HTTP POST to /accounts/{account_id}/workflows/{workflow_name}/instances/{instance_id}/events/{eventName}
60+
const waitForApproval = await step.waitForEvent("request-approval", {
61+
type: "approval", // define an optional key to switch on
62+
timeout: "1 minute", // keep it short for the example!
63+
});
64+
65+
const apiResponse = await step.do("some other step", async () => {
66+
let resp = await fetch("https://api.cloudflare.com/client/v4/ips");
67+
return await resp.json();
68+
});
69+
70+
await step.sleep("wait on something", "1 minute");
71+
72+
await step.do(
73+
"make a call to write that could maybe, just might, fail",
74+
// Define a retry strategy
75+
/** @type {Object} */ ({
76+
retries: {
77+
limit: 5,
78+
delay: "5 second",
79+
backoff: "exponential",
80+
},
81+
timeout: "15 minutes",
82+
}),
83+
async () => {
84+
// Do stuff here, with access to the state from our previous steps
85+
if (Math.random() > 0.5) {
86+
throw new Error("API call to $STORAGE_SYSTEM failed");
87+
}
88+
},
89+
);
90+
}
91+
}
92+
93+
export default {
94+
/**
95+
* @param {Request} req
96+
* @param {Env} env
97+
* @returns {Promise<Response>}
98+
*/
99+
async fetch(req, env) {
100+
let url = new URL(req.url);
101+
102+
if (url.pathname.startsWith("/favicon")) {
103+
return Response.json({}, { status: 404 });
104+
}
105+
106+
// Get the status of an existing instance, if provided
107+
// GET /?instanceId=<id here>
108+
let id = url.searchParams.get("instanceId");
109+
if (id) {
110+
let instance = await env.MY_WORKFLOW.get(id);
111+
return Response.json({
112+
status: await instance.status(),
113+
});
114+
}
115+
116+
// Spawn a new instance and return the ID and status
117+
let instance = await env.MY_WORKFLOW.create();
118+
// You can also set the ID to match an ID in your own system
119+
// and pass an optional payload to the Workflow
120+
// let instance = await env.MY_WORKFLOW.create({
121+
// id: 'id-from-your-system',
122+
// params: { payload: 'to send' },
123+
// });
124+
return Response.json({
125+
id: instance.id,
126+
details: await instance.status(),
127+
});
128+
},
129+
};
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"name": "<TBD>",
3+
"main": "src/index.js",
4+
"compatibility_date": "<TBD>",
5+
6+
"observability": {
7+
"enabled": true,
8+
"head_sampling_rate": 1,
9+
},
10+
"workflows": [
11+
{
12+
"name": "<TBD>",
13+
"binding": "MY_WORKFLOW",
14+
"class_name": "MyWorkflow",
15+
},
16+
],
17+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "<TBD>",
3+
"version": "0.0.0",
4+
"private": true,
5+
"scripts": {
6+
"deploy": "wrangler deploy",
7+
"start": "wrangler dev",
8+
"dev": "wrangler dev",
9+
"cf-typegen": "wrangler types"
10+
},
11+
"devDependencies": {
12+
"typescript": "^5.0.4",
13+
"wrangler": "^4.6.0"
14+
}
15+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import {
2+
WorkflowEntrypoint,
3+
WorkflowEvent,
4+
WorkflowStep,
5+
} from "cloudflare:workers";
6+
7+
/**
8+
* Welcome to Cloudflare Workers! This is your first Workflows application.
9+
*
10+
* - Run `npm run dev` in your terminal to start a development server
11+
* - Open a browser tab at http://localhost:8787/ to see your Workflow in action
12+
* - Run `npm run deploy` to publish your application
13+
*
14+
* Learn more at https://developers.cloudflare.com/workflows
15+
*/
16+
17+
// User-defined params passed to your Workflow
18+
type Params = {
19+
email: string;
20+
metadata: Record<string, string>;
21+
};
22+
23+
export class MyWorkflow extends WorkflowEntrypoint<Env, Params> {
24+
async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
25+
// Can access bindings on `this.env`
26+
// Can access params on `event.payload`
27+
28+
const files = await step.do("my first step", async () => {
29+
// Fetch a list of files from $SOME_SERVICE
30+
return {
31+
inputParams: event,
32+
files: [
33+
"doc_7392_rev3.pdf",
34+
"report_x29_final.pdf",
35+
"memo_2024_05_12.pdf",
36+
"file_089_update.pdf",
37+
"proj_alpha_v2.pdf",
38+
"data_analysis_q2.pdf",
39+
"notes_meeting_52.pdf",
40+
"summary_fy24_draft.pdf",
41+
],
42+
};
43+
});
44+
45+
// You can optionally have a Workflow wait for additional data,
46+
// human approval or an external webhook or HTTP request, before progressing.
47+
// You can submit data via HTTP POST to /accounts/{account_id}/workflows/{workflow_name}/instances/{instance_id}/events/{eventName}
48+
const waitForApproval = await step.waitForEvent("request-approval", {
49+
type: "approval", // define an optional key to switch on
50+
timeout: "1 minute", // keep it short for the example!
51+
});
52+
53+
const apiResponse = await step.do("some other step", async () => {
54+
let resp = await fetch("https://api.cloudflare.com/client/v4/ips");
55+
return await resp.json<any>();
56+
});
57+
58+
await step.sleep("wait on something", "1 minute");
59+
60+
await step.do(
61+
"make a call to write that could maybe, just might, fail",
62+
// Define a retry strategy
63+
{
64+
retries: {
65+
limit: 5,
66+
delay: "5 second",
67+
backoff: "exponential",
68+
},
69+
timeout: "15 minutes",
70+
},
71+
async () => {
72+
// Do stuff here, with access to the state from our previous steps
73+
if (Math.random() > 0.5) {
74+
throw new Error("API call to $STORAGE_SYSTEM failed");
75+
}
76+
},
77+
);
78+
}
79+
}
80+
export default {
81+
async fetch(req: Request, env: Env): Promise<Response> {
82+
let url = new URL(req.url);
83+
84+
if (url.pathname.startsWith("/favicon")) {
85+
return Response.json({}, { status: 404 });
86+
}
87+
88+
// Get the status of an existing instance, if provided
89+
// GET /?instanceId=<id here>
90+
let id = url.searchParams.get("instanceId");
91+
if (id) {
92+
let instance = await env.MY_WORKFLOW.get(id);
93+
return Response.json({
94+
status: await instance.status(),
95+
});
96+
}
97+
98+
// Spawn a new instance and return the ID and status
99+
let instance = await env.MY_WORKFLOW.create();
100+
// You can also set the ID to match an ID in your own system
101+
// and pass an optional payload to the Workflow
102+
// let instance = await env.MY_WORKFLOW.create({
103+
// id: 'id-from-your-system',
104+
// params: { payload: 'to send' },
105+
// });
106+
return Response.json({
107+
id: instance.id,
108+
details: await instance.status(),
109+
});
110+
},
111+
};

0 commit comments

Comments
 (0)