mcpField
T
type-fest

type-fest

github.com/izidorome開発0151 回インストール40 週間前に更新
リポジトリ

type-fest について

type-festは、TypeScript開発者向けの180種類以上のコンパイル時ユーティリティ型集です。実行時の負荷を加えず、標準型だけでは難しい複雑な変換や厳密な制約を表現できます。

主な機能

  1. 01180種類以上のコンパイル時ユーティリティ型
  2. 02深いオプショナル型と読み取り専用型への変換
  3. 03オブジェクトのマージと厳密なキー選択
  4. 04JSON型とケース変換型
  5. 05パス型と非同期戻り値の抽出

活用シーン

  1. 01APIリクエストとレスポンス型の変換
  2. 02ネストした設定更新の型定義
  3. 03状態への深い不変性の適用
  4. 04JSONシリアライズ結果の型付け

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/izidorome/skills --skill type-fest

インストール後、Claude Code で/type-festを入力して読み込まれたか確認します。

詳細

type-fest:高度な TypeScript ユーティリティ型

type-fest は、本番環境の TypeScript アプリケーション向けに、実行時オーバーヘッドなしで 180 種類以上のコンパイル時ユーティリティ型を提供します。組み込みの TypeScript ユーティリティでは厳密な型操作、深い変換、複雑な型レベルプログラミングに対応できない場合に使用してください。

このスキルを使う場面

次の場合に有効化してください:

  • API のレスポンス/リクエスト型の変換(snake_case ↔ camelCase)
  • 設定の深いマージと部分更新
  • readonly による保証を備えた不変状態管理
  • JSON シリアライズの型安全性(Date → string など)
  • 厳密なチェックを伴うオブジェクトプロパティの削除/選択
  • 型安全なパスアクセスとプロパティ選択
  • 非同期関数の戻り値型の抽出
  • 条件付き型操作

次の場合は使用しないでください:

  • 実行時のバリデーション(zod/yup/io-ts を使用してください。type-fest はコンパイル時のみです)
  • 組み込みの Partial、Pick、Omit で対応できる単純なケース
  • 実行時の型ガード(type-fest を実行時チェックと併用してください)

インストール

npm install type-fest

最適なツリーシェイキングのため、必要なものだけを個別にインポートしてください:

import type {Except, Merge, PartialDeep, Jsonify} from 'type-fest';

基本的なユーティリティ

Except<T, K> - プロパティの厳密な削除

使用場面: コンパイル時の検証を行いながら、オブジェクトからキーを削除する場合。

Omit に対する利点: タイプミスを検出し、削除するキーが実際に存在することを要求します。

import type {Except} from 'type-fest';

interface User {
  id: number;
  email: string;
  password: string;
  role: string;
}

// Remove sensitive fields for public API
type PublicUser = Except<User, 'password'>;
// Result: { id: number; email: string; role: string }

// Catches typos
type Bad = Except<User, 'pasword'>; // ✗ Error: 'pasword' doesn't exist

// Strict mode prevents re-adding omitted props
type StrictPublic = Except<User, 'password', {requireExactProps: true}>;
Merge<T, U> - 型安全なオブジェクトのマージ

使用場面: 2 つ目の型のプロパティで 1 つ目の型のプロパティを上書きしながら、型を結合する場合。

import type {Merge} from 'type-fest';

interface BaseConfig {
  host: string;
  port: number;
  timeout: number;
}

interface ProdOverrides {
  port: 3306;  // Literal type
  ssl: boolean;
  poolSize: number;
}

type ProdConfig = Merge<BaseConfig, ProdOverrides>;
// Result: { host: string; port: 3306; timeout: number; ssl: boolean; poolSize: number }
PartialDeep<T> - 再帰的なオプショナルプロパティ

使用場面: 深い階層を持つ設定オブジェクトや、ネストされた部分更新。

import type {PartialDeep} from 'type-fest';

interface AppSettings {
  theme: {
    colors: {
      primary: string;
      secondary: string;
    };
    fontSize: number;
  };
  features: {
    autoSave: boolean;
  };
}

// User can override any nested property
function updateSettings(updates: PartialDeep<AppSettings>) {
  // updates.theme?.colors?.primary is allowed
}

// Valid calls
updateSettings({theme: {colors: {primary: '#ff0000'}}});
updateSettings({features: {autoSave: false}});
updateSettings({theme: {fontSize: 16}});
Jsonify<T> - JSON シリアライズの型

使用場面: JSON シリアライズされる API のレスポンス/リクエストを型付けする場合。

処理内容:

  • Date → string
  • Map、Set → {}
  • 関数、undefined → 削除
  • ネストされた型を再帰的に変換
import type {Jsonify} from 'type-fest';

interface Activity {
  userId: number;
  timestamp: Date;
  metadata: Map<string, string>;
  handler: () => void;
}

type ApiActivity = Jsonify<Activity>;
// Result: { userId: number; timestamp: string; metadata: {} }
// (handler and Map removed, Date → string)

async function fetchActivity(id: string): Promise<Activity> {
  const response = await fetch(`/api/activity/${id}`);
  const json: ApiActivity = await response.json();
  
  // Reconstruct proper types
  return {
    userId: json.userId,
    timestamp: new Date(json.timestamp), // string → Date
    metadata: new Map(),
    handler: () => console.log('loaded')
  };
}
ReadonlyDeep<T> - 深い階層の不変性

使用場面: 状態管理や設定オブジェクトで不変性を強制する場合。

import type {ReadonlyDeep} from 'type-fest';

interface State {
  users: Array<{
    id: number;
    profile: {name: string};
  }>;
}

type ImmutableState = ReadonlyDeep<State>;

const state: ImmutableState = {users: [{id: 1, profile: {name: 'John'}}]};

// All modification attempts cause errors
state.users.push(...); // ✗ Error
state.users[0].profile.name = 'Jane'; // ✗ Error
CamelCase<T> - 大文字/小文字の変換

使用場面: API やデータベースの命名規則を JavaScript の慣習に合わせて変換する場合。

import type {CamelCase} from 'type-fest';

interface DbUser {
  'user-id': number;
  'email_address': string;
  'created_at': string;
}

type AppUser = {
  [K in keyof DbUser as CamelCase<K>]: DbUser[K]
};
// Result: { userId: number; emailAddress: string; createdAt: string }

// Also available: SnakeCase, KebabCase, PascalCase
LiteralUnion<T, U> - オートコンプリートを維持するユニオン型

使用場面: IDE のオートコンプリートを維持しながら、拡張可能な文字列列挙型を作成する場合。

解決する問題: 'red' | 'blue' | string とすると、'red' と 'blue' のオートコンプリートが失われます。

import type {LiteralUnion} from 'type-fest';

// Without LiteralUnion - no autocomplete
type BadColor = 'red' | 'blue' | 'green' | string;

// With LiteralUnion - IDE autocompletes known values
type Color = LiteralUnion<'red' | 'blue' | 'green', string>;

function setTheme(color: Color) {
  // IDE suggests 'red', 'blue', 'green'
  // But also accepts '#FF5733', 'custom-color', etc.
}
AsyncReturnType<T> - Promise 型のアンラップ

使用場面: Promise を手動でアンラップせずに、非同期関数の結果を型付けする場合。

import type {AsyncReturnType} from 'type-fest';

async function fetchUser(id: number) {
  const res = await fetch(`/api/users/${id}`);
  const data = await res.json();
  return {
    id,
    name: data.name,
    email: data.email,
    roles: data.roles as string[]
  };
}

type User = AsyncReturnType<typeof fetchUser>;
// Result: { id: number; name: any; email: any; roles: string[] }

// Use in callbacks
function processUsers(users: User[]) {
  users.forEach(u => console.log(u.name));
}
RequireAtLeastOne<T, K> - 条件付き必須プロパティ

使用場面: 「これらのフィールドのうち、少なくとも 1 つが存在すること」を強制する場合。

import type {RequireAtLeastOne} from 'type-fest';

interface SearchParams {
  query?: string;
  userId?: number;
  email?: string;
  tags?: string[];
}

type ValidSearch = RequireAtLeastOne<SearchParams, 'query' | 'userId' | 'email'>;

function search(params: ValidSearch) {
  // At least one of: query, userId, or email is guaranteed
}

search({query: 'test'}); // ✓
search({userId: 123, tags: ['tag']}); // ✓
search({tags: ['tag']}); // ✗ Error: need query, userId, or email
ConditionalPick<T, Condition> - 型による選択

使用場面: 特定の型に一致するプロパティを抽出する場合。

import type {ConditionalPick} from 'type-fest';

interface Endpoint {
  path: string;
  method: 'GET' | 'POST';
  timeout: number;
  retries: number;
  handler: (req: any) => Promise<any>;
  validate: (data: any) => boolean;
}

// Extract only functions
type EndpointFns = ConditionalPick<Endpoint, Function>;
// Result: { handler: ...; validate: ... }

// Extract only numbers
type EndpointNums = ConditionalPick<Endpoint, number>;
// Result: { timeout: number; retries: number }
Paths<T> - 型安全なプロパティパス

使用場面: lodash のような型安全な get/set ユーティリティを構築する場合。

import type {Paths} from 'type-fest';

interface Config {
  database: {
    host: string;
    credentials: {
      user: string;
      password: string;
    };
  };
  api: {endpoint: string};
}

type ConfigPath = Paths<Config>;
// Union: 'database' | 'database.host' | 'database.credentials' | 
//        'database.credentials.user' | 'database.credentials.password' | 
//        'api' | 'api.endpoint'

function get<P extends ConfigPath>(config: Config, path: P): any {
  return path.split('.').reduce((obj: any, key) => obj?.[key], config);
}

get(config, 'database.credentials.user'); // ✓ Autocompletes
get(config, 'invalid.path'); // ✗ Type error
Simplify<T> - 交差型のフラット化

使用場面: 合成した型の IDE ヒントや型の代入可能性を改善する場合。

import type {Simplify} from 'type-fest';

type Position = {x: number; y: number};
type Size = {width: number; height: number};
type Styles = {color: string; opacity: number};

// Without Simplify - IDE shows: Position & Size & Styles
type Complex = Position & Size & Styles;

// With Simplify - IDE shows flat object
type Simple = Simplify<Position & Size & Styles>;
// Result: { x: number; y: number; width: number; height: number; color: string; opacity: number }

よくあるパターン

型変換を行う API クライアント
import type {Except, Jsonify, CamelCase, AsyncReturnType, ReadonlyDeep} from 'type-fest';

// External API (snake_case)
interface ApiUser {
  user_id: number;
  email_address: string;
  created_at: string;
}

// Internal app (camelCase)
type AppUser = {
  [K in keyof ApiUser as CamelCase<K>]: ApiUser[K]
};

// Public user (no sensitive data)
type PublicUser = Except<AppUser, 'emailAddress'>;

// Immutable state
type UserState = ReadonlyDeep<AppUser>;

class UserService {
  async getUser(id: number): Promise<AppUser> {
    const res = await fetch(`/api/users/${id}`);
    const data: Jsonify<ApiUser> = await res.json();
    
    return {
      userId: data.user_id,
      emailAddress: data.email_address,
      createdAt: data.created_at
    };
  }
}

type User = AsyncReturnType<UserService['getUser']>;
設定管理
import type {PartialDeep, Merge, Simplify} from 'type-fest';

interface BaseConfig {
  database: {host: string; port: number};
  api: {timeout: number};
}

interface EnvOverrides {
  database: {port: 3306};
  api: {retries: number};
}

// Merge and flatten
type FinalConfig = Simplify<Merge<BaseConfig, EnvOverrides>>;

function loadConfig(
  defaults: BaseConfig,
  overrides: PartialDeep<EnvOverrides>
): FinalConfig {
  // Deep merge logic
  return {...defaults, ...overrides} as FinalConfig;
}
型安全なビルダー
import type {RequireAtLeastOne, Merge} from 'type-fest';

interface BaseRequest {
  timeout?: number;
  headers?: Record<string, string>;
}

interface RequiredFields {
  url: string;
}

type HttpRequest = RequireAtLeastOne<
  Merge<RequiredFields, BaseRequest>,
  'url'
>;

class HttpClient {
  request(config: HttpRequest) {
    // config.url guaranteed to exist
    fetch(config.url, {
      method: 'GET',
      headers: config.headers
    });
  }
}

組み合わせ方

ユーティリティを組み合わせて、強力な型変換を実現します:

import type {Merge, Simplify, PartialDeep, Except} from 'type-fest';

// Base + overrides + flatten
type CleanMerge<T, U> = Simplify<Merge<T, U>>;

// Remove sensitive + deep partial
type SafePartial<T> = PartialDeep<Except<T, 'password' | 'secret'>>;

// Chain multiple operations
type ComplexType = Simplify<
  Merge<
    Merge<BaseType, OverrideType>,
    FinalType
  >
>;

注意すべき点

1. 実行時とコンパイル時

重要: type-fest の型は、実行時には検証を行いません。

// DON'T assume runtime safety
function process(data: Jsonify<MyType>) {
  const date = new Date(data.timestamp); // Could fail if data is malformed
}

// DO add runtime validation
function process(data: Jsonify<MyType>) {
  if (typeof data.timestamp !== 'string') {
    throw new Error('Invalid timestamp');
  }
  const date = new Date(data.timestamp);
}
2. 深い型のネストの上限

PartialDeep/ReadonlyDeep の過度なネストは避けてください(最大 3~4 階層):

// DON'T
type Bad = PartialDeep<PartialDeep<DeepType>>;

// DO
type Good = PartialDeep<DeepType>;
3. 必要なものだけをインポートする

個別にインポートすると、ツリーシェイキングが最も効果的に機能します:

// DO
import type {Except, Merge} from 'type-fest';

// DON'T
import type * as TypeFest from 'type-fest';
4. LiteralUnion の適用範囲

すでに狭い型には使用しないでください:

// DON'T
type Bad = LiteralUnion<'active', 'active' | 'inactive'>;

// DO
type Good = LiteralUnion<'active' | 'inactive', string>;

クイックリファレンス

必要な機能type-fest ユーティリティ代替手段
キーを削除(厳密)Except<T, K>Omit<T, K>(寛容)
オブジェクトをマージMerge<T, U>T & U(交差型)
ディープな部分型PartialDeep<T>Partial<T>(浅い)
ディープな読み取り専用型ReadonlyDeep<T>Readonly<T>(浅い)
JSON セーフな型Jsonify<T>手動で変換
大文字・小文字の変換CamelCase<T>手動でマッピング
拡張可能な列挙型LiteralUnion<T, U>T | U(オートコンプリートなし)
Promise のラップを解除AsyncReturnType<T>手動で抽出
一部のキーを必須にするRequireAtLeastOne<T, K>カスタム条件型
型で選択ConditionalPick<T, C>カスタムマップ型
型のパスPaths<T>文字列リテラル
型をフラット化Simplify<T>交差型

追加のユーティリティ

こちらも利用できます(全180以上):

  • RequireExactlyOne - ちょうど1つのキーを必須にする
  • SetRequired、SetOptional - 個々のプロパティを変更する
  • SnakeCase、KebabCase、PascalCase - その他の大文字・小文字変換
  • IsEqual<T, U> - 型の等価性をチェックする
  • Get<T, Path> - 型安全なプロパティアクセス
  • Opaque<T> - ブランド型
  • PromiseValue<T> - Promise の値を抽出する
  • さらに170以上の特化したユーティリティ

全リストを見る:https://github.com/sindresorhus/type-fest

リソース

コメント