All skills
upstash avatar

/upstash-workflow-js

@36daab8
by upstashupstash/skills27 stars
7

Work with the @upstash/workflow TypeScript/JavaScript SDK for durable, long-running workflows in serverless functions, multi-step processes that survive timeouts, retries, and restarts (built on QStash). Use when defining a workflow endpoint with serve(), running steps with context.run, sleeping for minutes to days without holding a function open, calling external APIs with context.call, waiting for an external event or webhook, invoking other workflows, configuring retries, failure callbacks, and a DLQ, controlling concurrency, rate, and parallelism, triggering, cancelling, or inspecting runs with the Workflow client, building AI agents and orchestrators, human-in-the-loop approvals, realtime updates, local development with the QStash dev server, adding middleware, or migrating workflows safely. Also use when the user asks for durable execution, step functions, saga or orchestration patterns, background jobs with checkpoints, or long-running tasks on Vercel, Next.js, Cloudflare Workers, or other serverless platforms.

Use this Skill: https://skilld.dev/gh/upstash/skills/upstash-workflow-js

This session only. Nothing lands on disk.

how-torealtime.md

≈1.2k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Realtime Workflow Integration (TypeScript)

This Skill provides guidance for building real‑time and human‑in‑the‑loop workflows using Upstash Workflow + Upstash Realtime. It covers event schemas, workflow patterns, emitting/receiving updates, and handling interactive pauses.

Key capabilities:

  • Emit strongly typed workflow events.
  • Subscribe to live updates in the frontend.
  • Implement pause/resume workflows using waitForEvent and notify.

Core Concepts

Realtime schema design

Define all workflow event types in one schema. Keep event definitions minimal and stable.

// lib/realtime.ts
const schema = {
  workflow: {
    // Completion events
    runFinish: z.object({}),
    stepFinish: z.object({ stepName: z.string(), result: z.unknown().optional() }),

    // Human‑in‑the‑loop events
    waitingForInput: z.object({ eventId: z.string(), message: z.string() }),
    inputResolved: z.object({ eventId: z.string() }),
  },
};

export const realtime = new Realtime({ schema, redis });
export type RealtimeEvents = InferRealtimeEvents<typeof realtime>;

Common mistakes:

  • Forgetting to wrap events under a namespace (e.g. workflow.*).

Emitting Events Inside Workflows

Emit inside context.run() whenever possible to avoid duplicate emissions on retries.

// app/api/workflow/route.ts
export const { POST } = serve(async (context) => {
  const channel = realtime.channel(context.workflowRunId);

  // Emit step updates
  await context.run("validate", async () => {
    const result = { ok: true };
    await channel.emit("workflow.stepFinish", { stepName: "validate", result });
    return result;
  });

  // Final event
  await context.run("finish", () => channel.emit("workflow.runFinish", {}));
});

Pitfall:

  • Emitting events outside of context.run() risks duplicate delivery if retries happen.

Human‑in‑the‑Loop Workflows

Use waitForEvent() with unique eventId values and emit a waitingForInput event immediately.

// Step that pauses for human input
const eventId = `approval-${context.workflowRunId}`;

const [{ eventData, timeout }] = await Promise.all([
  context.waitForEvent("wait-for-approval", eventId, { timeout: "5m" }),
  context.run("notify-wait", () =>
    channel.emit("workflow.waitingForInput", {
      eventId,
      message: "Waiting for approval...",
    })
  ),
]);

if (timeout) return { success: false, reason: "timeout" };

await context.run("resolved", () => channel.emit("workflow.inputResolved", { eventId }));

Important details:

  • Always handle timeout: otherwise the workflow may appear stuck.
  • Emit inputResolved so the UI can clear any pending state.
  • Use deterministic eventId prefixes per action/approval.

Notify Endpoint (User Response)

The frontend sends input back to the workflow using client.notify().

// app/api/notify/route.ts
export async function POST(req: NextRequest) {
  const { eventId, eventData } = await req.json();

  await workflowClient.notify({ eventId, eventData }); // resumes workflow
  return NextResponse.json({ success: true });
}

Common mistakes:

  • Sending the wrong eventId → workflow never resumes.
  • Sending eventData that doesn’t match the awaited type.

Realtime Subscription in the Frontend

Use a single hook to consume all workflow‑related events and manage state.

// lib/realtime-client.ts & useWorkflow hooks
useRealtime({
  enabled: !!workflowRunId,
  channels: [workflowRunId],
  events: [
    "workflow.stepFinish",
    "workflow.runFinish",
    "workflow.waitingForInput",
    "workflow.inputResolved",
  ],
  onData({ event, data }) {
    switch (event) {
      case "workflow.stepFinish":
        setSteps((prev) => [...prev, data]);
        break;
      case "workflow.waitingForInput":
        setWaitingState(data);
        break;
      case "workflow.inputResolved":
        setWaitingState((prev) => (prev?.eventId === data.eventId ? null : prev));
        break;
      case "workflow.runFinish":
        setIsRunFinished(true);
    }
  },
});

Pitfalls:

  • Forgetting to clear waiting state on inputResolved.
  • Forgetting to reset state on re‑trigger.

Triggering the Workflow

One simple endpoint triggers both basic and human‑in‑the‑loop flows.

// app/api/trigger/route.ts
export async function POST(req: NextRequest) {
  const workflowUrl = `${req.nextUrl.origin}/api/workflow`; // or /human-in-loop
  const { workflowRunId } = await workflowClient.trigger({
    url: workflowUrl,
    body: { userId: "123", action: "process" },
  });
  return NextResponse.json({ workflowRunId });
}

Recommended Patterns

  • Use one channel per workflow run (workflowRunId).
  • Always place event emissions inside context.run().
  • Use stable eventId formats for all interactive steps.
  • Subscribe to only the events your UI needs.
  • Reset UI state before every new trigger.

Source: SKILL.md on GitHub

1 warning17d3 checks · Risk SAFE
  • Gen Agent Trust Hub17d

    The skill is a comprehensive documentation and implementation guide for the Upstash Workflow SDK. It covers durable serverless workflows, agent orchestration, and reliability features. All identified external resources and tool downloads are official components of the Upstash platform or well-known development services. No security issues or malicious patterns were detected.

  • Socket17d

    No alerts

  • Snyk17d

    Risk: MEDIUM · 1 issue

Signed by skilld at 36daab8. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 6 days ago.

Activeupdated last month
metadata
{
  "author": "Upstash",
  "homepage": "https://upstash.com"
}

README badge

README badge for upstash/skills/upstash-workflow-js