(null)
11 |
12 | useEffect(() => {
13 | if (!ref.current) return
14 | Prism.highlightElement(ref.current)
15 | }, [])
16 |
17 | return (
18 |
22 |
23 | {children}
24 |
25 |
26 | )
27 | }
28 |
--------------------------------------------------------------------------------
/examples/cloudflare-workers-hono/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cloudflare-workers",
3 | "version": "0.0.0",
4 | "dependencies": {
5 | "@upstash/qstash": "latest",
6 | "@upstash/redis": "^1.34.3",
7 | "@upstash/workflow": "latest",
8 | "hono": "^4.5.8"
9 | },
10 | "devDependencies": {
11 | "@cloudflare/workers-types": "^4.20240815.0",
12 | "@vitest/ui": "^2.1.8",
13 | "typescript": "^5.5.2",
14 | "vitest": "^2.1.8",
15 | "wrangler": "^3.60.3"
16 | },
17 | "private": true,
18 | "scripts": {
19 | "dev": "wrangler dev --port 3001 --var UPSTASH_WORKFLOW_URL:$UPSTASH_WORKFLOW_URL",
20 | "deploy": "wrangler publish",
21 | "test": "vitest run",
22 | "test:watch": "vitest"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "tsup";
2 |
3 | export default defineConfig({
4 | entry: {
5 | index: "./src/index.ts",
6 | nextjs: "./platforms/nextjs.ts",
7 | h3: "./platforms/h3.ts",
8 | svelte: "./platforms/svelte.ts",
9 | solidjs: "./platforms/solidjs.ts",
10 | hono: "./platforms/hono.ts",
11 | cloudflare: "./platforms/cloudflare.ts",
12 | astro: "./platforms/astro.ts",
13 | express: "./platforms/express.ts",
14 | tanstack: "./platforms/tanstack.ts",
15 | },
16 | format: ["cjs", "esm"],
17 | clean: true,
18 | dts: true,
19 | // This should optimally be an optional peer dependency,
20 | // we can change it in a future release
21 | external: ["next", "express"],
22 | });
23 |
--------------------------------------------------------------------------------
/examples/agents-researcher/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.*
7 | .yarn/*
8 | !.yarn/patches
9 | !.yarn/plugins
10 | !.yarn/releases
11 | !.yarn/versions
12 |
13 | # testing
14 | /coverage
15 |
16 | # next.js
17 | /.next/
18 | /out/
19 |
20 | # production
21 | /build
22 |
23 | # misc
24 | .DS_Store
25 | *.pem
26 |
27 | # debug
28 | npm-debug.log*
29 | yarn-debug.log*
30 | yarn-error.log*
31 | .pnpm-debug.log*
32 |
33 | # env files (can opt-in for committing if needed)
34 | .env
35 | .env*.local
36 |
37 | # vercel
38 | .vercel
39 |
40 | # typescript
41 | *.tsbuildinfo
42 | next-env.d.ts
43 |
44 | bootstrap.sh
45 | ngrok.log
--------------------------------------------------------------------------------
/examples/agents-researcher/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2017",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "noEmit": true,
9 | "esModuleInterop": true,
10 | "module": "esnext",
11 | "moduleResolution": "bundler",
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "jsx": "preserve",
15 | "incremental": true,
16 | "plugins": [
17 | {
18 | "name": "next"
19 | }
20 | ],
21 | "paths": {
22 | "@/*": ["./*"]
23 | }
24 | },
25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26 | "exclude": ["node_modules"]
27 | }
28 |
--------------------------------------------------------------------------------
/examples/email-analyzer-o1/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2017",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "noEmit": true,
9 | "esModuleInterop": true,
10 | "module": "esnext",
11 | "moduleResolution": "bundler",
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "jsx": "preserve",
15 | "incremental": true,
16 | "plugins": [
17 | {
18 | "name": "next"
19 | }
20 | ],
21 | "paths": {
22 | "@/*": ["./*"]
23 | }
24 | },
25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26 | "exclude": ["node_modules"]
27 | }
28 |
--------------------------------------------------------------------------------
/examples/nextjs-webhook-stripe/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2017",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "noEmit": true,
9 | "esModuleInterop": true,
10 | "module": "esnext",
11 | "moduleResolution": "bundler",
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "jsx": "preserve",
15 | "incremental": true,
16 | "plugins": [
17 | {
18 | "name": "next"
19 | }
20 | ],
21 | "paths": {
22 | "@/*": ["./*"]
23 | }
24 | },
25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26 | "exclude": ["node_modules"]
27 | }
28 |
--------------------------------------------------------------------------------
/examples/upstash-realtime/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2017",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "noEmit": true,
9 | "esModuleInterop": true,
10 | "module": "esnext",
11 | "moduleResolution": "bundler",
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "jsx": "preserve",
15 | "incremental": true,
16 | "plugins": [
17 | {
18 | "name": "next"
19 | }
20 | ],
21 | "paths": {
22 | "@/*": ["./src/*"]
23 | }
24 | },
25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26 | "exclude": ["node_modules"]
27 | }
28 |
--------------------------------------------------------------------------------
/examples/agents-researcher/app/types.ts:
--------------------------------------------------------------------------------
1 | export type AgentName =
2 | | "Wikipedia"
3 | | "WolframAlpha"
4 | | "Exa"
5 | | "Cross Reference";
6 |
7 | export type StepRecord = {
8 | stepName: string;
9 | stepOut: string;
10 | };
11 |
12 | export type StepStatus = "init" | "loading" | "done";
13 |
14 | export type WorkflowStatus = {
15 | query: string;
16 | progress: string;
17 | agentStates: {
18 | [key in AgentName]: StepStatus;
19 | };
20 | };
21 |
22 | export type PollResult = {
23 | query: string | null;
24 | progress: string | null;
25 | wikipediaOutput: StepRecord[] | null;
26 | wolframAlphaOutput: StepRecord[] | null;
27 | searchOutput: StepRecord[] | null;
28 | crossReferenceOutput: StepRecord[] | null;
29 | };
30 |
--------------------------------------------------------------------------------
/examples/agents-instagram-post-generator/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2017",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "noEmit": true,
9 | "esModuleInterop": true,
10 | "module": "esnext",
11 | "moduleResolution": "bundler",
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "jsx": "preserve",
15 | "incremental": true,
16 | "plugins": [
17 | {
18 | "name": "next"
19 | }
20 | ],
21 | "paths": {
22 | "@/*": ["./*"]
23 | }
24 | },
25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26 | "exclude": ["node_modules"]
27 | }
28 |
--------------------------------------------------------------------------------
/examples/upstash-realtime-ai-sdk/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2017",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "noEmit": true,
9 | "esModuleInterop": true,
10 | "module": "esnext",
11 | "moduleResolution": "bundler",
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "jsx": "preserve",
15 | "incremental": true,
16 | "plugins": [
17 | {
18 | "name": "next"
19 | }
20 | ],
21 | "paths": {
22 | "@/*": ["./src/*"]
23 | }
24 | },
25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26 | "exclude": ["node_modules", "_old"]
27 | }
28 |
--------------------------------------------------------------------------------
/examples/upstash-realtime-ai-sdk/src/components/providers.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
4 | import { NuqsAdapter } from "nuqs/adapters/next/app"
5 | import { useState } from "react"
6 |
7 | export function Providers({ children }: { children: React.ReactNode }) {
8 | const [queryClient] = useState(
9 | () =>
10 | new QueryClient({
11 | defaultOptions: {
12 | queries: {
13 | staleTime: 60 * 1000,
14 | refetchOnWindowFocus: false,
15 | },
16 | },
17 | })
18 | )
19 |
20 | return (
21 |
22 | {children}
23 |
24 | )
25 | }
26 |
--------------------------------------------------------------------------------
/examples/image-gen-with-workflow/components/button.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import cx from 'utils/cx'
3 |
4 | const Button = ({
5 | variant = 'primary',
6 | className,
7 | ...props
8 | }: React.ComponentProps<'button'> & {
9 | variant?: 'primary' | 'secondary'
10 | }) => {
11 | return (
12 |
23 | )
24 | }
25 |
26 | export default Button
27 |
--------------------------------------------------------------------------------
/examples/nextjs-pages/src/pages/api/path.ts:
--------------------------------------------------------------------------------
1 | import { servePagesRouter } from "@upstash/workflow/nextjs";
2 |
3 | const someWork = (input: string) => {
4 | return `processed '${JSON.stringify(input)}'`
5 | }
6 |
7 | const { handler } = servePagesRouter<{ text: string }>(
8 | async (context) => {
9 | const input = context.requestPayload.text
10 | const result1 = await context.run("step1", async () => {
11 | const output = someWork(input)
12 | console.log("step 1 input", input, "output", output)
13 | return output
14 | });
15 |
16 | await context.run("step2", async () => {
17 | const output = someWork(result1)
18 | console.log("step 2 input", result1, "output", output)
19 | });
20 | },
21 | )
22 |
23 | export default handler;
--------------------------------------------------------------------------------
/examples/nextjs/app/sleep/route.ts:
--------------------------------------------------------------------------------
1 | import { serve } from '@upstash/workflow/nextjs'
2 |
3 | const someWork = (input: string) => {
4 | return `processed '${JSON.stringify(input)}'`
5 | }
6 |
7 | export const { POST } = serve(async (context) => {
8 | const input = context.requestPayload
9 | const result1 = await context.run('step1', async () => {
10 | const output = someWork(input)
11 | console.log('step 1 input', input, 'output', output)
12 | return output
13 | })
14 |
15 | await context.sleepUntil('sleep1', Date.now() / 1000 + 3)
16 |
17 | const result2 = await context.run('step2', async () => {
18 | const output = someWork(result1)
19 | console.log('step 2 input', result1, 'output', output)
20 | return output
21 | })
22 | })
23 |
--------------------------------------------------------------------------------
/examples/sveltekit/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./.svelte-kit/tsconfig.json",
3 | "compilerOptions": {
4 | "allowJs": true,
5 | "checkJs": true,
6 | "esModuleInterop": true,
7 | "forceConsistentCasingInFileNames": true,
8 | "resolveJsonModule": true,
9 | "skipLibCheck": true,
10 | "sourceMap": true,
11 | "strict": true,
12 | "moduleResolution": "bundler"
13 | }
14 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
15 | // except $lib which is handled by https://kit.svelte.dev/docs/configuration#files
16 | //
17 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
18 | // from the referenced tsconfig.json - TypeScript does not merge them in
19 | }
20 |
--------------------------------------------------------------------------------
/examples/agents-researcher/app/components/code-block.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import React, { useEffect } from 'react';
4 | import Prism from 'prismjs';
5 | import 'prismjs/themes/prism-twilight.css';
6 | import cx from '@/app/utils/cx';
7 |
8 | export default function CodeBlock({
9 | children,
10 | className,
11 | ...props
12 | }: React.ComponentProps<'pre'>) {
13 | const ref = React.useRef(null);
14 |
15 | useEffect(() => {
16 | if (!ref.current) return;
17 | Prism.highlightElement(ref.current);
18 | }, [children]);
19 |
20 | return (
21 |
25 | {children}
26 |
27 | );
28 | }
29 |
--------------------------------------------------------------------------------
/examples/image-gen-with-workflow/app/api/check-workflow/route.ts:
--------------------------------------------------------------------------------
1 | import { NextRequest, NextResponse } from 'next/server'
2 | import { RedisEntry } from 'utils/types'
3 | import { RATELIMIT_CODE } from 'utils/constants'
4 | import { checkRatelimit, redis, validateRequest } from 'utils/redis'
5 |
6 | export const POST = async (request: NextRequest) => {
7 | const response = await validateRequest(request, checkRatelimit)
8 | if (response.status === RATELIMIT_CODE) return response
9 |
10 | const { callKey } = (await request.json()) as { callKey: string }
11 | const result = (await redis.get(callKey)) as RedisEntry | undefined
12 |
13 | if (result) {
14 | await redis.del(callKey)
15 | }
16 |
17 | return new NextResponse(JSON.stringify(result), { status: 200 })
18 | }
19 |
--------------------------------------------------------------------------------
/examples/upstash-realtime/src/app/api/notify/route.ts:
--------------------------------------------------------------------------------
1 | import { Client } from "@upstash/workflow";
2 | import { NextRequest, NextResponse } from "next/server";
3 |
4 | const workflowClient = new Client({
5 | baseUrl: process.env.QSTASH_URL!,
6 | token: process.env.QSTASH_TOKEN!,
7 | })
8 |
9 | export async function POST(request: NextRequest) {
10 | const body = await request.json();
11 | const { eventId, eventData } = body;
12 |
13 | if (!eventId) {
14 | return NextResponse.json(
15 | { success: false, error: "eventId is required" },
16 | { status: 400 }
17 | );
18 | }
19 |
20 | // Notify the workflow
21 | await workflowClient.notify({
22 | eventId,
23 | eventData,
24 | });
25 |
26 | return NextResponse.json({ success: true });
27 | }
--------------------------------------------------------------------------------
/src/agents/constants.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * header we pass to generateText to designate the agent name
3 | *
4 | * this allows us to access the agent name when naming the context.call step,
5 | * inside fetch implementation
6 | */
7 | export const AGENT_NAME_HEADER = "upstash-agent-name";
8 |
9 | export const MANAGER_AGENT_PROMPT = `You are an agent orchestrating other AI Agents.
10 |
11 | These other agents have tools available to them.
12 |
13 | Given a prompt, utilize these agents to address requests.
14 |
15 | Don't always call all the agents provided to you at the same time. You can call one and use it's response to call another.
16 |
17 | Avoid calling the same agent twice in one turn. Instead, prefer to call it once but provide everything
18 | you need from that agent.
19 | `;
20 |
--------------------------------------------------------------------------------
/examples/cloudflare-workers/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cloudflare-workers",
3 | "version": "0.0.0",
4 | "private": true,
5 | "scripts": {
6 | "deploy": "wrangler deploy",
7 | "dev": "wrangler dev --port 3001 --var UPSTASH_WORKFLOW_URL:$UPSTASH_WORKFLOW_URL",
8 | "start": "wrangler dev --port 3001 --var UPSTASH_WORKFLOW_URL:$UPSTASH_WORKFLOW_URL",
9 | "test": "vitest",
10 | "cf-typegen": "wrangler types"
11 | },
12 | "devDependencies": {
13 | "@cloudflare/vitest-pool-workers": "^0.4.5",
14 | "@cloudflare/workers-types": "^4.20240821.1",
15 | "typescript": "^5.5.2",
16 | "vitest": "1.5.0",
17 | "wrangler": "^3.60.3"
18 | },
19 | "dependencies": {
20 | "@upstash/qstash": "latest",
21 | "@upstash/workflow": "latest"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/examples/sveltekit/src/routes/path/+server.ts:
--------------------------------------------------------------------------------
1 | import { serve } from "@upstash/workflow/svelte";
2 | import { env } from '$env/dynamic/private'
3 |
4 | const someWork = (input: string) => {
5 | return `processed '${JSON.stringify(input)}'`
6 | }
7 |
8 | export const { POST } = serve(
9 | async context => {
10 | const input = context.requestPayload
11 | const result1 = await context.run("step1", async () => {
12 | const output = someWork(input)
13 | console.log("step 1 input", input, "output", output)
14 | return output
15 | });
16 |
17 | await context.run("step2", async () => {
18 | const output = someWork(result1)
19 | console.log("step 2 input", result1, "output", output)
20 | });
21 | },
22 | {
23 | env,
24 | }
25 | )
26 |
--------------------------------------------------------------------------------
/examples/express/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "upstash-workflow-express",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "api/index.js",
6 | "scripts": {
7 | "build": "tsc -p tsconfig.json",
8 | "dev": "tsx api/index.ts",
9 | "start": "node dist/index.js",
10 | "test": "vitest run",
11 | "test:watch": "vitest"
12 | },
13 | "author": "",
14 | "license": "ISC",
15 | "dependencies": {
16 | "@types/express": "5.0.3",
17 | "@upstash/redis": "^1.34.3",
18 | "@upstash/workflow": "latest",
19 | "dotenv": "^16.4.5",
20 | "express": "^4.19.2"
21 | },
22 | "devDependencies": {
23 | "@types/node": "^20.5.2",
24 | "@vitest/ui": "^2.1.8",
25 | "tsx": "^4.7.3",
26 | "typescript": "^5.1.6",
27 | "vitest": "^2.1.8"
28 | }
29 | }
--------------------------------------------------------------------------------
/examples/astro/public/favicon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
--------------------------------------------------------------------------------
/examples/express/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
4 | "module": "commonjs" /* Specify what module code is generated. */,
5 | "outDir": "./dist" /* Specify an output folder for all emitted files. */,
6 | "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
7 | "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
8 | "strict": true /* Enable all strict type-checking options. */,
9 | "skipLibCheck": true /* Skip type checking all .d.ts files. */
10 | },
11 | "exclude": ["ci.test.ts"]
12 | }
--------------------------------------------------------------------------------
/examples/nestjs/test/app.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { Test, TestingModule } from '@nestjs/testing';
2 | import { INestApplication } from '@nestjs/common';
3 | import * as request from 'supertest';
4 | import { App } from 'supertest/types';
5 | import { AppModule } from './../src/app.module';
6 |
7 | describe('AppController (e2e)', () => {
8 | let app: INestApplication;
9 |
10 | beforeEach(async () => {
11 | const moduleFixture: TestingModule = await Test.createTestingModule({
12 | imports: [AppModule],
13 | }).compile();
14 |
15 | app = moduleFixture.createNestApplication();
16 | await app.init();
17 | });
18 |
19 | it('/ (GET)', () => {
20 | return request(app.getHttpServer())
21 | .get('/')
22 | .expect(200)
23 | .expect('Hello World!');
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/examples/solidjs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "solidstart-openai",
3 | "private": true,
4 | "type": "module",
5 | "scripts": {
6 | "dev": "vinxi dev --port 3001",
7 | "build": "vinxi build",
8 | "start": "vinxi start --port 3001"
9 | },
10 | "devDependencies": {
11 | "autoprefixer": "^10.4.19",
12 | "postcss": "^8.4.38",
13 | "tailwindcss": "^3.4.3",
14 | "vinxi": "^0.3.12"
15 | },
16 | "dependencies": {
17 | "@solidjs/meta": "0.29.4",
18 | "@solidjs/router": "^0.13.6",
19 | "@solidjs/start": "^1.0.2",
20 | "@upstash/qstash": "latest",
21 | "@upstash/workflow": "latest",
22 | "clsx": "^2.1.1",
23 | "solid-js": "^1.8.17",
24 | "tailwind-merge": "^2.5.2"
25 | },
26 | "engines": {
27 | "node": ">=18"
28 | },
29 | "version": "0.0.0"
30 | }
31 |
--------------------------------------------------------------------------------
/examples/upstash-realtime/src/app/layout.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import { RealtimeProvider } from "@upstash/realtime/client";
4 |
5 | import type { Metadata } from "next";
6 | import { Geist, Geist_Mono } from "next/font/google";
7 | import "./globals.css";
8 |
9 | const geistSans = Geist({
10 | variable: "--font-geist-sans",
11 | subsets: ["latin"],
12 | });
13 |
14 | const geistMono = Geist_Mono({
15 | variable: "--font-geist-mono",
16 | subsets: ["latin"],
17 | });
18 |
19 | export default function RootLayout({ children }: { children: React.ReactNode }) {
20 | return (
21 |
22 |
25 | {children}
26 |
27 |
28 | );
29 | }
--------------------------------------------------------------------------------
/examples/nextjs/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from 'next'
2 | import { Inter } from 'next/font/google'
3 | import './globals.css'
4 | import cx from 'utils/cx'
5 |
6 | export const metadata: Metadata = {
7 | title: 'Upstash Workflow',
8 | description: 'Generated by create next app',
9 | icons: {
10 | icon: '/favicon-32x32.png',
11 | },
12 | }
13 |
14 | const defaultFont = Inter({
15 | variable: '--font-inter',
16 | display: 'swap',
17 | style: 'normal',
18 | subsets: ['latin-ext'],
19 | })
20 |
21 | export default function RootLayout({
22 | children,
23 | }: Readonly<{
24 | children: React.ReactNode
25 | }>) {
26 | return (
27 |
28 | {children}
29 |
30 | )
31 | }
32 |
--------------------------------------------------------------------------------
/examples/agents/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from "next";
2 | import { Geist, Geist_Mono } from "next/font/google";
3 | import "./globals.css";
4 |
5 | const geistSans = Geist({
6 | variable: "--font-geist-sans",
7 | subsets: ["latin"],
8 | });
9 |
10 | const geistMono = Geist_Mono({
11 | variable: "--font-geist-mono",
12 | subsets: ["latin"],
13 | });
14 |
15 | export const metadata: Metadata = {
16 | title: "Create Next App",
17 | description: "Generated by create next app",
18 | };
19 |
20 | export default function RootLayout({
21 | children,
22 | }: Readonly<{
23 | children: React.ReactNode;
24 | }>) {
25 | return (
26 |
27 |
30 | {children}
31 |
32 |
33 | );
34 | }
35 |
--------------------------------------------------------------------------------
/examples/solidjs/src/routes/-call-qstash.ts:
--------------------------------------------------------------------------------
1 | import { Client } from "@upstash/qstash";
2 | import type { APIEvent, APIHandler } from "@solidjs/start/server";
3 |
4 | const client = new Client({ baseUrl: process.env.QSTASH_URL!, token: process.env.QSTASH_TOKEN! });
5 |
6 | export const POST: APIHandler = async (event: APIEvent) => {
7 | try {
8 | const { route, payload } = await event.request.json();
9 |
10 | const baseUrl = process.env.UPSTASH_WORKFLOW_URL ?? event.request.url.replace("/-call-qstash", "")
11 | const { messageId } = await client.publishJSON({
12 | url: `${baseUrl}/${route}`,
13 | body: payload
14 | });
15 |
16 | return new Response(JSON.stringify({ messageId }), { status: 200 });
17 | } catch (error) {
18 | return new Response(`Error when publishing to QStash: ${error}`, { status: 500 });
19 | }
20 | };
21 |
--------------------------------------------------------------------------------
/examples/tanstack-start/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["**/*.ts", "**/*.tsx", "eslint.config.js", "prettier.config.js", "vite.config.js"],
3 |
4 | "compilerOptions": {
5 | "target": "ES2022",
6 | "jsx": "react-jsx",
7 | "module": "ESNext",
8 | "lib": ["ES2022", "DOM", "DOM.Iterable"],
9 | "types": ["vite/client"],
10 |
11 | /* Bundler mode */
12 | "moduleResolution": "bundler",
13 | "allowImportingTsExtensions": true,
14 | "verbatimModuleSyntax": false,
15 | "noEmit": true,
16 |
17 | /* Linting */
18 | "skipLibCheck": true,
19 | "strict": true,
20 | "noUnusedLocals": true,
21 | "noUnusedParameters": true,
22 | "noFallthroughCasesInSwitch": true,
23 | "noUncheckedSideEffectImports": true,
24 | "baseUrl": ".",
25 | "paths": {
26 | "@/*": ["./src/*"],
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/examples/email-analyzer-o1/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from "next";
2 | import { Geist, Geist_Mono } from "next/font/google";
3 | import "./globals.css";
4 |
5 | const geistSans = Geist({
6 | variable: "--font-geist-sans",
7 | subsets: ["latin"],
8 | });
9 |
10 | const geistMono = Geist_Mono({
11 | variable: "--font-geist-mono",
12 | subsets: ["latin"],
13 | });
14 |
15 | export const metadata: Metadata = {
16 | title: "Create Next App",
17 | description: "Generated by create next app",
18 | };
19 |
20 | export default function RootLayout({
21 | children,
22 | }: Readonly<{
23 | children: React.ReactNode;
24 | }>) {
25 | return (
26 |
27 |
30 | {children}
31 |
32 |
33 | );
34 | }
35 |
--------------------------------------------------------------------------------
/examples/tanstack-start/src/routes/demo/api/single-workflow.ts:
--------------------------------------------------------------------------------
1 | import { createFileRoute } from '@tanstack/react-router'
2 | import { serve } from '@upstash/workflow/tanstack'
3 |
4 | const someWork = (input: string) => {
5 | return `processed '${JSON.stringify(input)}'`
6 | }
7 |
8 | export const Route = createFileRoute('/demo/api/single-workflow')({
9 | server: {
10 | handlers: serve(async (context) => {
11 | const input = context.requestPayload
12 | const result1 = await context.run('step1', () => {
13 | const output = someWork(input)
14 | console.log('step 1 input', input, 'output', output)
15 | return output
16 | })
17 |
18 | await context.run('step2', () => {
19 | const output = someWork(result1)
20 | console.log('step 2 input', result1, 'output', output)
21 | })
22 | }),
23 | },
24 | })
25 |
--------------------------------------------------------------------------------
/examples/upstash-realtime/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "upstash-realtime",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev --turbopack",
7 | "build": "next build --turbopack",
8 | "start": "next start",
9 | "lint": "eslint"
10 | },
11 | "dependencies": {
12 | "@upstash/realtime": "1.0.0",
13 | "@upstash/redis": "^1.35.6",
14 | "@upstash/workflow": "^0.2.21",
15 | "next": "15.5.9",
16 | "react": "19.1.0",
17 | "react-dom": "19.1.0",
18 | "zod": "^3.25.76"
19 | },
20 | "devDependencies": {
21 | "@eslint/eslintrc": "^3",
22 | "@tailwindcss/postcss": "^4",
23 | "@types/node": "^20",
24 | "@types/react": "^19",
25 | "@types/react-dom": "^19",
26 | "eslint": "^9",
27 | "eslint-config-next": "15.5.6",
28 | "tailwindcss": "^4",
29 | "typescript": "^5"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/examples/agents-instagram-post-generator/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from "next";
2 | import { Geist, Geist_Mono } from "next/font/google";
3 | import "./globals.css";
4 |
5 | const geistSans = Geist({
6 | variable: "--font-geist-sans",
7 | subsets: ["latin"],
8 | });
9 |
10 | const geistMono = Geist_Mono({
11 | variable: "--font-geist-mono",
12 | subsets: ["latin"],
13 | });
14 |
15 | export const metadata: Metadata = {
16 | title: "Create Next App",
17 | description: "Generated by create next app",
18 | };
19 |
20 | export default function RootLayout({
21 | children,
22 | }: Readonly<{
23 | children: React.ReactNode;
24 | }>) {
25 | return (
26 |
27 |
30 | {children}
31 |
32 |
33 | );
34 | }
35 |
--------------------------------------------------------------------------------
/examples/nextjs-webhook-stripe/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nextjs-webhook-stripe",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@clerk/nextjs": "^6.5.1",
13 | "@upstash/workflow": "^0.2.0",
14 | "next": "15.1.11",
15 | "react": "^19.0.0-rc-de68d2f4-20241204",
16 | "react-dom": "19.0.0-rc-de68d2f4-20241204",
17 | "resend": "^4.0.1",
18 | "stripe": "^17.4.0",
19 | "svix": "^1.42.0"
20 | },
21 | "devDependencies": {
22 | "@types/node": "^20",
23 | "@types/react": "^18",
24 | "@types/react-dom": "^18",
25 | "eslint": "^8",
26 | "eslint-config-next": "15.0.3",
27 | "postcss": "^8",
28 | "tailwindcss": "^3.4.1",
29 | "typescript": "^5"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/examples/nextjs-webhook-stripe/sh/create-user.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | CLERK_API_KEY="API_KEY"
4 |
5 |
6 | RANDOM_NUM=$(( RANDOM % 9000 + 1000 ))
7 | EXTERNAL_ID_NUM=$(( RANDOM % 9000 + 1000 ))
8 |
9 | # Get current date in ISO 8601 format
10 | CURRENT_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
11 |
12 | # Create JSON payload with unverified email
13 | curl -X POST "https://api.clerk.com/v1/users" \
14 | -H "Authorization: Bearer ${CLERK_API_KEY}" \
15 | -H "Content-Type: application/json" \
16 | -d '{
17 | "external_id": "user'"${EXTERNAL_ID_NUM}"'",
18 | "first_name": "John",
19 | "last_name": "Doe",
20 | "email_address": [
21 | "john.doe'"${RANDOM_NUM}"'@example.com"
22 | ],
23 | "skip_password_requirement": true,
24 | "public_metadata": {
25 | "role": "user",
26 | "plan": "free"
27 | },
28 | "created_at": "'"${CURRENT_DATE}"'"
29 | }'
--------------------------------------------------------------------------------
/examples/cloudflare-workers/src/index.ts:
--------------------------------------------------------------------------------
1 | import { serve } from '@upstash/workflow/cloudflare';
2 |
3 | const someWork = (input: string) => {
4 | return `processed '${JSON.stringify(input)}'`;
5 | };
6 |
7 | export default serve<{ text: string }>(
8 | async (context) => {
9 | if (!context.requestPayload) throw new Error('No payload given, send a curl request to start the workflow');
10 |
11 | const input = context.requestPayload.text;
12 | const result1 = await context.run('step1', async () => {
13 | const output = someWork(input);
14 | console.log('step 1 input', input, 'output', output);
15 | return output;
16 | });
17 |
18 | await context.run('step2', async () => {
19 | const output = someWork(result1);
20 | console.log('step 2 input', result1, 'output', output);
21 | });
22 | },
23 | {
24 | receiver: undefined,
25 | },
26 | )
27 |
--------------------------------------------------------------------------------
/examples/astro/src/pages/api/demo-workflow.ts:
--------------------------------------------------------------------------------
1 | import { serve } from "@upstash/workflow/astro";
2 |
3 | const someWork = (input: string) => {
4 | return `processed '${JSON.stringify(input)}'`
5 | }
6 |
7 | export const { POST } = serve<{ url: string }>(async (context) => {
8 | const input = context.requestPayload.url
9 | const result1 = await context.run('step1', async () => {
10 | const output = someWork(input)
11 | console.log('step 1 input', input, 'output', output)
12 | return output
13 | })
14 |
15 | await context.run('step2', async () => {
16 | const output = someWork(result1)
17 | console.log('step 2 input', result1, 'output', output)
18 | })
19 | }, {
20 | // env must be passed in astro.
21 | // for local dev, we need import.meta.env.
22 | // For deployment, we need process.env:
23 | env: {
24 | ...process.env,
25 | ...import.meta.env
26 | }
27 | })
--------------------------------------------------------------------------------
/examples/agents-researcher/app/components/agent-block.tsx:
--------------------------------------------------------------------------------
1 | import cx from '../utils/cx';
2 | import type { AgentName } from '../types';
3 |
4 | export const AgentBlock = ({
5 | children,
6 | name,
7 | agentInfoDisplay,
8 | setAgentInfoDisplay,
9 | isDisabled
10 | }: {
11 | children: React.ReactNode;
12 | name: AgentName;
13 | agentInfoDisplay: AgentName | false;
14 | setAgentInfoDisplay: (name: AgentName) => void;
15 | isDisabled: boolean;
16 | }) => {
17 | return (
18 | setAgentInfoDisplay(name)}
26 | disabled={isDisabled}
27 | >
28 | {children}
29 |
30 | );
31 | };
32 |
--------------------------------------------------------------------------------
/examples/image-gen-with-workflow/utils/types.ts:
--------------------------------------------------------------------------------
1 | import { PROMPTS } from "./constants"
2 |
3 | export type CallInfo = {
4 | duration: number
5 | result: string
6 | functionTime: number
7 | }
8 |
9 | export type RedisEntry = {
10 | time: number
11 | url: string
12 | }
13 |
14 | export type FetchParameters = {
15 | url: string
16 | method: 'POST'
17 | body: object
18 | headers: Record
19 | }
20 |
21 | export type IdeogramResponse = {
22 | created: string
23 | data: Array<{
24 | prompt: string
25 | url: string
26 | }>
27 | }
28 |
29 | export type OpenAIResponse = {
30 | created: number
31 | data: Array<{
32 | revised_prompt: string
33 | url: string
34 | }>
35 | }
36 |
37 | export type ImageResponse = IdeogramResponse | OpenAIResponse
38 |
39 | export type CallPayload = {
40 | promptIndex: number
41 | }
42 |
43 | export type Prompt = typeof PROMPTS[number]
--------------------------------------------------------------------------------
/examples/nextjs-webhook-stripe/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from "next";
2 | import localFont from "next/font/local";
3 | import "./globals.css";
4 |
5 | const geistSans = localFont({
6 | src: "./fonts/GeistVF.woff",
7 | variable: "--font-geist-sans",
8 | weight: "100 900",
9 | });
10 | const geistMono = localFont({
11 | src: "./fonts/GeistMonoVF.woff",
12 | variable: "--font-geist-mono",
13 | weight: "100 900",
14 | });
15 |
16 | export const metadata: Metadata = {
17 | title: "Create Next App",
18 | description: "Generated by create next app",
19 | };
20 |
21 | export default function RootLayout({
22 | children,
23 | }: Readonly<{
24 | children: React.ReactNode;
25 | }>) {
26 | return (
27 |
28 |
31 | {children}
32 |
33 |
34 | );
35 | }
36 |
--------------------------------------------------------------------------------
/examples/sveltekit/src/routes/-call-qstash/+server.ts:
--------------------------------------------------------------------------------
1 | import { Client } from "@upstash/qstash";
2 | import type { RequestHandler } from "@sveltejs/kit";
3 | import { json } from "@sveltejs/kit";
4 | import { env } from '$env/dynamic/private'
5 |
6 | const client = new Client({
7 | baseUrl: env.QSTASH_URL!,
8 | token: env.QSTASH_TOKEN!
9 | });
10 |
11 | export const POST: RequestHandler = async ({ request }) => {
12 | const { route, payload } = await request.json() as { route: string, payload: unknown };
13 |
14 | try {
15 | const baseUrl = env.UPSTASH_WORKFLOW_URL ?? request.url.replace("/-call-qstash", "")
16 | const { messageId } = await client.publishJSON({
17 | url: `${baseUrl}/${route}`,
18 | body: payload
19 | });
20 |
21 | return json({ messageId }, { status: 200 });
22 | } catch (error) {
23 | return json({ error: `Error when publishing to QStash: ${error}` }, { status: 500 });
24 | }
25 | };
26 |
--------------------------------------------------------------------------------
/examples/agents-researcher/app/icons/caret-dropdown.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const IconCaretDownFilled = ({
4 | className
5 | }: {
6 | className?: React.HTMLProps['className'];
7 | }) => {
8 | return (
9 |
17 |
18 |
19 |
20 | );
21 | };
22 |
--------------------------------------------------------------------------------
/examples/nestjs/.gitignore:
--------------------------------------------------------------------------------
1 | # compiled output
2 | /dist
3 | /node_modules
4 | /build
5 |
6 | # Logs
7 | logs
8 | *.log
9 | npm-debug.log*
10 | pnpm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 | lerna-debug.log*
14 |
15 | # OS
16 | .DS_Store
17 |
18 | # Tests
19 | /coverage
20 | /.nyc_output
21 |
22 | # IDEs and editors
23 | /.idea
24 | .project
25 | .classpath
26 | .c9/
27 | *.launch
28 | .settings/
29 | *.sublime-workspace
30 |
31 | # IDE - VSCode
32 | .vscode/*
33 | !.vscode/settings.json
34 | !.vscode/tasks.json
35 | !.vscode/launch.json
36 | !.vscode/extensions.json
37 |
38 | # dotenv environment variable files
39 | .env
40 | .env.development.local
41 | .env.test.local
42 | .env.production.local
43 | .env.local
44 |
45 | # temp directory
46 | .temp
47 | .tmp
48 |
49 | # Runtime data
50 | pids
51 | *.pid
52 | *.seed
53 | *.pid.lock
54 |
55 | # Diagnostic reports (https://nodejs.org/api/report.html)
56 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
57 |
--------------------------------------------------------------------------------
/examples/upstash-realtime-ai-sdk/src/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from "next";
2 | import { Geist, Geist_Mono } from "next/font/google";
3 | import "./globals.css";
4 | import { Providers } from "@/components/providers";
5 |
6 | const geistSans = Geist({
7 | variable: "--font-geist-sans",
8 | subsets: ["latin"],
9 | });
10 |
11 | const geistMono = Geist_Mono({
12 | variable: "--font-geist-mono",
13 | subsets: ["latin"],
14 | });
15 |
16 | export const metadata: Metadata = {
17 | title: "Realtime Chat",
18 | description: "Real-time chat powered by Upstash",
19 | };
20 |
21 | export default function RootLayout({
22 | children,
23 | }: Readonly<{
24 | children: React.ReactNode;
25 | }>) {
26 | return (
27 |
28 |
31 | {children}
32 |
33 |
34 | );
35 | }
36 |
--------------------------------------------------------------------------------
/examples/email-analyzer-o1/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "email-analyzer-o1",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev --turbopack",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@upstash/workflow": "^0.2.4",
13 | "next": "15.1.11",
14 | "nodemailer": "^6.9.16",
15 | "openai": "^4.79.1",
16 | "pdf-parse": "^1.1.1",
17 | "react": "^19.0.0",
18 | "react-dom": "^19.0.0",
19 | "resend": "^4.1.1"
20 | },
21 | "devDependencies": {
22 | "@eslint/eslintrc": "^3",
23 | "@types/node": "^20",
24 | "@types/nodemailer": "^6.4.17",
25 | "@types/pdf-parse": "^1.1.4",
26 | "@types/react": "^19",
27 | "@types/react-dom": "^19",
28 | "eslint": "^9",
29 | "eslint-config-next": "15.1.5",
30 | "postcss": "^8",
31 | "tailwindcss": "^3.4.1",
32 | "typescript": "^5"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/examples/ci/app/test-routes/wait-for-event/constants.ts:
--------------------------------------------------------------------------------
1 | export const NOTIFIER_WORKFLOW_ROUTE = "wait-for-event/notifier-workflow"
2 | export const NOTIFIER_RESULT = "super-secret-foo"
3 | export const NOTIFIER_CALL_COUNT_OVERRIDE = -1
4 |
5 | export const SDK_EVENT_DATA = "notifying-with-sdk"
6 | export const TEXT_EVENT_DATA = "notifying-with-text-foo"
7 | export const OBJECT_EVENT_DATA = {"notifying": "object", "with": 1}
8 |
9 | export const NOTIFIER_SECRET = process.env.UPSTASH_REDIS_REST_TOKEN!.slice(0, 10)
10 |
11 | export type NotifierWorkflowConfig = {
12 | /**
13 | * event id used in /notifier
14 | */
15 | sdkEventId: string,
16 | /**
17 | * event id used in /notifier-workflow for text
18 | */
19 | textEventId: string,
20 | /**
21 | * event id used in /notifier-workflow for object
22 | */
23 | objectEventId: string,
24 | /**
25 | * random id used to save result to redis in /notifier-workflow
26 | */
27 | redisEntryId: string
28 | }
29 |
--------------------------------------------------------------------------------
/examples/nextjs-pages/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nextjs-pages",
3 | "version": "0.0.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev -p 3001",
7 | "build": "next build",
8 | "start": "next start -p 3001",
9 | "lint": "next lint",
10 | "test": "vitest run",
11 | "test:watch": "vitest"
12 | },
13 | "dependencies": {
14 | "@upstash/qstash": "^2.7.16",
15 | "@upstash/redis": "^1.34.3",
16 | "@upstash/workflow": "latest",
17 | "clsx": "^2.1.1",
18 | "next": "14.2.35",
19 | "react": "^18",
20 | "react-dom": "^18",
21 | "tailwind-merge": "^2.5.2"
22 | },
23 | "devDependencies": {
24 | "@types/node": "^20",
25 | "@types/react": "^18",
26 | "@types/react-dom": "^18",
27 | "@vitest/ui": "^2.1.8",
28 | "eslint": "^8",
29 | "eslint-config-next": "14.2.8",
30 | "postcss": "^8",
31 | "tailwindcss": "^3.4.1",
32 | "typescript": "^5",
33 | "vitest": "^2.1.8"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/examples/upstash-realtime-ai-sdk/src/components/ui/collapsible.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
4 |
5 | function Collapsible({
6 | ...props
7 | }: React.ComponentProps) {
8 | return
9 | }
10 |
11 | function CollapsibleTrigger({
12 | ...props
13 | }: React.ComponentProps) {
14 | return (
15 |
19 | )
20 | }
21 |
22 | function CollapsibleContent({
23 | ...props
24 | }: React.ComponentProps) {
25 | return (
26 |
30 | )
31 | }
32 |
33 | export { Collapsible, CollapsibleTrigger, CollapsibleContent }
34 |
--------------------------------------------------------------------------------
/examples/nestjs/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | // @ts-check
2 | import eslint from '@eslint/js';
3 | import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
4 | import globals from 'globals';
5 | import tseslint from 'typescript-eslint';
6 |
7 | export default tseslint.config(
8 | {
9 | ignores: ['eslint.config.mjs'],
10 | },
11 | eslint.configs.recommended,
12 | ...tseslint.configs.recommendedTypeChecked,
13 | eslintPluginPrettierRecommended,
14 | {
15 | languageOptions: {
16 | globals: {
17 | ...globals.node,
18 | ...globals.jest,
19 | },
20 | sourceType: 'commonjs',
21 | parserOptions: {
22 | projectService: true,
23 | tsconfigRootDir: import.meta.dirname,
24 | },
25 | },
26 | },
27 | {
28 | rules: {
29 | '@typescript-eslint/no-explicit-any': 'off',
30 | '@typescript-eslint/no-floating-promises': 'warn',
31 | '@typescript-eslint/no-unsafe-argument': 'warn'
32 | },
33 | },
34 | );
--------------------------------------------------------------------------------
/examples/agents-instagram-post-generator/app/api/check-workflow/route.ts:
--------------------------------------------------------------------------------
1 | import { Post } from '@/app/page';
2 | import { Redis } from '@upstash/redis';
3 | import { NextRequest, NextResponse } from 'next/server';
4 |
5 | const redis = Redis.fromEnv();
6 |
7 | export async function POST(request: NextRequest) {
8 | try {
9 | const { callKey } = await request.json();
10 |
11 | if (!callKey) {
12 | return NextResponse.json(
13 | { error: 'No callKey provided' },
14 | { status: 400 }
15 | );
16 | }
17 |
18 | const posts = await redis.lrange(`${callKey}-posts`, 0, -1);
19 |
20 | if (!posts || posts.length === 0) {
21 | return NextResponse.json(null);
22 | }
23 |
24 | return NextResponse.json({
25 | posts
26 | });
27 | } catch (error) {
28 | console.error('Error checking workflow:', error);
29 | return NextResponse.json(
30 | { error: 'Failed to check workflow status' },
31 | { status: 500 }
32 | );
33 | }
34 | }
--------------------------------------------------------------------------------
/examples/solidjs/src/routes/sleep.ts:
--------------------------------------------------------------------------------
1 | import { serve } from "@upstash/workflow/solidjs"
2 |
3 | const someWork = (input: string) => {
4 | return `processed '${JSON.stringify(input)}'`
5 | }
6 |
7 | export const { POST } = serve(
8 | async context => {
9 | const input = context.requestPayload
10 | const result1 = await context.run("step1", async () => {
11 | const output = someWork(input)
12 | console.log("step 1 input", input, "output", output)
13 | return output
14 | });
15 |
16 | await context.sleepUntil("sleep1", (Date.now()/1000) + 3)
17 |
18 | const result2 = await context.run("step2", async () => {
19 | const output = someWork(result1)
20 | console.log("step 2 input", result1, "output", output)
21 | return output
22 | });
23 |
24 | await context.sleep("sleep2", 2)
25 |
26 | await context.run("step3", async () => {
27 | const output = someWork(result2)
28 | console.log("step 3 input", result2, "output", output)
29 | });
30 | }
31 | )
--------------------------------------------------------------------------------
/examples/agents/public/globe.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/image-gen-with-workflow/components/header.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | export default function Header({}: {}) {
4 | return (
5 |
26 | )
27 | }
28 |
--------------------------------------------------------------------------------
/examples/ci/app/test-routes/webhook/caller/route.ts:
--------------------------------------------------------------------------------
1 | import {
2 | WEBHOOK_TEST_METHOD,
3 | WEBHOOK_TEST_BODY,
4 | WEBHOOK_TEST_HEADER,
5 | WEBHOOK_TEST_HEADER_VALUE
6 | } from "../constants";
7 |
8 | export const POST = async (request: Request) => {
9 | const { webhookUrl } = await request.json() as { webhookUrl: string };
10 |
11 | if (!webhookUrl) {
12 | return new Response("webhook URL not provided", { status: 400 });
13 | }
14 |
15 | // Call the webhook URL with specific method, body, and headers
16 | const response = await fetch(webhookUrl, {
17 | method: WEBHOOK_TEST_METHOD,
18 | headers: {
19 | "Content-Type": "application/json",
20 | [WEBHOOK_TEST_HEADER]: WEBHOOK_TEST_HEADER_VALUE,
21 | },
22 | body: JSON.stringify(WEBHOOK_TEST_BODY),
23 | });
24 |
25 | if (!response.ok) {
26 | return new Response(`webhook call failed with status ${response.status}`, {
27 | status: 500
28 | });
29 | }
30 |
31 | return new Response("webhook called", { status: 200 });
32 | };
33 |
--------------------------------------------------------------------------------
/examples/email-analyzer-o1/public/globe.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/upstash-realtime/public/globe.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/nextjs-webhook-stripe/public/globe.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/image-gen-with-workflow/app/api/regular-simple/route.ts:
--------------------------------------------------------------------------------
1 | import { NextRequest, NextResponse } from 'next/server'
2 | import { ImageResponse } from 'utils/types'
3 |
4 | export const POST = async (request: NextRequest) => {
5 | // get prompt from request
6 | const params = await request.json()
7 | const prompt = params.prompt as string
8 |
9 | // make the fetch request
10 | const response = await fetch('https://api.ideogram.ai/generate', {
11 | method: 'POST',
12 | body: JSON.stringify({
13 | image_request: {
14 | model: 'V_2',
15 | prompt,
16 | aspect_ratio: 'ASPECT_1_1',
17 | magic_prompt_option: 'AUTO',
18 | },
19 | }),
20 | headers: {
21 | 'Content-Type': 'application/json',
22 | 'Api-Key': process.env.IDEOGRAM_API_KEY!,
23 | },
24 | })
25 |
26 | // get the image url
27 | const payload = (await response.json()) as ImageResponse
28 | const url = payload.data[0].url
29 |
30 | return new NextResponse(JSON.stringify({ url }), { status: 200 })
31 | }
32 |
--------------------------------------------------------------------------------
/examples/nextjs-12/pages/api/workflow.js:
--------------------------------------------------------------------------------
1 | import { servePagesRouter } from "@upstash/workflow/nextjs";
2 |
3 | const someWork = (input) => {
4 | return `processed '${JSON.stringify(input)}'`
5 | }
6 |
7 | const baseUrl = process.env.VERCEL_URL
8 | ? `https://${process.env.VERCEL_URL}`
9 | : process.env.UPSTASH_WORKFLOW_URL
10 | ? process.env.UPSTASH_WORKFLOW_URL
11 | : "http://localhost:3001"
12 |
13 | const endpointUrl = `${baseUrl}/api/workflow`
14 |
15 | const { handler } = servePagesRouter(
16 | async (context) => {
17 | const input = context.requestPayload
18 | const result1 = await context.run("step1", async () => {
19 | const output = someWork(input)
20 | console.log("step 1 input", input, "output", output)
21 | return output
22 | });
23 |
24 | await context.run("step2", async () => {
25 | const output = someWork(result1)
26 | console.log("step 2 input", result1, "output", output)
27 | });
28 | },
29 | {
30 | url: endpointUrl
31 | }
32 | )
33 |
34 | export default handler
35 |
--------------------------------------------------------------------------------
/examples/upstash-realtime-ai-sdk/public/globe.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/agents-instagram-post-generator/public/globe.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/nuxt/server/api/sleep.ts:
--------------------------------------------------------------------------------
1 |
2 | import { serve } from "@upstash/workflow/h3";
3 |
4 | const someWork = (input: string) => {
5 | return `processed '${JSON.stringify(input)}'`
6 | }
7 |
8 | const { handler } = serve(
9 | async context => {
10 | const input = context.requestPayload
11 | const result1 = await context.run("step1", async () => {
12 | const output = someWork(input)
13 | console.log("step 1 input", input, "output", output)
14 | return output
15 | });
16 |
17 | await context.sleepUntil("sleep1", (Date.now()/1000) + 3)
18 |
19 | const result2 = await context.run("step2", async () => {
20 | const output = someWork(result1)
21 | console.log("step 2 input", result1, "output", output)
22 | return output
23 | });
24 |
25 | await context.sleep("sleep2", 2)
26 |
27 | await context.run("step3", async () => {
28 | const output = someWork(result2)
29 | console.log("step 3 input", result2, "output", output)
30 | });
31 | }
32 | )
33 |
34 | export default handler
35 |
--------------------------------------------------------------------------------
/examples/ci/app/test-routes/wait-for-event/README.md:
--------------------------------------------------------------------------------
1 | Under the `wait-for-event` directory, there are three routes:
2 | - `/workflow`: the workflow we run in the tests to do the following in order:
3 | 1. wait for a random event which should timeout
4 | 2. sequentially, call `/notifier` and wait to get notified. `/notifier` uses `waitUntil` to sleep for 2 secs and call notify
5 | 3. in parallel, call `/notifier-workflow` and wait to get notified with text data
6 | 4. wait to get notified with object data by `/notifier-workflow`
7 | - `/notifier`: an endpoint which notifies the workflow using the SDK
8 | - `/notifier-workflow`: a workflow which notifies the original workflow two times:
9 | 1. with a text event data
10 | 2. with an object event data
11 |
12 | `/notifier` workflow will keep retrying until it has successfully notified the original worklfow two times (one with text one with object).
13 |
14 | once the `/notifier-workflow` finishes execution, it will save it's state to Redis. `/workflow` will check if `/notifier-workflow` has finished in its last step.
--------------------------------------------------------------------------------
/examples/image-gen-with-workflow/components/tooltip.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import type { TooltipProps } from '@radix-ui/react-tooltip'
3 | import * as RadixTooltip from '@radix-ui/react-tooltip'
4 |
5 | const Tooltip = ({
6 | children,
7 | title,
8 | }: TooltipProps & {
9 | title: React.ReactNode
10 | }) => {
11 | return (
12 |
13 |
14 |
15 | {children}
16 |
17 |
18 |
24 | {title}
25 |
26 |
27 |
28 |
29 |
30 | )
31 | }
32 |
33 | export default Tooltip
34 |
--------------------------------------------------------------------------------
/examples/agents-researcher/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata, Viewport } from "next";
2 | import { Inter } from "next/font/google";
3 | import "./globals.css";
4 | import cx from "./utils/cx";
5 | import "prismjs/themes/prism-tomorrow.css";
6 |
7 | export const metadata: Metadata = {
8 | title: "Cross Referencer",
9 | description: "Generated by create next app",
10 | icons: {
11 | icon: "/favicon-32x32.png",
12 | },
13 | };
14 |
15 | export const viewport: Viewport = {
16 | width: "device-width",
17 | initialScale: 1,
18 | maximumScale: 1,
19 | userScalable: false,
20 | };
21 |
22 | const defaultFont = Inter({
23 | variable: "--font-inter",
24 | display: "swap",
25 | style: "normal",
26 | subsets: ["latin-ext"],
27 | });
28 |
29 | export default function RootLayout({
30 | children,
31 | }: Readonly<{
32 | children: React.ReactNode;
33 | }>) {
34 | return (
35 |
36 | {children}
37 |
38 | );
39 | }
40 |
--------------------------------------------------------------------------------
/examples/ci/app/ci/types.ts:
--------------------------------------------------------------------------------
1 | export type TestConfig = {
2 | /**
3 | * path of the workflow endpoint
4 | *
5 | * will also be part of the redis key
6 | */
7 | route: string,
8 | /**
9 | * payload to send in the initial request
10 | */
11 | payload: TPayload,
12 | /**
13 | * headers of the request
14 | */
15 | headers?: Record,
16 | /**
17 | * number of times the endpoint is to be called in this test
18 | */
19 | expectedCallCount: number
20 | /**
21 | * expected result in the Redis
22 | */
23 | expectedResult: string
24 | /**
25 | * whether the workflow should start
26 | *
27 | * @default true
28 | */
29 | shouldWorkflowStart?: boolean
30 | }
31 |
32 | export type RedisResult = {
33 | /**
34 | * observed call count
35 | */
36 | callCount: number
37 | /**
38 | * result written to redis
39 | */
40 | result: string
41 | /**
42 | * a randomly generated string which is generated
43 | * in each test and sent as a header.
44 | */
45 | randomTestId: string
46 | }
47 |
--------------------------------------------------------------------------------
/examples/agents-instagram-post-generator/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "agents-instagram-post-generator",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev --turbopack",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@agentic/firecrawl": "^7.2.0",
13 | "@agentic/serpapi": "^7.2.0",
14 | "@upstash/redis": "^1.34.3",
15 | "@upstash/workflow": "^0.2.5-agents-3",
16 | "ai": "^4.1.0",
17 | "clsx": "^2.1.1",
18 | "lucide-react": "^0.473.0",
19 | "next": "15.1.11",
20 | "openai": "^4.79.4",
21 | "react": "^19.0.0",
22 | "react-dom": "^19.0.0",
23 | "tailwind-merge": "^2.6.0",
24 | "zod": "^3.24.1"
25 | },
26 | "devDependencies": {
27 | "@eslint/eslintrc": "^3",
28 | "@types/node": "^20",
29 | "@types/react": "^19",
30 | "@types/react-dom": "^19",
31 | "eslint": "^9",
32 | "eslint-config-next": "15.1.0",
33 | "postcss": "^8",
34 | "tailwindcss": "^3.4.1",
35 | "typescript": "^5"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/examples/agents-researcher/app/components/deploy-button.tsx:
--------------------------------------------------------------------------------
1 | export default function DeployButton() {
2 | return (
3 |
4 |
5 |
6 | );
7 | }
8 |
--------------------------------------------------------------------------------
/examples/ci/app/test-routes/failureUrl/workflow/route.ts:
--------------------------------------------------------------------------------
1 | import { serve } from "@upstash/workflow/nextjs";
2 | import { BASE_URL, TEST_ROUTE_PREFIX } from "app/ci/constants";
3 | import { testServe, expect, ANY_STRING } from "app/ci/utils";
4 | import { ERROR_MESSAGE, PAYLOAD, HEADER, HEADER_VALUE } from "../constants";
5 |
6 | const thirdPartyEndpoint = `${TEST_ROUTE_PREFIX}/failureUrl/third-party`
7 |
8 | export const { POST, GET } = testServe(
9 | serve(
10 | async (context) => {
11 | const input = context.requestPayload;
12 |
13 | expect(input, PAYLOAD);
14 | expect(context.headers.get(HEADER)!, HEADER_VALUE)
15 |
16 | await context.run("step1", () => {
17 | throw new Error(ERROR_MESSAGE);
18 | });
19 | }, {
20 | baseUrl: BASE_URL,
21 | retries: 0,
22 | failureUrl: thirdPartyEndpoint
23 | }
24 | ), {
25 | expectedCallCount: 2,
26 | expectedResult: `{"error":"Error","message":"${ERROR_MESSAGE}","stack":${ANY_STRING}}`,
27 | payload: PAYLOAD,
28 | headers: {
29 | [ HEADER ]: HEADER_VALUE,
30 | }
31 | }
32 | )
--------------------------------------------------------------------------------
/examples/nextjs-12/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
3 |
4 |
--------------------------------------------------------------------------------
/examples/nextjs/ci.test.ts:
--------------------------------------------------------------------------------
1 |
2 | import { Client } from "@upstash/qstash"
3 | import { serve } from "@upstash/workflow/nextjs"
4 | import { describe, test, expect } from "vitest"
5 |
6 | const qstashClient = new Client({
7 | baseUrl: "https://workflow-tests.requestcatcher.com/",
8 | token: "mock"
9 | })
10 |
11 | // mocking batch
12 | qstashClient.batch = async () => {
13 | return [{ messageId: "msgId" }]
14 | }
15 |
16 | const { POST: serveHandler } = serve(
17 | async (context) => {
18 | await context.sleep("sleeping", 10)
19 | }, {
20 | qstashClient,
21 | receiver: undefined
22 | }
23 | )
24 |
25 | describe("nextjs tests", () => {
26 | test("should send first invocation", async () => {
27 | const request = new Request("https://workflow-tests.requestcatcher.com/")
28 | const response = await serveHandler(request)
29 |
30 | // it should send a request, but get failed to parse error because
31 | // request catcher returns string
32 | expect(response.status).toBe(200)
33 | const result = await response.json()
34 | expect(result.workflowRunId).toBeTruthy()
35 | })
36 | })
37 |
--------------------------------------------------------------------------------
/examples/sveltekit/src/routes/sleep/+server.ts:
--------------------------------------------------------------------------------
1 | import { serve } from "@upstash/workflow/svelte";
2 | import { env } from '$env/dynamic/private'
3 |
4 | const someWork = (input: string) => {
5 | return `processed '${JSON.stringify(input)}'`
6 | }
7 |
8 | export const { POST } = serve(
9 | async context => {
10 | const input = context.requestPayload
11 | const result1 = await context.run("step1", async () => {
12 | const output = someWork(input)
13 | console.log("step 1 input", input, "output", output)
14 | return output
15 | });
16 |
17 | await context.sleepUntil("sleep1", (Date.now()/1000) + 3)
18 |
19 | const result2 = await context.run("step2", async () => {
20 | const output = someWork(result1)
21 | console.log("step 2 input", result1, "output", output)
22 | return output
23 | });
24 |
25 | await context.sleep("sleep2", 2)
26 |
27 | await context.run("step3", async () => {
28 | const output = someWork(result2)
29 | console.log("step 3 input", result2, "output", output)
30 | });
31 | },
32 | {
33 | env
34 | }
35 | )
36 |
--------------------------------------------------------------------------------
/examples/nextjs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "workflow",
3 | "version": "0.0.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev -p 3001",
7 | "build": "next build",
8 | "start": "next start -p 3001",
9 | "lint": "next lint",
10 | "test": "vitest run",
11 | "test:watch": "vitest"
12 | },
13 | "dependencies": {
14 | "@ai-sdk/openai": "^1.0.8",
15 | "@upstash/qstash": "^2.7.12",
16 | "@upstash/workflow": "latest",
17 | "ai": "^4.0.16",
18 | "clsx": "^2.1.1",
19 | "next": "14.2.35",
20 | "react": "^18.3.1",
21 | "react-dom": "^18.3.1",
22 | "tailwind-merge": "^2.5.2",
23 | "zod": "^3.25.76"
24 | },
25 | "devDependencies": {
26 | "@types/node": "^20.16.5",
27 | "@types/react": "^18.3.5",
28 | "@types/react-dom": "^18.3.0",
29 | "@vitest/ui": "^2.1.8",
30 | "eslint": "^8.0.0",
31 | "eslint-config-next": "14.2.9",
32 | "postcss": "^8.4.45",
33 | "prettier": "^3.3.3",
34 | "prettier-plugin-tailwindcss": "^0.6.6",
35 | "tailwindcss": "^3.4.1",
36 | "typescript": "^5.6.2",
37 | "vitest": "^2.1.8"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/examples/ci/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "workflow",
3 | "version": "0.0.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev -p 3001",
7 | "build": "next build",
8 | "start": "next start -p 3001",
9 | "lint": "next lint",
10 | "test": "vitest run",
11 | "test:watch": "vitest"
12 | },
13 | "dependencies": {
14 | "@upstash/qstash": "^2.7.12",
15 | "@upstash/redis": "^1.34.2",
16 | "@upstash/workflow": "latest",
17 | "@vercel/functions": "^1.5.0",
18 | "clsx": "^2.1.1",
19 | "next": "14.2.35",
20 | "react": "^18.3.1",
21 | "react-dom": "^18.3.1",
22 | "tailwind-merge": "^2.5.2",
23 | "zod": "^3.24.2"
24 | },
25 | "devDependencies": {
26 | "@types/node": "^20.16.5",
27 | "@types/react": "^18.3.5",
28 | "@types/react-dom": "^18.3.0",
29 | "@vitest/ui": "^2.1.8",
30 | "eslint": "^8.0.0",
31 | "eslint-config-next": "14.2.9",
32 | "postcss": "^8.4.45",
33 | "prettier": "^3.3.3",
34 | "prettier-plugin-tailwindcss": "^0.6.6",
35 | "tailwindcss": "^3.4.1",
36 | "typescript": "^5.6.2",
37 | "vitest": "^2.1.8"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2024 Upstash, Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/examples/sveltekit/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sveltekit",
3 | "version": "0.0.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "vite dev",
7 | "build": "vite build",
8 | "preview": "vite preview",
9 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
10 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
11 | "test": "vitest run",
12 | "test:watch": "vitest"
13 | },
14 | "devDependencies": {
15 | "@sveltejs/adapter-vercel": "4",
16 | "@sveltejs/kit": "^2.0.0",
17 | "@sveltejs/vite-plugin-svelte": "^3.0.0",
18 | "@vitest/ui": "^2.1.8",
19 | "autoprefixer": "^10.4.19",
20 | "postcss": "^8.4.39",
21 | "svelte": "^5.0.0",
22 | "svelte-check": "^4.0.0",
23 | "tailwindcss": "^3.4.6",
24 | "tslib": "^2.4.1",
25 | "typescript": "^5.0.0",
26 | "vite": "^5.0.3",
27 | "vitest": "^2.1.8"
28 | },
29 | "type": "module",
30 | "dependencies": {
31 | "@sveltejs/adapter-auto": "^3.3.1",
32 | "@upstash/qstash": "latest",
33 | "@upstash/redis": "^1.34.3",
34 | "@upstash/workflow": "latest"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/serve/utils.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, test, expect } from "bun:test";
2 | import { WorkflowContext } from "../context";
3 | import { Client } from "@upstash/qstash";
4 | import { isDisabledWorkflowContext } from "./utils";
5 | import { DisabledWorkflowContext } from "./authorization";
6 |
7 | describe("isDisabledWorkflowContext", () => {
8 | test("should return false for context", () => {
9 | const context = new WorkflowContext({
10 | qstashClient: new Client({ token: "mock" }),
11 | headers: new Headers({}) as Headers,
12 | initialPayload: "",
13 | steps: [],
14 | url: "",
15 | workflowRunId: "",
16 | });
17 |
18 | expect(isDisabledWorkflowContext(context)).toBeFalse();
19 | });
20 |
21 | test("should return true for disabled context", () => {
22 | const context = new DisabledWorkflowContext({
23 | qstashClient: new Client({ token: "mock" }),
24 | headers: new Headers({}) as Headers,
25 | initialPayload: "",
26 | steps: [],
27 | url: "",
28 | workflowRunId: "",
29 | });
30 |
31 | expect(isDisabledWorkflowContext(context)).toBeTrue();
32 | });
33 | });
34 |
--------------------------------------------------------------------------------
/examples/nextjs/app/-call-qstash/route.ts:
--------------------------------------------------------------------------------
1 | import { Client as WorkflowClient } from '@upstash/workflow'
2 | import { NextRequest } from 'next/server'
3 |
4 | const client = new WorkflowClient({
5 | baseUrl: process.env.QSTASH_URL!,
6 | token: process.env.QSTASH_TOKEN!,
7 | })
8 |
9 | export const POST = async (request: NextRequest) => {
10 | const { route, payload } = (await request.json()) as {
11 | route: string
12 | payload: unknown
13 | }
14 |
15 | console.log('Route:', route)
16 | console.log('Payload:', payload)
17 |
18 | try {
19 | const baseUrl =
20 | process.env.UPSTASH_WORKFLOW_URL ??
21 | request.url.replace('/-call-qstash', '')
22 |
23 | const { workflowRunId } = await client.trigger({
24 | url: `${baseUrl}/${route}`,
25 | body: payload,
26 | headers: {
27 | "test": "value"
28 | }
29 | })
30 |
31 | return new Response(JSON.stringify({ workflowRunId }), { status: 200 })
32 | } catch (error) {
33 | return new Response(
34 | JSON.stringify({ error: `Error when publishing to QStash: ${error}` }),
35 | {
36 | status: 500,
37 | },
38 | )
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/examples/agents-researcher/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "agents-researcher",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@langchain/community": "^0.3.28",
13 | "@langchain/core": "^0.3.40",
14 | "@langchain/exa": "^0.1.0",
15 | "@tabler/icons-react": "^3.31.0",
16 | "@upstash/redis": "^1.34.4",
17 | "@upstash/workflow": "^0.2.11",
18 | "clsx": "^2.1.1",
19 | "exa-js": "^1.4.10",
20 | "markdown-to-jsx": "^7.7.4",
21 | "next": "15.1.11",
22 | "prismjs": "^1.29.0",
23 | "react": "^19.0.0",
24 | "react-dom": "^19.0.0",
25 | "tailwind-merge": "^3.0.1"
26 | },
27 | "devDependencies": {
28 | "@eslint/eslintrc": "^3",
29 | "@types/node": "^20",
30 | "@types/prismjs": "^1.26.5",
31 | "@types/react": "^19",
32 | "@types/react-dom": "^19",
33 | "eslint": "^9",
34 | "eslint-config-next": "15.1.6",
35 | "postcss": "^8",
36 | "prettier": "^3.5.3",
37 | "tailwindcss": "^3.4.1",
38 | "typescript": "^5"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/examples/nuxt/server/api/callQstash.ts:
--------------------------------------------------------------------------------
1 | import { Client } from "@upstash/qstash";
2 | import type { H3Event } from "h3";
3 | import { defineEventHandler, readBody } from "h3";
4 |
5 | const client = new Client({ baseUrl: process.env.QSTASH_URL!, token: process.env.QSTASH_TOKEN! });
6 |
7 | export default defineEventHandler(async (event: H3Event) => {
8 | const { route, payload } = await readBody(event) as { route: string, payload: unknown };
9 |
10 | try {
11 | const request_ = event.node.req;
12 | const protocol = request_.headers["x-forwarded-proto"];
13 | const host = request_.headers.host;
14 | const url = `${protocol}://${host}${event.path}`;
15 |
16 | const baseUrl = process.env.UPSTASH_WORKFLOW_URL ?? url?.replace("/api/callQstash", "")
17 |
18 | const { messageId } = await client.publishJSON({
19 | url: `${baseUrl}/api/${route}`,
20 | body: payload,
21 | });
22 |
23 | return {
24 | statusCode: 200,
25 | body: JSON.stringify({ messageId }),
26 | };
27 | } catch (error) {
28 | return {
29 | statusCode: 500,
30 | body: `Error when publishing to QStash: ${error}`,
31 | };
32 | }
33 | });
34 |
--------------------------------------------------------------------------------
/examples/image-gen-with-workflow/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "image-gen-with-workflow",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev -p 3001",
7 | "build": "next build",
8 | "start": "next start -p 3001",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@radix-ui/react-tooltip": "^1.1.2",
13 | "@tabler/icons-react": "^3.17.0",
14 | "@upstash/qstash": "^2.7.8",
15 | "@upstash/ratelimit": "^2.0.3",
16 | "@upstash/redis": "^1.34.0",
17 | "@upstash/workflow": "^0.2.2",
18 | "@vercel/functions": "^1.4.1",
19 | "clsx": "^2.1.1",
20 | "next": "14.2.35",
21 | "pretty-ms": "^9.1.0",
22 | "prismjs": "^1.29.0",
23 | "react": "^18.3.1",
24 | "react-dom": "^18.3.1"
25 | },
26 | "devDependencies": {
27 | "@types/node": "^20.16.5",
28 | "@types/prismjs": "^1.26.4",
29 | "@types/react": "^18.3.8",
30 | "@types/react-dom": "^18.3.0",
31 | "postcss": "^8.4.47",
32 | "prettier": "^3.3.3",
33 | "prettier-plugin-tailwindcss": "^0.6.6",
34 | "tailwind-merge": "^2.5.2",
35 | "tailwindcss": "^3.4.12",
36 | "typescript": "^5.6.2"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/examples/image-gen-with-workflow/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from 'next'
2 | import { Reddit_Mono } from 'next/font/google'
3 | import cx from 'utils/cx'
4 | import './globals.css'
5 | import 'prismjs/themes/prism-tomorrow.css'
6 |
7 | export const metadata: Metadata = {
8 | title: 'Image Generation with Workflow',
9 | description: 'Optimizing Vercel Functions With Upstash Workflow',
10 | icons: {
11 | icon: '/upstash-logo.svg',
12 | },
13 | }
14 |
15 | const defaultFont = Reddit_Mono({
16 | variable: '--font-default',
17 | display: 'swap',
18 | style: ['normal'],
19 | weight: ['400', '700'],
20 | subsets: ['latin-ext'],
21 | })
22 |
23 | export default function RootLayout({
24 | children,
25 | }: Readonly<{
26 | children: React.ReactNode
27 | }>) {
28 | return (
29 |
30 |
36 | {children}
37 |
38 |
39 | )
40 | }
41 |
--------------------------------------------------------------------------------
/examples/ci/app/test-routes/wait-for-event/notifier/route.ts:
--------------------------------------------------------------------------------
1 | import { Client } from "@upstash/workflow"
2 | import { NOTIFIER_SECRET, NotifierWorkflowConfig, SDK_EVENT_DATA } from "../constants"
3 | import { waitUntil } from '@vercel/functions';
4 |
5 | const client = new Client({ baseUrl: process.env.QSTASH_URL, token: process.env.QSTASH_TOKEN! })
6 |
7 | export const POST = async (request: Request) => {
8 | if (!NOTIFIER_SECRET) {
9 | return new Response("secret not set", { status: 500 })
10 | }
11 | if (request.headers.get("authorization") !== `Bearer ${NOTIFIER_SECRET}`) {
12 | return new Response("unauthorized.", { status: 401 } )
13 | }
14 |
15 | const { sdkEventId } = await request.json() as Pick
16 |
17 | const sleepAndNotify = async () => {
18 |
19 | await new Promise(r => setTimeout(r, 1000));
20 |
21 | const result = await client.notify({
22 | eventId: sdkEventId,
23 | eventData: SDK_EVENT_DATA
24 | })
25 |
26 | if (!result.length) {
27 | console.error("failed to notify workflow.")
28 | }
29 | }
30 |
31 | waitUntil(sleepAndNotify())
32 | return new Response("notifying...", { status: 200 })
33 | }
34 |
--------------------------------------------------------------------------------
/examples/cloudflare-workers/src/serve-many.ts:
--------------------------------------------------------------------------------
1 | import { WorkflowContext } from "@upstash/workflow";
2 | import { createWorkflow, serveMany } from "@upstash/workflow/cloudflare";
3 |
4 | const workflowOne = createWorkflow(async (context) => {
5 | await context.run("step 1", async () => {
6 | console.log("workflow one says hi")
7 | })
8 |
9 | const { body, isCanceled, isFailed } = await context.invoke("invoking other", {
10 | workflow: workflowTwo,
11 | body: "hello from workflow one",
12 | })
13 |
14 | await context.run("checking invoke results", () => {
15 | console.log("invoke results", { body, isCanceled, isFailed })
16 | })
17 |
18 | await context.run("step 2", async () => {
19 | console.log("workflow one says bye")
20 | })
21 | })
22 |
23 | const workflowTwo = createWorkflow(async (context: WorkflowContext) => {
24 | await context.run("step 1", async () => {
25 | console.log("workflow two says hi")
26 | })
27 |
28 | await context.run("step 2", async () => {
29 | console.log("workflow two says bye")
30 | })
31 |
32 | return "workflow two done"
33 | }, {
34 | retries: 0
35 | })
36 |
37 | export default serveMany({
38 | workflowOne,
39 | workflowTwo
40 | })
41 |
--------------------------------------------------------------------------------
/examples/hono/README.md:
--------------------------------------------------------------------------------
1 | # Upstash Workflow Hono Example
2 |
3 | This is an example of how to use Upstash Workflow in a Hono project. You can learn more in [Workflow documentation for Hono](https://upstash.com/docs/qstash/workflow/quickstarts/hono).
4 |
5 | ## Development
6 |
7 | 1. Install the dependencies
8 |
9 | ```bash
10 | npm install
11 | ```
12 |
13 | 2. [Start the QStash development server](https://upstash.com/docs/workflow/howto/local-development):
14 |
15 | ```bash
16 | npx @upstash/qstash-cli dev
17 | ```
18 |
19 | 3. Once you run the development server, you will see `QSTASH_URL` and `QSTASH_TOKEN` environment variables for the local development server. Add these to the `.env` file:
20 |
21 | ```bash
22 | QSTASH_URL="***"
23 | QSTASH_TOKEN="***"
24 | ```
25 |
26 | When you are deploying your app to production, you don't need to set `QSTASH_URL`. You should only set the `QSTASH_TOKEN` environment variable to the token you get from [Upstash Console](https://console.upstash.com/qstash).
27 |
28 | 4. Run your app:
29 |
30 | ```bash
31 | npm run dev
32 | ```
33 |
34 | 5. Send a `POST` request to the `/workflow` endpoint.
35 |
36 | ```bash
37 | curl -X POST "http://localhost:3001/workflow" -d '{"text": "hello world!"}'
38 | ```
39 |
--------------------------------------------------------------------------------
/examples/ci/app/test-routes/failureUrl/third-party/route.ts:
--------------------------------------------------------------------------------
1 | import { CI_RANDOM_ID_HEADER, CI_ROUTE_HEADER } from "app/ci/constants"
2 | import { saveResultsWithoutContext } from "app/ci/upstash/redis"
3 | import { ANY_STRING, expect } from "app/ci/utils"
4 | import { ERROR_MESSAGE, HEADER, HEADER_VALUE } from "../constants"
5 |
6 | export const POST = async (request: Request) => {
7 | const result = await request.json() as {
8 | body: string,
9 | header: Record,
10 | workflowRunId: string
11 | }
12 |
13 | const errorMessage = atob(result.body)
14 | expect(errorMessage, `{"error":"Error","message":"${ERROR_MESSAGE}","stack":${ANY_STRING}}`)
15 | expect(request.headers.get(HEADER), HEADER_VALUE)
16 |
17 | // get id and route
18 | const randomTestId = request.headers.get(CI_RANDOM_ID_HEADER)
19 | const route = request.headers.get(CI_ROUTE_HEADER)
20 |
21 | if (!route || !randomTestId || !errorMessage) {
22 | throw new Error(`failed to get route, randomTestId or errorMessage. route: ${route}, randomTestId: ${randomTestId}, errorMessage: ${errorMessage}`)
23 | }
24 |
25 | await saveResultsWithoutContext(
26 | route, randomTestId, errorMessage
27 | )
28 |
29 | return new Response("", { status: 200 })
30 | }
--------------------------------------------------------------------------------
/examples/nextjs/app/serve-many/[...any]/route.ts:
--------------------------------------------------------------------------------
1 | import { WorkflowContext } from "@upstash/workflow";
2 | import { createWorkflow, serveMany } from "@upstash/workflow/nextjs";
3 |
4 | const workflowOne = createWorkflow(async (context) => {
5 | await context.run("step 1", async () => {
6 | console.log("workflow one says hi")
7 | })
8 |
9 | const { body, isCanceled, isFailed } = await context.invoke("invoking other", {
10 | workflow: workflowTwo,
11 | body: "hello from workflow one",
12 | })
13 |
14 | await context.run("checking invoke results", () => {
15 | console.log("invoke results", { body, isCanceled, isFailed })
16 | })
17 |
18 | await context.run("step 2", async () => {
19 | console.log("workflow one says bye")
20 | })
21 | })
22 |
23 | const workflowTwo = createWorkflow(async (context: WorkflowContext) => {
24 | await context.run("step 1", async () => {
25 | console.log("workflow two says hi")
26 | })
27 |
28 | await context.run("step 2", async () => {
29 | console.log("workflow two says bye")
30 | })
31 |
32 | return "workflow two done"
33 | }, {
34 | retries: 0
35 | })
36 |
37 | export const { POST } = serveMany(
38 | {
39 | workflowOne,
40 | workflowTwo,
41 | }
42 | )
43 |
--------------------------------------------------------------------------------
/examples/astro/src/layouts/Layout.astro:
--------------------------------------------------------------------------------
1 | ---
2 | interface Props {
3 | title: string;
4 | }
5 |
6 | const { title } = Astro.props;
7 | ---
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | {title}
18 |
19 |
20 |
21 |
22 |
23 |
51 |
--------------------------------------------------------------------------------
/examples/nuxt/server/api/serve-many/[...].ts:
--------------------------------------------------------------------------------
1 | import type { WorkflowContext } from "@upstash/workflow";
2 | import { serveMany, createWorkflow } from "@upstash/workflow/h3";
3 |
4 |
5 | const workflowOne = createWorkflow(async (context) => {
6 | await context.run("step 1", async () => {
7 | console.log("workflow one says hi")
8 | })
9 |
10 | const { body, isCanceled, isFailed } = await context.invoke("invoking other", {
11 | workflow: workflowTwo,
12 | body: "hello from workflow one",
13 | })
14 |
15 | await context.run("checking invoke results", () => {
16 | console.log("invoke results", { body, isCanceled, isFailed })
17 | })
18 |
19 | await context.run("step 2", async () => {
20 | console.log("workflow one says bye")
21 | })
22 | })
23 |
24 | const workflowTwo = createWorkflow(async (context: WorkflowContext) => {
25 | await context.run("step 1", async () => {
26 | console.log("workflow two says hi")
27 | })
28 |
29 | await context.run("step 2", async () => {
30 | console.log("workflow two says bye")
31 | })
32 |
33 | return "workflow two done"
34 | }, {
35 | retries: 0
36 | })
37 |
38 | const { handler } = serveMany({
39 | workflowOne,
40 | workflowTwo
41 | });
42 |
43 | export default handler
--------------------------------------------------------------------------------
/examples/sveltekit/src/routes/ci/+server.ts:
--------------------------------------------------------------------------------
1 | import { serve } from "@upstash/workflow/svelte";
2 | import { env } from '$env/dynamic/private'
3 | import type { RedisEntry } from "../../types";
4 | import { Redis } from "@upstash/redis"
5 |
6 | export const { POST } = serve(
7 | async (context) => {
8 | const input = context.requestPayload
9 | const result1 = await context.run("step1", async () => {
10 | const output = `step 1 input: '${input}', type: '${typeof input}', stringified input: '${JSON.stringify(input)}'`
11 | return output
12 | });
13 |
14 | await context.sleep("sleep", 1);
15 |
16 | const secret = context.headers.get("secret-header")
17 | if (!secret) {
18 | console.error("secret not found");
19 | throw new Error("secret not found. can't end the CI workflow")
20 | } else {
21 | const redis = new Redis({
22 | url: context.env["UPSTASH_REDIS_REST_URL"],
23 | token: context.env["UPSTASH_REDIS_REST_TOKEN"]
24 | })
25 | await redis.set(
26 | `ci-cf-ran-${secret}`,
27 | {
28 | secret,
29 | result: result1
30 | },
31 | { ex: 30 }
32 | )
33 | }
34 | },
35 | {
36 | env,
37 | retries: 0,
38 | }
39 | )
40 |
--------------------------------------------------------------------------------
/examples/agents/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "agents",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev --turbopack",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@agentic/firecrawl": "^7.2.0",
13 | "@agentic/serpapi": "^7.2.0",
14 | "@browserbasehq/sdk": "^2.0.0",
15 | "@langchain/community": "^0.3.26",
16 | "@mozilla/readability": "^0.5.0",
17 | "@upstash/workflow": "0.2.5-agents-2",
18 | "ai": "^4.1.0",
19 | "html-to-text": "^9.0.5",
20 | "jsdom": "^26.0.0",
21 | "next": "15.1.11",
22 | "pdf-parse": "^1.1.1",
23 | "playwright-core": "^1.49.1",
24 | "react": "^19.0.0",
25 | "react-dom": "^19.0.0",
26 | "resend": "^4.1.1",
27 | "zod": "^3.24.1"
28 | },
29 | "devDependencies": {
30 | "@eslint/eslintrc": "^3",
31 | "@types/html-to-text": "^9.0.4",
32 | "@types/jsdom": "^21.1.7",
33 | "@types/node": "^20",
34 | "@types/pdf-parse": "^1.1.4",
35 | "@types/react": "^19",
36 | "@types/react-dom": "^19",
37 | "eslint": "^9",
38 | "eslint-config-next": "15.1.5",
39 | "postcss": "^8",
40 | "tailwindcss": "^3.4.1",
41 | "typescript": "^5"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------