概要

Cloudflare Workers で Env 型を手書きしていると、wrangler.jsonc のバインディングを増やすたびに worker/types.tsEnv も手で同期する作業が発生します。wrangler types で生成される worker-configuration.d.ts を取り込めば、バインディング型は自動同期されます。さらに、用途別に Env から派生型を切ると、ハンドラごとの「DB が確実にある」「テスト用に部分的に与える」といった事情を型で表現できます。

本記事は、wrangler types の組み込み + Cloudflare.Env を起点にした派生型設計のテンプレートです。

全体像

wrangler.jsonc                  ← バインディング定義(D1, KV, Queue, Hyperdrive 等)
   ↓ wrangler types
worker-configuration.d.ts       ← `declare namespace Cloudflare { interface Env { ... } }`
   ↓ 派生
worker/types.ts
  - Env       = Cloudflare.Env + オプショナルなシークレット
  - RuntimeEnv = Env + 確実にあるバインディング
  - TestEnv   = Partial<RuntimeEnv>

手順

1. wrangler types の実行と取り込み

npx wrangler types

リポジトリ直下に worker-configuration.d.ts が生成されます。Cloudflare.Env namespace に wrangler.jsonc のバインディングが反映されます。

// worker-configuration.d.ts (生成物の概略)
declare namespace Cloudflare {
  interface Env {
    DB: D1Database
    HYPERDRIVE: Hyperdrive
    NOTIFICATION_QUEUE: Queue
    SHARE_KV: KVNamespace
    SUPABASE_URL: string
    // ...
  }
}

2. tsconfig に include + ESLint で ignore

// tsconfig.worker.json
{
  "compilerOptions": { /* ... */ },
- "include": ["worker/**/*.ts", "shared/**/*.ts"],
+ "include": ["worker/**/*.ts", "shared/**/*.ts", "worker-configuration.d.ts"],
  "exclude": ["worker/**/*.test.ts", /* ... */]
}
// eslint.config.js
export default defineConfig([
- globalIgnores(['dist/**', 'tmp/**', '.claude/worktrees/**']),
+ globalIgnores(['dist/**', 'tmp/**', '.claude/worktrees/**', 'worker-configuration.d.ts']),
  // ...
])

生成物なので ESLint には触らせません。tsconfig には include して Cloudflare.Env を解決可能にします。

3. Env 型を Cloudflare.Env から派生

// worker/types.ts
import type { QueueMessage } from '@worker/queue-messages'

// Cloudflare.Env (from worker-configuration.d.ts) covers wrangler.jsonc
// bindings. Secrets that may not be set in all environments stay optional.
// Queue<QueueMessage> で payload 型を上書き。
// DB はテスト注入用のスロット(runtime-db が代入する)。
export type Env = Omit<Cloudflare.Env, 'NOTIFICATION_QUEUE'> & {
  NOTIFICATION_QUEUE?: Queue<QueueMessage>
  DB?: AppDatabase
  // 追加のオプショナル secrets
  RECAPTCHA_SITE_KEY?: string
  RECAPTCHA_SECRET_KEY?: string
  STRIPE_SECRET_KEY?: string
  STRIPE_WEBHOOK_SECRET?: string
}

ポイント:

  • Omit<Cloudflare.Env, 'NOTIFICATION_QUEUE'> で生成物の型を一旦取り去って、payload 型 (QueueMessage) で上書きします。生成物の Queue は型パラメータが空なので、自前のメッセージ型で型付け直します
  • ローカル/プレビュー環境で未設定でも動かしたいシークレットは ? でオプショナルに
  • DB を Cloudflare.Env では required にしておきつつ、こちら側で ? にして「テスト時のみ注入」を表現します

4. Runtime/Test 用に派生型を切る

// scheduled/queue ハンドラのように runtime-db 解決後にしか走らないコード用
export type RuntimeEnv = Env & { DB: AppDatabase }

// バインディングの一部だけ与えたい単体テスト用
export type TestEnv = Partial<RuntimeEnv>

派生の目的:

  • RuntimeEnv を使うハンドラは env.DB?. で守らなくて済みます
  • TestEnv は不要なバインディングを毎回モックしなくて良いです(必要な分だけ与えます)
// 使用例
import type { Env, RuntimeEnv, TestEnv } from '@worker/types'

export default {
  fetch(req: Request, env: Env) { /* ... */ },           // 一般ハンドラは Env
  scheduled(event: ScheduledEvent, env: RuntimeEnv) { /* env.DB が確実にある */ },
  queue(batch: MessageBatch, env: RuntimeEnv) { /* 同上 */ },
}

// テスト
const env: TestEnv = { DB: makeFakeDb(), SUPABASE_URL: 'http://localhost:54321' }

学び・気づき

  • 生成物 (.d.ts) は手で書かない、include する: wrangler types が出すファイルは「ジェネレートしたら触らない」原則を徹底します。tsconfig で include + ESLint で ignore がセット
  • Omit<X, K> & { K: NewType } で型の上書き: 生成物の型を「外して新しい型で足し直す」イディオムは Cloudflare 型定義に限らず汎用的に有用です(Queue → Queue 等)
  • 同じ Env でも文脈で派生型を切る: fetch ハンドラは Envscheduled/queueRuntimeEnv(DB が確実にある)、テストは TestEnv(部分的)。同じバインディング集合でも前提条件が違う場合は型を分けると assertion/optional chain が減って読みやすいです
  • シークレットの optional/required は環境ごとの実情に合わせる: 生成物は wrangler.jsonc に書いた variable は required になるが、実際は preview / local で未設定のことが多い → アプリ側の Env でだけ optional に弱めます。実行時には assertEnv() 的なヘルパーで存在を保証します
  • pre-push フックで typecheck + lint + test を全部回す: Env 型を変えると影響範囲が広いので、push 前に npm run typecheck/lint/test をまとめて走らせる pre-push を仕込んでおくと、レビュー前に壊れないコードしか出ません
  • eslint config の globalIgnores: ESLint v9 の defineConfig + globalIgnores で生成物を一括無視。flat config 移行と相性が良いです

アンチパターン

  • Env を手書き + wrangler.jsonc を手で同期: バインディング追加のたびに型を書き換えることになり、PR レビューでも「同期忘れ」が頻発します
  • 生成物 .d.ts を git ignore: CI で wrangler types を毎回回せば成立するが、ローカル開発で IDE の型解決が遅くなります。コミットしてしまった方が IDE 体験が良いです(PR レビュー時のノイズは多くなる tradeoff)
  • as Env でキャスト: バインディング追加忘れを型で検出できなくなります

次にやりたいこと

  • worker-configuration.d.ts の自動再生成を pre-commit で回します(wrangler.jsonc 変更時のみ)
  • Cloudflare.Env から CI で型 diff を生成し、PR で Reviewer が把握しやすくします
  • assertEnv() 系のランタイムバリデータを zod で書き、optional を「明示的に存在を確認したら required になる」流れにします

まとめ

Cloudflare Workers で型が散らかってきたら、wrangler types 起点の Cloudflare.Env 派生に倒すと一気に楽になります。生成物 → 一元 EnvRuntimeEnv / TestEnv の3階層で「文脈ごとに前提条件を型で表現」します。