mcpField
F
feature-systems-pattern

feature-systems-pattern

github.com/marcioaltoe開発049 回インストール1 日前に更新
リポジトリ

feature-systems-pattern について

feature-systems-pattern は、TypeScript プロジェクトでドメイン単位のモジュールを構成する開発スキルです。API、クエリ層、フック、コンポーネント、公開エクスポートの設計と、依存関係やキャッシュの規則を案内します。

主な機能

  1. 01ドメイン別のシステムディレクトリ構成
  2. 02型付き API アダプターとエラー
  3. 03TanStack Query のキーとオプション
  4. 04フック、ミューテーション、楽観的更新
  5. 05Context と XState ストアのパターン
  6. 06依存方向とキャッシュ範囲の規則

活用シーン

  1. 01新しいドメインモジュールの構成
  2. 02クエリとミューテーションフックの追加
  3. 03楽観的キャッシュ更新の処理
  4. 04システム境界とエクスポートの確認

インストール

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

$npx skills add https://github.com/marcioaltoe/skills --skill feature-systems-pattern

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

同梱ファイルreferences/directory-layout.mdreferences/patterns.md

詳細

機能システムガイド

「システム」とは、1つのドメインに関連するすべての要素(API 呼び出し、クエリレイヤー、フック、コンポーネント、公開 API)を管理する、自己完結したドメイン駆動型のモジュールです。システムは systems/<domain>/ ディレクトリの下に配置します。

ディレクトリ構造と命名規則の全容については、references/directory-layout.md をお読みください。 各レイヤーの注釈付き実装パターンについては、references/patterns.md をお読みください。

クイックリファレンス

必須の併用スキル

システムは複数の技術領域にまたがるため、このスキルと併せて有効にしてください。

状況有効にするスキル
フックまたはコンポーネント全般react + tanstack-query
データの取得/キャッシュtanstack-query
ミューテーションtanstack-query
XState ストアxstate
ユーティリティ関数es-toolkit
テストの作成/修正testing-boss + vitest
バグ修正systematic-debugging + no-workarounds
システムディレクトリの概要
systems/<domain>/
├── index.ts               # Public API barrel — required for every system
├── types.ts               # TypeScript types for this domain
├── adapters/              # API service layer (HTTP calls, error types)
│   └── <domain>-api.ts
├── lib/                   # Pure utilities, schemas, constants, query keys
│   ├── query-keys.ts      # TanStack Query key factory
│   ├── query-options.ts   # Reusable queryOptions / mutationOptions
│   ├── <domain>-schemas.ts
│   └── constants.ts
├── hooks/                 # React hooks (queries, mutations, view-models)
│   ├── __tests__/
│   ├── use-<action>.ts    # Query hooks
│   ├── use-create-<entity>.ts  # Mutation hooks
│   ├── use-update-<entity>.ts
│   ├── use-delete-<entity>.ts
│   └── use-<domain>-view-model.ts
├── contexts/              # React contexts + providers
│   └── <domain>-context.tsx
├── stores/                # XState stores (complex async state machines)
│   └── <domain>-store.ts
├── components/            # React UI components
│   ├── stories/
│   └── index.ts
└── guards/                # Route guards / access checks

新しいシステムの作成手順

ステップ 1 — types.ts を定義する
  • ドメインの型を明確にエクスポートし、生の API レスポンス形式は公開しないでください。
  • 利用可能な場合は、プロジェクトの API コントラクト型から導出してください。
  • 複雑な集約型には、導出ルールと不変条件を説明する JSDoc を記述してください。
ステップ 2 — API サービスレイヤーを構築する
  • adapters/<domain>-api.ts を作成します。
  • API 呼び出しには、プロジェクトの HTTP クライアントを使用します。
  • 単一の名前空間オブジェクトをエクスポートします。例:export const <domain>Api = { list, create, update, delete }。
  • 型付きエラークラスをエクスポートします。例:export class <Domain>ApiError extends Error { ... }。
  • クエリのキャンセルに対応できるよう、すべての関数で signal?: AbortSignal を受け取ります。
  • エラーの抽出やレスポンスの正規化など、内部ヘルパーはすべてモジュール内に限定します。
ステップ 3 — lib/query-keys.ts を追加する
export const <domain>Keys = {
  all: ["<domain>"] as const,
  lists: () => [...<domain>Keys.all, "list"] as const,
  list: (scopeId: string | null) => [...<domain>Keys.lists(), scopeId] as const,
  details: () => [...<domain>Keys.all, "detail"] as const,
  detail: (id: string) => [...<domain>Keys.details(), id] as const,
};
  • 細かな無効化ができるよう、階層構造のキーを使用します。
  • キャッシュを正しく分離できる任意の識別子(userId、orgId など)でキーのスコープを設定します。
  • 各キーのタプルには必ず as const を指定します。
ステップ 4 — lib/query-options.ts を追加する
import { queryOptions } from "@tanstack/react-query";
import { <domain>Api } from "../adapters/<domain>-api";
import { <domain>Keys } from "./query-keys";

export function <domain>ListOptions(scopeId: string | null) {
  return queryOptions({
    queryKey: <domain>Keys.list(scopeId),
    queryFn: ({ signal }) => <domain>Api.list(scopeId!, signal),
    staleTime: 60_000,
    enabled: Boolean(scopeId),
  });
}

export function <domain>DetailOptions(id: string) {
  return queryOptions({
    queryKey: <domain>Keys.detail(id),
    queryFn: ({ signal }) => <domain>Api.get(id, signal),
    enabled: Boolean(id),
  });
}
  • 型安全性と再利用性を確保するため、queryOptions を使って queryKey と queryFn をまとめます。
  • フック、プリフェッチ、ルートローダーで使用できるよう、各オプションファクトリーをエクスポートします。
  • クエリコンテキストから受け取った signal は、必ず API レイヤーに渡します。
ステップ 5 — フックを作成する
  • クエリフック:queryOptions ファクトリーで useQuery をラップし、スコープ ID と任意の { enabled? } を受け取ります。
  • ミューテーションフック:適切な onMutate/onError/onSettled コールバックを設定した useMutation を使い、楽観的更新を実装します。
  • ビューモデルフック:複数のフックを組み合わせてページ/シェルコンポーネント向けの処理を行い、フラットなオブジェクトを返します。
  • テストは hooks/__tests__/ に配置するか、use-xxx.test.tsx として対象の近くに配置します。

ミューテーションと楽観的更新の完全なパターンについては、references/patterns.md をお読みください。

ステップ 6 — (任意)コンテキストを追加する

クエリデータや組み合わせた状態を、プロパティのバケツリレーなしでコンポーネントツリー内で共有する必要がある場合は、contexts/<domain>-context.tsx を作成します。

// Always nullable context — consumer hook throws if used outside provider
export const <Domain>Context = createContext<<Domain>ContextValue | null>(null);
  • コンテキスト、プロバイダーコンポーネント、再エクスポートするコンシューマーフックを同じファイルからエクスポートします。
  • パフォーマンスが重要なツリーでは、Core/UI/Operations のサブコンテキストに分割します。
ステップ 7 — (任意)XState ストアを追加する

複雑な非同期ステートマシン(複数ステップのフロー、ポーリング、イベント発行)には、stores/<domain>-store.ts を作成します。

export const <domain>Store = createStore({
  context: { ... } as <Domain>Context,
  emits: { ... },
  on: {
    someEvent: (context, event, enqueue) => {
      enqueue.effect(async () => { ... });
      return { ...context, isLoading: true };
    },
  },
});
ステップ 8 — index.ts を接続する

ラベル付きのセクションと明示的な名前付きエクスポートを使って、バレルを整理します。

// Types
export type { <Domain>Type } from "./types";

// Hooks
export { use<Domain>List, use<Domain>Detail } from "./hooks";
export { useCreate<Domain>, useUpdate<Domain>, useDelete<Domain> } from "./hooks";

// Components
export { <Domain>Component } from "./components";

// Utilities
export { <domain>HelperFn } from "./lib/<domain>-utils";

// Query Keys & Options
export { <domain>Keys } from "./lib/query-keys";
export { <domain>ListOptions, <domain>DetailOptions } from "./lib/query-options";

// API
export { <domain>Api, <Domain>ApiError } from "./adapters/<domain>-api";

重要なルール

  1. 同じ場所にまとめるには queryOptions を使用します。 再利用可能なオプションファクトリーに queryKey と queryFn をまとめます。同じクエリーキーを複数のファイルに分散させないでください。
  2. 依存関係は一方向に流します。 adapters -> lib -> hooks -> components。アダプターからフックやコンポーネントをインポートしてはいけません。
  3. クエリーキーのスコープを設定します。 認証済みスコープ(ユーザー、組織、テナント)に依存するクエリーには、そのスコープ ID をキーに含め、スコープをまたいで古いデータが使われないようにします。
  4. API レイヤーでは型付きエラーを使用します。 アダプターから生のエラーをスローしてはいけません。コンシューマーがエラーメッセージの文字列を調べなくてもエラーの種類を判別できるよう、型付きエラークラスを使用します。
  5. AbortSignal を伝播させます。 クエリーを適切にキャンセルできるよう、queryFn のコンテキストから渡される signal をすべての API 呼び出しに渡します。
  6. ミューテーション後は必ず無効化します。 サーバーとの結果整合性を確保するため、onSettled で queryClient.invalidateQueries を使用します。
  7. 楽観的更新にはロールバックが必要です。 キャッシュベースの楽観的更新を行う場合は、onMutate で以前のデータをスナップショットとして保存し、onError で復元します。
  8. 楽観的更新の前に、実行中のクエリーをキャンセルします。 再フェッチによって楽観的な状態が上書きされないよう、onMutate で queryClient.cancelQueries を呼び出します。
  9. Zod スキーマは lib/ に配置します。 API 境界で実行時検証を行うため、すべての Zod スキーマを lib/<domain>-schemas.ts に配置します。

エラー処理

  • API レイヤーが型付きエラーをスローします:TanStack Query がエラーを捕捉し、query.error を通じて公開します。
  • 楽観的更新を伴うミューテーションが失敗します:onError コールバックが、onMutate で取得したスナップショットを使ってキャッシュをロールバックします。その後、onSettled がキャッシュを無効化し、最新のデータを再取得します。
  • スコープをまたいで古いデータが使われます:スコープ ID をクエリーキーに追加し、enabled の条件で Boolean(scopeId) が確認されていることを確かめます。
  • アンマウント時にクエリーがキャンセルされます:コンポーネントがアンマウントされると、TanStack Query は signal を使って実行中のクエリーを自動的にキャンセルします。signal が API レイヤーまで伝播されていることを確認してください。

詳細なリファレンス

  • references/directory-layout.md — ディレクトリー構成、ファイル命名規則、バレルに関する規約の全容
  • references/patterns.md — API レイヤー、クエリーオプション、フック、ミューテーション、楽観的更新、コンテキスト、ストアに関するコードパターンの解説

コメント