first commit
Some checks failed
Test examples / Test Examples (20) (push) Has been cancelled
Test examples / Test Examples (22) (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Trigger Release / start (push) Has been cancelled
Stale issue handler / stale (push) Has been cancelled
Update Font Data / create-pull-request (push) Has been cancelled
build-and-deploy / deploy-target (push) Has been cancelled
build-and-deploy / build (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / build-wasm (nodejs) (push) Has been cancelled
build-and-deploy / build-wasm (web) (push) Has been cancelled
build-and-deploy / Deploy preview tarball (push) Has been cancelled
build-and-deploy / Potentially publish release (push) Has been cancelled
build-and-deploy / publish-turbopack-npm-packages (push) Has been cancelled
build-and-deploy / Deploy examples (push) Has been cancelled
build-and-deploy / thank you, build (push) Has been cancelled
build-and-deploy / Upload Turbopack Bytesize metrics to Datadog (push) Has been cancelled
Rspack Next.js development integration tests / Rspack integration tests (push) Has been cancelled
Rspack Next.js production integration tests / Rspack integration tests (push) Has been cancelled
Turbopack Next.js development integration tests / Next.js integration tests (push) Has been cancelled
Turbopack Next.js production integration tests / Next.js integration tests (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack development test manifest (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack production test manifest (push) Has been cancelled
Upload bundler test manifests to areweturboyet.com / Upload test results (push) Has been cancelled
Update React / create-pull-request (push) Has been cancelled
test-e2e-project-reset-cron / reset-test-project (push) Has been cancelled
Notify about the top 15 issues/PRs/feature requests (most reacted) in the last 90 days / run (push) Has been cancelled

This commit is contained in:
Arian Tron
2026-03-10 19:37:31 +03:30
commit 61f56f997c
27684 changed files with 2784175 additions and 0 deletions

40
examples/with-temporal/.gitignore vendored Normal file
View File

@@ -0,0 +1,40 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts

View File

@@ -0,0 +1,91 @@
# Temporal + Next.js Example
This is a starter project for creating resilient Next.js applications with [Temporal](https://temporal.io/). Whenever our [API routes](https://nextjs.org/docs/api-routes/introduction) need to do any of the following, we can greatly increase our code's fault tolerance by using Temporal:
- Perform a series of network requests (to a database, another function, an internal service, or an external API), any of which may fail. Temporal will automatically set timeouts and retry, as well as remember the state of execution in the event of power loss.
- Long-running tasks
- Cron jobs
The starter project has this logic flow:
- Load [`localhost:3000`](http://localhost:3000)
- Click the “Create order” button
- The click handler POSTs a new order object to `/api/orders`
- The serverless function at `pages/api/orders/index.ts`:
- Parses the order object
- Tells Temporal Server to start a new Order Workflow, and passes the user ID and order info
- Waits for the Workflow result and returns it to the client
- Temporal Server puts Workflow tasks on the `orders` task queue
- The Worker executes chunks of Workflow and Activity code. The Activity code logs:
```
Reserving 2 of item B102
Charging user 123 for 2 of item B102
```
- The Workflow completes and Temporal Server sends the result back to the serverless function, which returns it to the client, which alerts the result.
Here is the Temporal code:
- The Workflow: `temporal/src/workflows/order.ts`
- The Activities: `temporal/src/activities/{payment|inventory}.ts`
There are three parts of this starter project that are left unimplemented:
- Authentication (currently, the client sends their user ID in the authorization header): `pages/api/orders/index.ts`
- Doing database queries to check and alter inventory levels: `temporal/src/activities/inventory.ts`
- Communicating with a payments service (or function) to charge the user: `temporal/src/activities/payment.ts`
## Deploy your own
### Web server
The Next.js server can be deployed using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example):
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/vercel/next.js/tree/canary/examples/with-temporal&project-name=with-temporal&repository-name=with-temporal)
### Worker
One or more instances of the worker (`temporal/src/worker/`) can be deployed to a PaaS (each worker is a long-running Node process, so it can't run on a FaaS/serverless platform).
### Temporal Server
Temporal Server is a cluster of internal services, a database of record, and a search database. It can be run locally with Docker Compose and [deployed](https://docs.temporal.io/docs/server/production-deployment) with a container orchestration service like Kubernetes or ECS.
## How to use
Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init), [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/), or [pnpm](https://pnpm.io) to bootstrap the example:
```bash
npx create-next-app --example with-temporal next-temporal-app
```
```bash
yarn create next-app --example with-temporal next-temporal-app
```
```bash
pnpm create next-app --example with-temporal next-temporal-app
```
The Temporal Node SDK requires [Node `>= 14`, `node-gyp`, and Temporal Server](https://docs.temporal.io/docs/node/getting-started#step-0-prerequisites). Once you have everything installed, you can develop locally with the below commands in four different shells:
In the Temporal Server docker directory:
```bash
docker compose up
```
In the `next-temporal-app/` directory:
```bash
npm run dev
```
```bash
npm run build-worker.watch
```
```bash
npm run start-worker
```

View File

@@ -0,0 +1,28 @@
import React, { ReactNode } from "react";
import Link from "next/link";
import Head from "next/head";
type Props = {
children?: ReactNode;
title?: string;
};
const Layout = ({ children, title = "This is the default title" }: Props) => (
<div>
<Head>
<title>{title}</title>
</Head>
<header>
<nav>
<Link href="/">Home</Link> | <Link href="/about">About</Link>{" "}
</nav>
</header>
{children}
<footer>
<hr />
<span>I'm here to stay (Footer)</span>
</footer>
</div>
);
export default Layout;

View File

@@ -0,0 +1,43 @@
{
"private": true,
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start",
"type-check": "tsc",
"build:worker": "tsc --build temporal/tsconfig.json",
"build:worker.watch": "tsc --build --watch temporal/tsconfig.json",
"start:worker": "cross-env TS_NODE_PROJECT=\"temporal/tsconfig.json\" node --loader ts-node/esm temporal/src/worker.ts",
"start:worker.watch": "cross-env TS_NODE_PROJECT=\"temporal/tsconfig.json\" nodemon temporal/src/worker.ts"
},
"dependencies": {
"next": "latest",
"node-fetch": "^3.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"temporalio": "^0.17.2"
},
"devDependencies": {
"@babel/core": "7.22.5",
"@tsconfig/node16": "^1.0.0",
"@types/node": "^12.12.21",
"@types/node-fetch": "^3.0.3",
"@types/react": "^17.0.2",
"@types/react-dom": "^17.0.1",
"@typescript-eslint/eslint-plugin": "^6.1.0",
"@typescript-eslint/parser": "^6.1.0",
"cross-env": "^7.0.3",
"nodemon": "^2.0.12",
"ts-node": "^10.2.1",
"typescript": "^4.4.2"
},
"nodemonConfig": {
"execMap": {
"ts": "node --loader ts-node/esm"
},
"ext": "ts",
"watch": [
"temporal/src"
]
}
}

View File

@@ -0,0 +1,15 @@
import type { NextPage } from "next";
import Link from "next/link";
import Layout from "../components/Layout";
const AboutPage: NextPage = () => (
<Layout title="About | Next.js + Temporal Example">
<h1>About</h1>
<p>This is the about page</p>
<p>
<Link href="/">Go home</Link>
</p>
</Layout>
);
export default AboutPage;

View File

@@ -0,0 +1,38 @@
import { NextApiRequest, NextApiResponse } from "next";
import { WorkflowClient } from "@temporalio/client";
import { order } from "../../../temporal/src/workflows";
export type Data = {
result: string;
};
function getUserId(token?: string): string {
// TODO if the token is a JWT, decode & verify it. If it's a session ID,
// look up the user's ID in the session store.
return "user-id-123";
}
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<Data>,
) {
if (req.method !== "POST") {
res.send({ result: "Error code 405: use POST" });
return;
}
const userId: string = getUserId(req.headers.authorization);
const { itemId, quantity } = JSON.parse(req.body);
// Connect to our Temporal Server running locally in Docker
const client = new WorkflowClient();
// Execute the order Workflow and wait for it to finish
const result = await client.execute(order, {
taskQueue: "my-nextjs-project",
workflowId: "my-business-id",
args: [userId, itemId, quantity],
});
return res.status(200).json({ result });
}

View File

@@ -0,0 +1,31 @@
import Link from "next/link";
import Layout from "../components/Layout";
import { Data } from "./api/orders";
const IndexPage = () => (
<Layout title="Home | Next.js + Temporal Example">
<h1>Hello Next.js 👋</h1>
<button
onClick={async () => {
const newOrder = { itemId: "B102", quantity: 2 };
const response = await fetch("/api/orders", {
method: "POST",
headers: { Authorization: "session-id-or-jwt" },
body: JSON.stringify(newOrder),
});
const data: Data = await response.json();
console.log(data);
alert(data.result);
}}
>
Create order
</button>
<p>
<Link href="/about">About</Link>
</p>
</Layout>
);
export default IndexPage;

View File

@@ -0,0 +1,3 @@
{
"type": "module"
}

View File

@@ -0,0 +1,54 @@
import fetch from "node-fetch";
export type ChargeResult = {
status: string;
errorMessage?: string;
};
export async function chargeUser(
userId: string,
itemId: string,
quantity: number,
): Promise<ChargeResult> {
// TODO send request to the payments service that looks up the user's saved
// payment info and the cost of the item and attempts to charge their payment
// method.
console.log(`Charging user ${userId} for ${quantity} of item ${itemId}`);
try {
const response = await fetch("http://httpbin.org/get?status=success");
const body: any = await response.json();
return { status: body.args.status };
} catch (e: any) {
return { status: "failure", errorMessage: e.message };
}
}
export async function checkAndDecrementInventory(
itemId: string,
quantity: number,
): Promise<boolean> {
// TODO a database request that—in a single operation or transaction—checks
// whether there are `quantity` items remaining, and if so, decreases the
// total. Something like:
// const result = await db.collection('items').updateOne(
// { _id: itemId, numAvailable: { $gte: quantity } },
// { $inc: { numAvailable: -quantity } }
// )
// return result.modifiedCount === 1
console.log(`Reserving ${quantity} of item ${itemId}`);
return true;
}
export async function incrementInventory(
itemId: string,
quantity: number,
): Promise<boolean> {
// TODO increment inventory:
// const result = await db.collection('items').updateOne(
// { _id: itemId },
// { $inc: { numAvailable: quantity } }
// )
// return result.modifiedCount === 1
console.log(`Incrementing ${itemId} inventory by ${quantity}`);
return true;
}

View File

@@ -0,0 +1,18 @@
import { Worker } from "@temporalio/worker";
import { URL } from "url";
import path from "path";
import * as activities from "./activities.js";
// Support running both complied code and ts-node/esm loader
const workflowsPath = new URL(
`./workflows${path.extname(import.meta.url)}`,
import.meta.url,
).pathname;
const worker = await Worker.create({
workflowsPath,
activities,
taskQueue: "my-nextjs-project",
});
await worker.run();

View File

@@ -0,0 +1,34 @@
import { proxyActivities } from "@temporalio/workflow";
// Only import the activity types
import type * as activities from "./activities.js";
const { chargeUser, checkAndDecrementInventory, incrementInventory } =
proxyActivities<typeof activities>({
startToCloseTimeout: "1 minute",
});
export async function order(
userId: string,
itemId: string,
quantity: number,
): Promise<string> {
const haveEnoughInventory: boolean = await checkAndDecrementInventory(
itemId,
quantity,
);
if (haveEnoughInventory) {
const result: activities.ChargeResult = await chargeUser(
userId,
itemId,
quantity,
);
if (result.status === "success") {
return `Order successful!`;
} else {
await incrementInventory(itemId, quantity);
return `Unable to complete payment. Error: ${result.errorMessage}`;
}
} else {
return `Sorry, we don't have enough items in stock to fulfill your order.`;
}
}

View File

@@ -0,0 +1,15 @@
{
"extends": "@tsconfig/node16/tsconfig.json",
"version": "4.4.2",
"compilerOptions": {
"target": "es2017",
"module": "esnext",
"moduleResolution": "node",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"rootDir": "./src",
"outDir": "./lib"
},
"include": ["src/**/*.ts"]
}

View File

@@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx"
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["temporal/"]
}