inngest-v3-v4-migration

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Inngest v3 to v4 Migration

Inngest v3 迁移至 v4

Use this skill when the user asks to upgrade Inngest, fix v3/v4 errors, migrate realtime, or clean up a codebase that has mixed SDK patterns.
This skill is agent-first: detect actual usage first, make mechanical API changes in a controlled order, then typecheck and run focused tests.
当用户要求升级Inngest、修复v3/v4版本错误、迁移实时功能或清理存在混合SDK模式的代码库时,使用本技能。
本技能以Agent优先:先检测实际使用情况,按可控顺序进行机械API变更,然后进行类型检查并运行针对性测试。

When to Trigger

触发场景

Use this skill for requests like:
  • "Upgrade Inngest from v3 to v4"
  • "Fix our Inngest v4 migration"
  • "We're getting signing key required / cloud mode errors after upgrading"
  • "
    step.invoke
    with a string function ID stopped working"
  • "
    @inngest/realtime
    broke after installing
    inngest@4
    "
  • "Move from EventSchemas to eventType"
  • "Make this existing Inngest repo v4-compatible"
If the user asks for a broad codebase reliability audit first, use
inngest-brownfield-audit
to choose scope, then return here for the v4 changes.
以下类型的请求适用本技能:
  • "将Inngest从v3升级到v4"
  • "修复我们的Inngest v4迁移问题"
  • "升级后出现签名密钥必填/云模式错误"
  • "使用字符串函数ID的
    step.invoke
    停止工作"
  • "安装
    inngest@4
    @inngest/realtime
    失效"
  • "从EventSchemas切换到eventType"
  • "让现有Inngest仓库兼容v4版本"
如果用户首先要求进行广泛的代码库可靠性审计,请使用
inngest-brownfield-audit
确定范围,再返回此处进行v4版本变更。

Migration Scan

迁移扫描

Start by locating all Inngest surfaces:
bash
rg -n '"inngest"|"@inngest/realtime"|"@inngest/agent-kit"' package.json **/package.json
rg -n 'new Inngest|EventSchemas|eventType|staticSchema|createFunction\\(|serve\\(|connect\\(|step\\.invoke|referenceFunction|@inngest/realtime|realtimeMiddleware|useInngestSubscription|serveHost|rewriteGatewayEndpoint|logLevel|streaming:|signingKey|signingKeyFallback|baseUrl|INNGEST_DEV|INNGEST_SIGNING_KEY' .
Then classify the repo:
  • No Inngest yet: use
    inngest-setup
    , not this migration skill.
  • v3 only: migrate the SDK and all breaking changes together.
  • mixed v3/v4: prioritize removing broken v3 APIs from v4 code.
  • v4 mostly done: focus on missed runtime gotchas like local dev mode, serverless
    maxRuntime
    , realtime package imports, and string
    step.invoke
    .
Before editing, record:
text
Inngest migration scan:
- Current package versions:
- Client files:
- Serve/connect entrypoints:
- Functions using old trigger syntax:
- EventSchemas usage:
- Realtime v3 package usage:
- step.invoke string IDs:
- Serverless runtime constraints:
- Tests/checks available:
首先定位所有Inngest相关代码:
bash
rg -n '"inngest"|"@inngest/realtime"|"@inngest/agent-kit"' package.json **/package.json
rg -n 'new Inngest|EventSchemas|eventType|staticSchema|createFunction\\(|serve\\(|connect\\(|step\\.invoke|referenceFunction|@inngest/realtime|realtimeMiddleware|useInngestSubscription|serveHost|rewriteGatewayEndpoint|logLevel|streaming:|signingKey|signingKeyFallback|baseUrl|INNGEST_DEV|INNGEST_SIGNING_KEY' .
然后对仓库进行分类:
  • 未使用Inngest:使用
    inngest-setup
    ,而非本迁移技能。
  • 仅使用v3版本:同步迁移SDK及所有破坏性变更。
  • 混合使用v3/v4:优先移除v4代码中已失效的v3 API。
  • 基本完成v4迁移:关注遗漏的运行时问题,如本地开发模式、无服务器
    maxRuntime
    、实时包导入以及字符串形式的
    step.invoke
编辑前记录以下信息:
text
Inngest迁移扫描结果:
- 当前包版本:
- 客户端文件:
- Serve/connect入口:
- 使用旧触发器语法的函数:
- EventSchemas使用情况:
- v3实时包使用情况:
- step.invoke字符串ID:
- 无服务器运行时限制:
- 可用测试/检查项:

Upgrade Order

升级步骤

  1. Update package versions.
  2. Fix client construction and local/prod mode.
  3. Move serve options to the client.
  4. Move triggers into
    createFunction
    options.
  5. Replace
    EventSchemas
    with
    eventType()
    /
    staticSchema()
    .
  6. Rewrite
    step.invoke()
    string IDs.
  7. Migrate realtime from
    @inngest/realtime
    to v4 native APIs.
  8. Update middleware and logging.
  9. Configure checkpointing/serverless runtime.
  10. Typecheck, run tests, and optionally sync with the dev server.
  1. 更新包版本。
  2. 修复客户端构造及本地/生产模式配置。
  3. 将serve选项迁移至客户端。
  4. 将触发器移入
    createFunction
    选项。
  5. eventType()
    /
    staticSchema()
    替代
    EventSchemas
  6. 重写
    step.invoke()
    的字符串ID。
  7. 将实时功能从
    @inngest/realtime
    迁移至v4原生API。
  8. 更新中间件与日志配置。
  9. 配置检查点/无服务器运行时。
  10. 类型检查、运行测试,并可选与开发服务器同步。

Package and Environment

包与环境配置

Install the latest v4 SDK:
bash
npm install inngest@latest
安装最新v4 SDK:
bash
npm install inngest@latest

or pnpm add inngest@latest

或 pnpm add inngest@latest

or yarn add inngest@latest

或 yarn add inngest@latest


If the repo uses v3 realtime, remove `@inngest/realtime`; v4 realtime lives in
the `inngest` package and subpaths such as `inngest/realtime`, `inngest/react`,
and native `step.realtime` / `inngest.realtime`.

v4 defaults to Cloud mode. For local development, use an env var:

```bash
INNGEST_DEV=1 npm run dev
Do not hardcode
isDev: true
in source unless the repo's existing environment pattern clearly scopes it to local-only code. Production should use
INNGEST_SIGNING_KEY
.

如果仓库使用v3实时功能,移除`@inngest/realtime`;v4实时功能位于`inngest`包及其子路径中,如`inngest/realtime`、`inngest/react`,以及原生的`step.realtime` / `inngest.realtime`。

v4默认使用云模式。本地开发时,使用环境变量:

```bash
INNGEST_DEV=1 npm run dev
除非仓库现有环境模式明确限定为仅本地代码,否则不要在源码中硬编码
isDev: true
。生产环境应使用
INNGEST_SIGNING_KEY

Client and Serve Options

客户端与Serve选项

In v4, options such as
signingKey
,
signingKeyFallback
, and
baseUrl
belong on
new Inngest(...)
, not on
serve(...)
.
typescript
// Old v3
app.use(
  "/api/inngest",
  serve({
    client: inngest,
    functions,
    signingKey: process.env.INNGEST_SIGNING_KEY,
    baseUrl: process.env.INNGEST_BASE_URL,
  })
);

// New v4
export const inngest = new Inngest({
  id: "my-app",
  signingKey: process.env.INNGEST_SIGNING_KEY,
  baseUrl: process.env.INNGEST_BASE_URL,
});

app.use("/api/inngest", serve({ client: inngest, functions }));
If the repo already relies on supported environment variables and does not pass serve options explicitly, no code change may be required for those keys.
Other renames:
  • serveHost
    ->
    serveOrigin
  • streaming: "force"
    ->
    streaming: true
  • streaming: "allow"
    ->
    streaming: true
  • streaming: false
    stays
    false
  • logLevel
    is removed; pass a
    logger
    such as
    new ConsoleLogger({ level })
在v4中,
signingKey
signingKeyFallback
baseUrl
等选项应配置在
new Inngest(...)
中,而非
serve(...)
typescript
// 旧v3写法
app.use(
  "/api/inngest",
  serve({
    client: inngest,
    functions,
    signingKey: process.env.INNGEST_SIGNING_KEY,
    baseUrl: process.env.INNGEST_BASE_URL,
  })
);

// 新v4写法
export const inngest = new Inngest({
  id: "my-app",
  signingKey: process.env.INNGEST_SIGNING_KEY,
  baseUrl: process.env.INNGEST_BASE_URL,
});

app.use("/api/inngest", serve({ client: inngest, functions }));
如果仓库已依赖支持的环境变量且未显式传递serve选项,则这些配置项无需修改。
其他重命名:
  • serveHost
    ->
    serveOrigin
  • streaming: "force"
    ->
    streaming: true
  • streaming: "allow"
    ->
    streaming: true
  • streaming: false
    保持不变
  • logLevel
    已移除;需传递
    logger
    ,如
    new ConsoleLogger({ level })

createFunction Triggers

createFunction 触发器

Triggers move into the first argument's options object.
typescript
// Old v3
inngest.createFunction(
  { id: "send-welcome" },
  { event: "user/created" },
  async ({ event, step }) => {}
);

// New v4
inngest.createFunction(
  { id: "send-welcome", triggers: [{ event: "user/created" }] },
  async ({ event, step }) => {}
);
Cron triggers move the same way:
typescript
inngest.createFunction(
  { id: "nightly-sync", triggers: [{ cron: "0 2 * * *" }] },
  async ({ step }) => {}
);
If a function is invoked only via
step.invoke
, it may be triggerless.
触发器需移入第一个参数的选项对象中。
typescript
// 旧v3写法
inngest.createFunction(
  { id: "send-welcome" },
  { event: "user/created" },
  async ({ event, step }) => {}
);

// 新v4写法
inngest.createFunction(
  { id: "send-welcome", triggers: [{ event: "user/created" }] },
  async ({ event, step }) => {}
);
Cron触发器同样迁移:
typescript
inngest.createFunction(
  { id: "nightly-sync", triggers: [{ cron: "0 2 * * *" }] },
  async ({ step }) => {}
);
如果函数仅通过
step.invoke
调用,则可以无触发器。

EventSchemas to eventType/staticSchema

从EventSchemas迁移至eventType/staticSchema

Replace centralized
EventSchemas
with event-specific definitions.
typescript
import { Inngest, eventType, staticSchema } from "inngest";
import { z } from "zod";

export const userCreated = eventType("user/created", {
  schema: z.object({
    userId: z.string(),
    email: z.string().email(),
  }),
});

type InvoicePaid = {
  invoiceId: string;
  customerId: string;
};

export const invoicePaid = eventType("billing/invoice.paid", {
  schema: staticSchema<InvoicePaid>(),
});
Use event types consistently:
typescript
await inngest.send(userCreated.create({ userId, email }));

inngest.createFunction(
  { id: "on-user-created", triggers: [userCreated] },
  async ({ event }) => {}
);

await step.waitForEvent("wait-for-invoice", {
  event: invoicePaid,
  timeout: "7d",
});
Important:
staticSchema
expects a type, not an interface. Convert interfaces to type aliases when needed.
用事件专属定义替换集中式的
EventSchemas
typescript
import { Inngest, eventType, staticSchema } from "inngest";
import { z } from "zod";

export const userCreated = eventType("user/created", {
  schema: z.object({
    userId: z.string(),
    email: z.string().email(),
  }),
});

type InvoicePaid = {
  invoiceId: string;
  customerId: string;
};

export const invoicePaid = eventType("billing/invoice.paid", {
  schema: staticSchema<InvoicePaid>(),
});
保持事件类型的一致性使用:
typescript
await inngest.send(userCreated.create({ userId, email }));

inngest.createFunction(
  { id: "on-user-created", triggers: [userCreated] },
  async ({ event }) => {}
);

await step.waitForEvent("wait-for-invoice", {
  event: invoicePaid,
  timeout: "7d",
});
重要提示:
staticSchema
接受类型别名而非接口。必要时将接口转换为类型别名。

step.invoke

step.invoke

v4 no longer accepts raw string function IDs. Use an imported function reference or
referenceFunction()
.
typescript
import { referenceFunction } from "inngest";

await step.invoke("run-report", {
  function: referenceFunction({
    appId: "analytics-app",
    functionId: "generate-report",
  }),
  data: { reportId },
});
If the target function is in the same codebase, prefer passing the imported function itself:
typescript
await step.invoke("run-report", {
  function: generateReport,
  data: { reportId },
});
v4不再接受原始字符串函数ID。需使用导入的函数引用或
referenceFunction()
typescript
import { referenceFunction } from "inngest";

await step.invoke("run-report", {
  function: referenceFunction({
    appId: "analytics-app",
    functionId: "generate-report",
  }),
  data: { reportId },
});
如果目标函数在同一代码库中,优先传递导入的函数本身:
typescript
await step.invoke("run-report", {
  function: generateReport,
  data: { reportId },
});

Realtime Migration

实时功能迁移

v3 realtime used
@inngest/realtime
and middleware-injected
publish
. v4 realtime is native.
Replace:
  • @inngest/realtime
    package
  • realtimeMiddleware()
  • handler args such as
    { publish }
  • v3 React hooks such as
    useInngestSubscription()
With:
  • channel definitions from
    inngest/realtime
  • step.realtime.publish
    between steps
  • inngest.realtime.publish
    inside an existing
    step.run
  • subscription helpers/hooks from current v4 APIs
Use
inngest-realtime
for detailed patterns. Do not call
step.realtime.publish
from inside
step.run
; use
inngest.realtime.publish
there to avoid step-in-step behavior.
v3实时功能使用
@inngest/realtime
和中间件注入的
publish
。v4实时功能为原生内置。
替换以下内容:
  • @inngest/realtime
  • realtimeMiddleware()
  • 处理器参数如
    { publish }
  • v3 React钩子如
    useInngestSubscription()
替换为:
  • 来自
    inngest/realtime
    的通道定义
  • 步骤间的
    step.realtime.publish
  • 现有
    step.run
    内的
    inngest.realtime.publish
  • 当前v4 API中的订阅助手/钩子
详细模式请使用
inngest-realtime
。请勿在
step.run
内调用
step.realtime.publish
;应使用
inngest.realtime.publish
以避免步骤嵌套行为。

Parallelism and Checkpointing

并行性与检查点

v4 enables optimized parallelism and checkpointing by default.
Watch for
Promise.race
over steps. With optimized parallelism,
Promise.race
waits for all step promises to settle. If the repo relies on first-winner behavior, use
group.parallel()
.
For serverless platforms, configure checkpointing
maxRuntime
slightly below the platform limit:
typescript
export const inngest = new Inngest({
  id: "my-app",
  checkpointing: {
    maxRuntime: "50s",
  },
});
On Vercel or similar frameworks, also set the route handler's max duration where the platform supports it.
v4默认启用优化的并行性与检查点。
注意步骤上的
Promise.race
。在优化并行性下,
Promise.race
会等待所有步骤Promise完成。如果仓库依赖先完成即返回的行为,请使用
group.parallel()
对于无服务器平台,配置检查点
maxRuntime
略低于平台限制:
typescript
export const inngest = new Inngest({
  id: "my-app",
  checkpointing: {
    maxRuntime: "50s",
  },
});
在Vercel或类似框架中,还需在平台支持的情况下设置路由处理器的最大时长。

Connect Changes

Connect变更

If the repo uses Connect:
  • rewriteGatewayEndpoint
    is replaced by
    gatewayUrl
    .
  • Connect may use worker-thread isolation. If integration issues appear, check
    isolateExecution: false
    or
    INNGEST_CONNECT_ISOLATE_EXECUTION=false
    .
  • Keep target URLs and signing/event keys in env vars.
如果仓库使用Connect:
  • rewriteGatewayEndpoint
    替换为
    gatewayUrl
  • Connect可能使用工作线程隔离。如果出现集成问题,检查
    isolateExecution: false
    INNGEST_CONNECT_ISOLATE_EXECUTION=false
  • 将目标URL和签名/事件密钥保留在环境变量中。

Verification

验证环节

Run checks in increasing confidence:
  1. Package manager install/update.
  2. Typecheck.
  3. Unit/integration tests around migrated functions.
  4. Start the app with
    INNGEST_DEV=1
    .
  5. Run
    npx inngest-cli@latest dev
    and confirm function discovery.
  6. Send one representative event and inspect the run.
If local dev-server verification is not possible, state exactly which static checks passed and what runtime verification remains.
按置信度递增顺序运行检查:
  1. 包管理器安装/更新。
  2. 类型检查。
  3. 针对迁移函数的单元/集成测试。
  4. 使用
    INNGEST_DEV=1
    启动应用。
  5. 运行
    npx inngest-cli@latest dev
    并确认函数可被发现。
  6. 发送一个代表性事件并检查运行情况。
如果无法进行本地开发服务器验证,请明确说明通过了哪些静态检查以及剩余的运行时验证项。

Common Failure Messages

常见错误信息

  • "A signing key is required to run in Cloud mode": set
    INNGEST_DEV=1
    for local development or configure
    INNGEST_SIGNING_KEY
    for production.
  • Cls is not a constructor
    on
    /api/inngest
    : likely v3
    @inngest/realtime
    middleware in a v4 app. Remove the package and migrate to native realtime.
  • step.invoke
    fails with string function ID
    : replace strings with imported function references or
    referenceFunction()
    .
  • Type errors around schemas: replace
    EventSchemas
    with
    eventType()
    and
    staticSchema()
    .
  • Unexpected
    Promise.race
    behavior
    : use
    group.parallel()
    for first-winner step races or disable optimized parallelism only when necessary.
  • "A signing key is required to run in Cloud mode":本地开发设置
    INNGEST_DEV=1
    ,或为生产环境配置
    INNGEST_SIGNING_KEY
  • Cls is not a constructor
    /api/inngest
    :可能是v4应用中仍使用v3的
    @inngest/realtime
    中间件。移除该包并迁移至原生实时功能。
  • step.invoke
    使用字符串函数ID失败
    :将字符串替换为导入的函数引用或
    referenceFunction()
  • 模式相关类型错误:用
    eventType()
    staticSchema()
    替换
    EventSchemas
  • Promise.race
    行为不符合预期
    :对步骤竞争使用
    group.parallel()
    实现先完成即返回,或仅在必要时禁用优化并行性。

Anti-Patterns

反模式

  • Mixing v3 realtime middleware with
    inngest@4
    .
  • Passing
    signingKey
    ,
    baseUrl
    , or
    signingKeyFallback
    to
    serve()
    .
  • Moving event trigger syntax but forgetting cron/invoke-triggered functions.
  • Replacing
    EventSchemas
    with untyped string events everywhere.
  • Hardcoding
    isDev: true
    in production-bound source.
  • Leaving string IDs in
    step.invoke
    .
  • Skipping typecheck after mechanical migration.
  • inngest@4
    中混合使用v3实时中间件。
  • serve()
    传递
    signingKey
    baseUrl
    signingKeyFallback
  • 迁移了事件触发器语法,但遗漏了cron/调用触发的函数。
  • EventSchemas
    替换为无类型的字符串事件。
  • 在生产源码中硬编码
    isDev: true
  • step.invoke
    中保留字符串ID。
  • 机械迁移后跳过类型检查。