chore: import upstream snapshot with attribution
Deploy (to testing) and Test Playground Preview Worker / Deploy Playground Preview Worker (testing) (push) Has been skipped
Deploy Workers Shared Staging / Deploy Workers Shared Staging (push) Failing after 0s
Prerelease / build (push) Has been skipped
Handle Changesets / Handle Changesets (push) Has been cancelled
Semgrep OSS scan / semgrep-oss (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:30:11 +08:00
commit 70bf21e064
5213 changed files with 731895 additions and 0 deletions
+16
View File
@@ -0,0 +1,16 @@
{
"name": "@fixture/workflow-multiple",
"private": true,
"scripts": {
"deploy": "wrangler deploy",
"start": "wrangler dev",
"test:ci": "vitest run"
},
"devDependencies": {
"@cloudflare/workers-types": "catalog:default",
"typescript": "catalog:default",
"undici": "catalog:default",
"vitest": "catalog:default",
"wrangler": "workspace:*"
}
}
+139
View File
@@ -0,0 +1,139 @@
import {
WorkerEntrypoint,
WorkflowEntrypoint,
WorkflowEvent,
WorkflowStep,
} from "cloudflare:workers";
type Params = {
name: string;
};
export class Demo extends WorkflowEntrypoint<{}, Params> {
async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
const { timestamp, payload } = event;
await step.sleep("Wait", "1 second");
const result = await step.do("First step", async function () {
return {
output: "First step result",
};
});
await step.sleep("Wait", "1 second");
const result2 = await step.do("Second step", async function () {
return {
output: "workflow1",
};
});
return "i'm workflow1";
}
}
export class Demo2 extends WorkflowEntrypoint<{}, Params> {
async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
const { timestamp, payload } = event;
await step.sleep("Wait", "1 second");
const result = await step.do("First step", async function () {
return {
output: "First step result",
};
});
await step.sleep("Wait", "1 second");
const result2 = await step.do("Second step", async function () {
return {
output: "workflow2",
};
});
return "i'm workflow2";
}
}
export class Demo3 extends WorkflowEntrypoint<{}, Params> {
async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
const result = await step.do("First step", async function () {
return {
output: "First step result",
};
});
await step.waitForEvent("wait for signal", {
type: "continue",
});
const result2 = await step.do("Second step", async function () {
return {
output: "workflow3",
};
});
return "i'm workflow3";
}
}
type Env = {
WORKFLOW: Workflow;
WORKFLOW2: Workflow;
WORKFLOW3: Workflow;
};
export default class extends WorkerEntrypoint<Env> {
async fetch(req: Request) {
const url = new URL(req.url);
const id = url.searchParams.get("id");
const workflowName = url.searchParams.get("workflowName");
if (url.pathname === "/favicon.ico") {
return new Response(null, { status: 404 });
}
let workflowToUse: Workflow;
if (workflowName === "3") {
workflowToUse = this.env.WORKFLOW3;
} else if (workflowName === "2") {
workflowToUse = this.env.WORKFLOW2;
} else {
workflowToUse = this.env.WORKFLOW;
}
let handle: WorkflowInstance;
if (url.pathname === "/create") {
if (id === null) {
handle = await workflowToUse.create();
} else {
handle = await workflowToUse.create({ id });
}
} else if (url.pathname === "/pause") {
handle = await workflowToUse.get(id);
await handle.pause();
} else if (url.pathname === "/resume") {
handle = await workflowToUse.get(id);
await handle.resume();
} else if (url.pathname === "/restart") {
handle = await workflowToUse.get(id);
await handle.restart();
} else if (url.pathname === "/terminate") {
handle = await workflowToUse.get(id);
await handle.terminate();
} else if (url.pathname === "/sendEvent") {
handle = await workflowToUse.get(id);
await handle.sendEvent({
type: "continue",
payload: await req.json(),
});
return Response.json({ ok: true });
} else {
handle = await workflowToUse.get(id);
}
return Response.json({ status: await handle.status(), id: handle.id });
}
}
@@ -0,0 +1,244 @@
import { randomUUID } from "crypto";
import { rm } from "fs/promises";
import { resolve } from "path";
import { fetch } from "undici";
import { afterAll, beforeAll, describe, it, test, vi } from "vitest";
import { runWranglerDev } from "../../shared/src/run-wrangler-long-lived";
describe("Workflows", () => {
let ip: string,
port: number,
stop: (() => Promise<unknown>) | undefined,
getOutput: () => string;
beforeAll(async () => {
// delete previous run contents because of persistence
await rm(resolve(__dirname, "..") + "/.wrangler", {
force: true,
recursive: true,
});
({ ip, port, stop, getOutput } = await runWranglerDev(
resolve(__dirname, ".."),
["--port=0", "--inspector-port=0"]
));
});
afterAll(async () => {
await stop?.();
});
async function fetchJson(url: string, body?: unknown, method?: string) {
const response = await fetch(url, {
headers: {
"MF-Disable-Pretty-Error": "1",
},
method: method ?? "GET",
body: body !== undefined ? JSON.stringify(body) : undefined,
});
const text = await response.text();
try {
return JSON.parse(text);
} catch (err) {
throw new Error(`Couldn't parse JSON:\n\n${text}`);
}
}
it("creates two instances with same id in two different workflows", async ({
expect,
}) => {
// Create both workflow instances
// Note: We don't assert the intermediate "running" status because the workflow
// may complete before we can observe it, causing flaky tests on fast CI machines
await Promise.all([
fetchJson(`http://${ip}:${port}/create?workflowName=1&id=test`),
fetchJson(`http://${ip}:${port}/create?workflowName=2&id=test`),
]);
// Wait for both workflows to complete with their final outputs
await Promise.all([
vi.waitFor(
async () => {
await expect(
fetchJson(`http://${ip}:${port}/status?workflowName=1&id=test`)
).resolves.toStrictEqual({
id: "test",
status: {
status: "complete",
__LOCAL_DEV_STEP_OUTPUTS: [
{ output: "First step result" },
{ output: "workflow1" },
],
output: "i'm workflow1",
},
});
},
{ timeout: 10000 }
),
vi.waitFor(
async () => {
await expect(
fetchJson(`http://${ip}:${port}/status?workflowName=2&id=test`)
).resolves.toStrictEqual({
id: "test",
status: {
status: "complete",
__LOCAL_DEV_STEP_OUTPUTS: [
{ output: "First step result" },
{ output: "workflow2" },
],
output: "i'm workflow2",
},
});
},
{ timeout: 10000 }
),
]);
});
describe("instance lifecycle methods (workflow3)", () => {
test("pause and resume a workflow", async ({ expect }) => {
const id = randomUUID();
await fetchJson(`http://${ip}:${port}/create?workflowName=3&id=${id}`);
await vi.waitFor(
async () => {
const result = (await fetchJson(
`http://${ip}:${port}/status?workflowName=3&id=${id}`
)) as {
status: {
__LOCAL_DEV_STEP_OUTPUTS: { output: string }[];
};
};
expect(result.status.__LOCAL_DEV_STEP_OUTPUTS).toContainEqual({
output: "First step result",
});
},
{ timeout: 5000 }
);
// Pause the instance
await fetchJson(`http://${ip}:${port}/pause?workflowName=3&id=${id}`);
await vi.waitFor(
async () => {
const result = (await fetchJson(
`http://${ip}:${port}/status?workflowName=3&id=${id}`
)) as { status: { status: string } };
expect(result.status.status).toBe("paused");
},
{ timeout: 5000 }
);
// Resume the instance
await fetchJson(`http://${ip}:${port}/resume?workflowName=3&id=${id}`);
await fetchJson(
`http://${ip}:${port}/sendEvent?workflowName=3&id=${id}`,
{ done: true },
"POST"
);
await vi.waitFor(
async () => {
const result = (await fetchJson(
`http://${ip}:${port}/status?workflowName=3&id=${id}`
)) as { status: { status: string; output: string } };
expect(result.status.status).toBe("complete");
expect(result.status.output).toBe("i'm workflow3");
},
{ timeout: 5000 }
);
});
test("terminate a running workflow", async ({ expect }) => {
const id = randomUUID();
await fetchJson(`http://${ip}:${port}/create?workflowName=3&id=${id}`);
await vi.waitFor(
async () => {
const result = (await fetchJson(
`http://${ip}:${port}/status?workflowName=3&id=${id}`
)) as {
status: {
__LOCAL_DEV_STEP_OUTPUTS: { output: string }[];
};
};
expect(result.status.__LOCAL_DEV_STEP_OUTPUTS).toContainEqual({
output: "First step result",
});
},
{ timeout: 5000 }
);
// Terminate
await fetchJson(`http://${ip}:${port}/terminate?workflowName=3&id=${id}`);
await vi.waitFor(
async () => {
const result = (await fetchJson(
`http://${ip}:${port}/status?workflowName=3&id=${id}`
)) as { status: { status: string } };
expect(result.status.status).toBe("terminated");
},
{ timeout: 5000 }
);
});
test("restart a running workflow", async ({ expect }) => {
const id = randomUUID();
await fetchJson(`http://${ip}:${port}/create?workflowName=3&id=${id}`);
await vi.waitFor(
async () => {
const result = (await fetchJson(
`http://${ip}:${port}/status?workflowName=3&id=${id}`
)) as {
status: {
__LOCAL_DEV_STEP_OUTPUTS: { output: string }[];
};
};
expect(result.status.__LOCAL_DEV_STEP_OUTPUTS).toContainEqual({
output: "First step result",
});
},
{ timeout: 5000 }
);
// Restart the instance
await fetchJson(`http://${ip}:${port}/restart?workflowName=3&id=${id}`);
// After restart, wait for it to be running again
await vi.waitFor(
async () => {
const result = (await fetchJson(
`http://${ip}:${port}/status?workflowName=3&id=${id}`
)) as { status: { status: string } };
expect(result.status.status).toBe("running");
},
{ timeout: 5000 }
);
// Send event to complete the restarted workflow
await fetchJson(
`http://${ip}:${port}/sendEvent?workflowName=3&id=${id}`,
{ done: true },
"POST"
);
await vi.waitFor(
async () => {
const result = (await fetchJson(
`http://${ip}:${port}/status?workflowName=3&id=${id}`
)) as { status: { status: string; output: string } };
expect(result.status.status).toBe("complete");
expect(result.status.output).toBe("i'm workflow3");
},
{ timeout: 5000 }
);
});
});
});
@@ -0,0 +1,7 @@
{
"extends": "@cloudflare/workers-tsconfig/tsconfig.json",
"compilerOptions": {
"types": ["node"]
},
"include": ["**/*.ts"]
}
+13
View File
@@ -0,0 +1,13 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "preserve",
"lib": ["ES2020"],
"types": ["@cloudflare/workers-types"],
"moduleResolution": "node",
"noEmit": true,
"skipLibCheck": true
},
"include": ["**/*.ts"],
"exclude": ["tests"]
}
@@ -0,0 +1,9 @@
import { defineProject, mergeConfig } from "vitest/config";
import configShared from "../../vitest.shared";
export default mergeConfig(
configShared,
defineProject({
test: {},
})
);
+22
View File
@@ -0,0 +1,22 @@
{
"name": "my-workflow-demo",
"main": "src/index.ts",
"compatibility_date": "2024-10-22",
"workflows": [
{
"binding": "WORKFLOW",
"name": "my-workflow",
"class_name": "Demo",
},
{
"binding": "WORKFLOW2",
"name": "my-workflow-2",
"class_name": "Demo2",
},
{
"binding": "WORKFLOW3",
"name": "my-workflow-3",
"class_name": "Demo3",
},
],
}