Add a background workflow that survives restarts. Generates src/lib/workflows/<name>.ts with a workflow defined through ow.defineWorkflow, a Zod schema for its input, a retry policy, and a first step to build on, plus a server test that runs it to completion. Start a run from any server code — a form action, an API route, or another workflow — with .run(input). Every step's result is saved, so an interrupted run resumes from the last completed step, and runs are listed under Workflows in the PocketBase dashboard. Pass --cron with a five-field schedule to make the workflow recurring, once per minute across all servers. Projects created before workflows shipped in the base template need vela enable workflows first.
import { z } from "zod";
import { ow } from "$lib/server/workflows";
/**
* Start a run from any server code with `sendWelcomeEmail.run(input)`. Runs
* are listed under Workflows in the PocketBase dashboard.
*/
export const sendWelcomeEmail = ow.defineWorkflow(
{
name: "send-welcome-email",
// What `run()` takes, checked before the run is queued.
schema: z.object({}),
retryPolicy: { maximumAttempts: 3 },
},
async ({ input, step }) => {
// Each step's return value is saved, so a retry or a restart carries on
// after the last step that finished. Keep every step safe to repeat.
const result = await step.run({ name: "first-step" }, async () => {
return input;
});
return result;
},
);
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { startWorker, stopWorker } from "$lib/server/workflows";
import { sendWelcomeEmail } from "./send-welcome-email";
describe("send-welcome-email", () => {
// The test process runs a worker of its own, so a run completes here
// without going through the dev server.
beforeAll(() => startWorker());
afterAll(() => stopWorker());
it("completes", async () => {
const handle = await sendWelcomeEmail.run({});
await expect(handle.result({ timeoutMs: 15_000 })).resolves.toEqual({});
});
});