type-fest について
type-festは、TypeScript開発者向けの180種類以上のコンパイル時ユーティリティ型集です。実行時の負荷を加えず、標準型だけでは難しい複雑な変換や厳密な制約を表現できます。
主な機能
- 01180種類以上のコンパイル時ユーティリティ型
- 02深いオプショナル型と読み取り専用型への変換
- 03オブジェクトのマージと厳密なキー選択
- 04JSON型とケース変換型
- 05パス型と非同期戻り値の抽出
活用シーン
- 01APIリクエストとレスポンス型の変換
- 02ネストした設定更新の型定義
- 03状態への深い不変性の適用
- 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→stringMap、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
リソース
- GitHub: https://github.com/sindresorhus/type-fest
- ドキュメント: https://sindresorhus.com/type-fest
- 型の全リスト:
/sourceディレクトリを参照 - 例: 使用パターンについてはテストファイルを確認
- LLM ガイド: https://context7.com/sindresorhus/type-fest/llms.txt