このスキルは、ユーザーが「Inngest 関数を作成」「バックグラウンドジョブを実装」「ステップ関数を書く」「Inngest をセットアップ」「イベント駆動処理を実装」「耐久実行を実装」「Cron ジョブを作成」「Fan-out パターンを実装」「サガパターン」「ワークフローオーケストレーション」「step.run」「step.waitForEvent」「step.invoke」「Inngest テスト」と言った場合に使用する。
/plugin marketplace add tadokoro-ryusuke/cc-plugins/plugin install dev-core@cc-pluginsThis skill inherits all available tools. When active, it can use any tool Claude has access to.
examples/basic-function.tsexamples/error-handling.tsexamples/fan-out-pattern.tsreferences/function-config.mdreferences/patterns.mdreferences/steps-api.mdInngest はイベント駆動型の耐久実行プラットフォームで、キュー管理やインフラなしで信頼性の高いバックグラウンドジョブとワークフローを構築できる。Next.js との統合により、App Router と Pages Router の両方でサーバーレス環境での堅牢なバックグラウンド処理が実現する。
イベントは関数をトリガーする JSON ペイロード。nameとdataプロパティを持つ。
{
name: "app/user.signup",
data: {
userId: "12345",
email: "user@example.com"
},
id?: string, // 冪等性ID(24時間重複防止)
ts?: number // タイムスタンプ(ミリ秒)
}
Inngest 関数は 3 つの主要部分で構成される:
ステップは独立して再試行可能な作業単位。各ステップは個別の HTTP リクエストとして実行され、成功した結果はメモ化される。失敗時に成功済みステップを再実行せずに済む。
重要: 各ステップは単一の副作用にすべき。複数の副作用を 1 つのステップに入れると、再試行時に問題が発生する。
// ❌ 悪い例:複数の副作用
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));
npm install inngest
// 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 } };
}>(),
});
// 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 };
}
);
// 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],
});
// APIルートから
await inngest.send({
name: "app/user.signup",
data: { userId: body.userId, email: body.email },
});
再試行可能なコードブロックを実行する。
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()),
]);
await step.sleep("wait-30-min", "30m");
await step.sleepUntil("wait-date", new Date(event.data.remind_at));
最大スリープ時間: 1 年間(無料プランは 7 日間)
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());
}
// Fan-outパターンで使用
await step.sendEvent("trigger-notifications", [
{ name: "notification/email", data: { userId: "123" } },
{ name: "notification/sms", data: { userId: "123" } },
]);
const result = await step.invoke("compute-square", {
function: computeSquare,
data: { number: 4 },
timeout: "1h",
});
| オプション | デフォルト | 説明 |
|---|---|---|
id | 必須 | 関数の一意識別子 |
retries | 4 | 再試行回数(0-20) |
concurrency | - | 並行実行制限 |
詳細な設定オプションはreferences/function-config.mdを参照。
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 });
}
});
import { RetryAfterError } from "inngest";
if (!success && retryAfter) {
throw new RetryAfterError("Rate limit hit", retryAfter);
}
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 }) => {
/* ... */
}
);
// 毎日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 }) => {
/* ... */
}
);
await inngest.send({
id: "checkout-completed-" + cartId, // 冪等性キー
name: "cart/checkout.completed",
data: { cartId },
});
// 同じidで24時間以内の重複イベントは無視される
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エンドポイント
# 基本コマンド
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 - エラーハンドリング実装Build robust backtesting systems for trading strategies with proper handling of look-ahead bias, survivorship bias, and transaction costs. Use when developing trading algorithms, validating strategies, or building backtesting infrastructure.