# inngest > このスキルは、ユーザーが「Inngest 関数を作成」「バックグラウンドジョブを実装」「ステップ関数を書く」「Inngest をセットアップ」「イベント駆動処理を実装」「耐久実行を実装」「Cron ジョブを作成」「Fan-out パターンを実装」「サガパターン」「ワークフローオーケストレーション」「step.run」「step.waitForEvent」「step.invoke」「Inngest テスト」と言った場合に使用する。 - Author: tadokoro-ryusuke - Repository: tadokoro-ryusuke/cc-plugins - Version: 20260121000758 - Stars: 0 - Forks: 0 - Last Updated: 2026-02-06 - Source: https://github.com/tadokoro-ryusuke/cc-plugins - Web: https://mule.run/skillshub/@@tadokoro-ryusuke/cc-plugins~inngest:20260121000758 --- --- name: inngest description: | このスキルは、ユーザーが「Inngest 関数を作成」「バックグラウンドジョブを実装」「ステップ関数を書く」「Inngest をセットアップ」「イベント駆動処理を実装」「耐久実行を実装」「Cron ジョブを作成」「Fan-out パターンを実装」「サガパターン」「ワークフローオーケストレーション」「step.run」「step.waitForEvent」「step.invoke」「Inngest テスト」と言った場合に使用する。 --- # Inngest 開発ガイド Inngest はイベント駆動型の耐久実行プラットフォームで、キュー管理やインフラなしで信頼性の高いバックグラウンドジョブとワークフローを構築できる。Next.js との統合により、App Router と Pages Router の両方でサーバーレス環境での堅牢なバックグラウンド処理が実現する。 ## コア概念 ### イベント(Events) イベントは関数をトリガーする JSON ペイロード。`name`と`data`プロパティを持つ。 ```typescript { name: "app/user.signup", data: { userId: "12345", email: "user@example.com" }, id?: string, // 冪等性ID(24時間重複防止) ts?: number // タイムスタンプ(ミリ秒) } ``` ### 関数(Functions) Inngest 関数は 3 つの主要部分で構成される: - **Triggers** - イベントまたは Cron - **Configuration** - id、concurrency 等 - **Handler** - ビジネスロジック ### ステップ(Steps) ステップは**独立して再試行可能な作業単位**。各ステップは個別の HTTP リクエストとして実行され、成功した結果はメモ化される。失敗時に成功済みステップを再実行せずに済む。 **重要**: 各ステップは**単一の副作用**にすべき。複数の副作用を 1 つのステップに入れると、再試行時に問題が発生する。 ```typescript // ❌ 悪い例:複数の副作用 await step.run("create-alert", async () => { const alertId = await createAlert(); // 成功 await sendAlertToSlack(alertId); // 失敗→再試行でalertが重複 }); // ✅ 良い例:1ステップ1副作用 const alertId = await step.run("create-alert", () => createAlert()); await step.run("send-to-slack", () => sendAlertToSlack(alertId)); ``` ## セットアップ ### パッケージインストール ```bash npm install inngest ``` ### クライアント作成 ```typescript // src/inngest/client.ts import { EventSchemas, Inngest } from "inngest"; export const inngest = new Inngest({ id: "my-app", schemas: new EventSchemas().fromRecord<{ "app/user.created": { data: { userId: string; email: string } }; "app/order.completed": { data: { orderId: string; total: number } }; }>(), }); ``` ### 基本的な関数定義 ```typescript // src/inngest/functions.ts import { inngest } from "./client"; export const helloWorld = inngest.createFunction( { id: "hello-world" }, // 設定 { event: "test/hello.world" }, // トリガー async ({ event, step }) => { // ハンドラー await step.sleep("wait", "1s"); return { message: "Hello " + event.data.email }; } ); ``` ### Next.js App Router 統合 ```typescript // src/app/api/inngest/route.ts import { serve } from "inngest/next"; import { inngest } from "@/inngest/client"; import { helloWorld, processOrder } from "@/inngest/functions"; export const { GET, POST, PUT } = serve({ client: inngest, functions: [helloWorld, processOrder], }); ``` ### イベント送信 ```typescript // APIルートから await inngest.send({ name: "app/user.signup", data: { userId: body.userId, email: body.email }, }); ``` ## 主要な Steps API ### step.run() - コード実行 再試行可能なコードブロックを実行する。 ```typescript const result = await step.run("fetch-data", async () => { return fetch("https://api.example.com/data").then((r) => r.json()); }); // 並列実行 const [users, orders] = await Promise.all([ step.run("fetch-users", () => fetchUsers()), step.run("fetch-orders", () => fetchOrders()), ]); ``` ### step.sleep() / step.sleepUntil() - 遅延 ```typescript await step.sleep("wait-30-min", "30m"); await step.sleepUntil("wait-date", new Date(event.data.remind_at)); ``` 最大スリープ時間: 1 年間(無料プランは 7 日間) ### step.waitForEvent() - 外部イベント待機 ```typescript const approval = await step.waitForEvent("wait-for-approval", { event: "app/invoice.approved", timeout: "7d", match: "data.invoiceId", // イベント間でマッチング }); if (approval) { await step.run("process", () => processApproval(approval)); } else { await step.run("remind", () => sendReminder()); } ``` ### step.sendEvent() - イベント送信 ```typescript // Fan-outパターンで使用 await step.sendEvent("trigger-notifications", [ { name: "notification/email", data: { userId: "123" } }, { name: "notification/sms", data: { userId: "123" } }, ]); ``` ### step.invoke() - 他の関数を呼び出し ```typescript const result = await step.invoke("compute-square", { function: computeSquare, data: { number: 4 }, timeout: "1h", }); ``` ## 基本的な関数設定 | オプション | デフォルト | 説明 | | ------------- | ---------- | ------------------ | | `id` | **必須** | 関数の一意識別子 | | `retries` | 4 | 再試行回数(0-20) | | `concurrency` | - | 並行実行制限 | 詳細な設定オプションは`references/function-config.md`を参照。 ## エラーハンドリング ### NonRetriableError - 即座に失敗 ```typescript import { NonRetriableError } from "inngest"; await step.run("get-store", async () => { try { return await database.getStore(event.data.storeId); } catch (err) { throw new NonRetriableError("Store not found", { cause: err }); } }); ``` ### RetryAfterError - 制御された遅延 ```typescript import { RetryAfterError } from "inngest"; if (!success && retryAfter) { throw new RetryAfterError("Rate limit hit", retryAfter); } ``` ### onFailure コールバック ```typescript inngest.createFunction( { id: "sync-products", retries: 5, onFailure: async ({ error, event, step }) => { await step.run("notify-slack", () => slack.postMessage({ channel: "alerts", text: "Sync failed: " + error.message, }) ); }, }, { event: "shop/sync.requested" }, async ({ event, step }) => { /* ... */ } ); ``` ## Cron スケジュール ```typescript // 毎日UTC 0:00 inngest.createFunction( { id: "daily-cleanup" }, { cron: "0 0 * * *" }, async ({ step }) => { /* ... */ } ); // タイムゾーン指定(毎週月曜9時 日本時間) inngest.createFunction( { id: "weekly-report" }, { cron: "TZ=Asia/Tokyo 0 9 * * 1" }, async ({ step }) => { /* ... */ } ); ``` ## 冪等性 ### イベントレベル ```typescript await inngest.send({ id: "checkout-completed-" + cartId, // 冪等性キー name: "cart/checkout.completed", data: { cartId }, }); // 同じidで24時間以内の重複イベントは無視される ``` ### 関数レベル ```typescript inngest.createFunction( { id: "send-checkout-email", idempotency: "event.data.cartId", // CEL式 }, { event: "cart/checkout.completed" }, async ({ event, step }) => { /* ... */ } ); ``` **注意**: 冪等性キーは**24 時間のみ**有効。 ## よくある落とし穴 | 問題 | 回避方法 | | ----------------------------- | ---------------------------------------- | | ステップ内の複数副作用 | 1 ステップ 1 副作用を徹底 | | `inngest.send()`の await 忘れ | サーバーレスは即終了するため必ず await | | ループによる大量ステップ | 最大 1,000 ステップ/関数、Fan-out を使用 | | 冪等性の 24 時間制限 | 長期の重複防止は別途実装 | | ステップ外の Date 生成 | `step.run()`内で Date 生成 | ## 推奨プロジェクト構成 ``` src/ ├── inngest/ │ ├── client.ts # Inngestクライアント(型定義含む) │ ├── functions/ │ │ ├── sendWelcomeEmail.ts │ │ ├── processOrder.ts │ │ └── index.ts # 全関数エクスポート │ └── schemas/ │ └── events.ts # イベント型定義 ├── app/ │ └── api/ │ └── inngest/ │ └── route.ts # serveエンドポイント ``` ## Dev Server ```bash # 基本コマンド npx inngest-cli@latest dev # URLを指定 npx inngest-cli@latest dev -u http://localhost:3000/api/inngest ``` Dev Server UI: `http://localhost:8288` ## 追加リソース ### リファレンスファイル 詳細なパターンと設定は以下を参照: - **`references/steps-api.md`** - Steps API 完全リファレンス - **`references/function-config.md`** - 関数設定オプション詳細 - **`references/patterns.md`** - 高度なパターン(Fan-out、バッチ、並行制御等) ### サンプルファイル `examples/`に実装例: - **`basic-function.ts`** - 基本的な Inngest 関数 - **`fan-out-pattern.ts`** - Fan-out パターン実装 - **`error-handling.ts`** - エラーハンドリング実装