data-client-schema について
data-client-schemaは、@data-clientアプリでAPIのJSONを表す複合スキーマを定義する開発者向けスキルです。エンティティ、ユニオン、コレクション、クエリを扱い、正規化キャッシュのモデル化を説明します。
主な機能
- 01エンティティとユニオンスキーマの定義
- 02オブジェクト・リスト・マップの構成
- 03可変コレクションのモデル化
- 04メモ化クエリの作成
- 05ネストおよび部分エンティティへの対応
活用シーン
- 01複雑なAPI JSONレスポンスの表現
- 02関連データの正規化とキャッシュ管理
- 03多相データ向けスキーマの定義
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ reactive/ data-client --skill data-client-schemaインストール後、Claude Code で/data-client-schemaを入力して読み込まれたか確認します。
詳細
1. スキーマの定義
エンドポイントから返される JSON を表すスキーマを定義します。これらを組み合わせて、期待されるデータを表します。
オブジェクト
- Entity - 単一の一意なオブジェクトを表します(非正規化)
- EntityMixin - 既存のクラスを Entity にします
- new Union(Entity) - ポリモーフィックなオブジェクト(A | B)
{[key:string]: Schema}- 不変オブジェクト- new Invalidate(Entity|Union) - Entity を削除します
- new Lazy(() => Schema) - 循環インポートを回避し、深い再帰的な非正規化を遅延します
リスト
- new Collection([Schema]) - 可変/拡張可能なリスト
[Schema]- 不変リスト- new All(Entity|Union) - 種類ごとにすべての Entity を一覧表示します
マップ
new Collection(Values(Schema))- 可変/拡張可能なマップ- new Values(Schema) - 不変マップ
レンズに依存する Entity フィールド
- new Scalar({ lens, key, entity? }) - Entity を変更せずに、実行時のレンズ(ポートフォリオ、通貨、ロケール)に応じて変化するフィールド
派生/セレクターパターン
-
new Query(Queryable) - メモ化されたプログラムによるセレクター
const queryRemainingTodos = new Query( TodoResource.getList.schema, entries => entries.filter(todo => !todo.completed).length, );const groupTodoByUser = new Query( TodoResource.getList.schema, todos => Object.groupBy(todos, todo => todo.userId), );Queryの変換は、データモデル(例:src/resources/)内で定義してください。useSuspense/useQuery をラップするカスタムフック内には定義しないでください。カスタムフック内に定義すると、データの依存関係が隠れ、データロジックがビューコードに結び付いてしまいます。
2. Entity のベストプラクティス
- すべての
Entityサブクラスで、オプションではないシリアライズ対象フィールドすべてにデフォルト値を定義します。 - 主キーが
idと異なる場合に限り、pk()をオーバーライドします。 pk()の戻り値の型はnumber | string | undefinedです。Entity.process(value, parent, key, args)をオーバーライドして、args/url に基づくフィールドを挿入します。- 入れ子のスキーマやデシリアライズ関数には、
static schema(任意)を使用します。- API の設計時には、Entity をネストすることを推奨します。
3. Entity のライフサイクルメソッド
- 正規化(JSON レスポンス → キャッシュ):POJO を処理します。出力は、正規化済みキャッシュに保存される JSON シリアライズ可能なプレーンデータです。順序:
process()→pk()→ validate() → 入れ子のスキーマを訪問(schemaフィールドを再帰的に処理)→ 既存の場合はmergeWithStore()を呼び出し、shouldUpdate()、必要に応じてshouldReorder()とmerge()を呼び出します。メタデータにはmergeMetaWithStore()を使用します。 - 非正規化(キャッシュ → コンポーネント):
fromJS()を使用して Entity のクラスインスタンスを作成し、プロトタイプチェーンを復元します。これにより、ゲッター、メソッド、schemaの処理が機能します。順序:createIfValid()→ validate() →fromJS()→ 入れ子のスキーマを非訪問(schemaフィールドを再帰的に処理)
4. Union 型(ポリモーフィックスキーマ)
イベントなどのポリモーフィックなリソースを定義するには、Union と識別子フィールドを使用します。
import { Union } from '@data-client/rest'; // also available from @data-client/endpoint
export abstract class Event extends Entity {
type: EventType = 'Issue'; // discriminator field is shared
/* ... */
}
export class PullRequestEvent extends Event { /* ... */ }
export class IssuesEvent extends Event { /* ... */ }
export const EventResource = resource({
path: '/users/:login/events/public/:id',
schema: new Union(
{
PullRequestEvent,
IssuesEvent,
// ...other event types...
},
'type', // discriminator field
),
});
5. コレクション(可変リストとマップ)
コレクションは Array または Values スキーマをラップして、変更(追加/削除/移動)を可能にします。
pk のルーティング
pk() は、Entity 内にネストされていて利用可能な場合は nestKey(parent) を使用します。それ以外の場合は argsKey(...args) を使用し、結果をシリアライズします。オプションがない場合は、デフォルトで argsKey: params => ({ ...params }) が使用され、すべてのエンドポイント引数がコレクションキーになります。
argsKey— エンドポイント引数から pk を導出します(デフォルト)nestKey— ネストされた共有状態コレクションの場合に、親 Entity から pk を導出します
トップレベルとネストされた状態の両方で同じ定義を再利用するには、同じ Collection に両方を定義します。argsKey(args) と nestKey(parent) が同じオブジェクト形状を返す場合、トップレベルのフェッチとネストされた読み取りは同一の(参照同値な)配列/マップを参照します。一方での push/unshift/assign/move/remove による変更は、もう一方にも反映されます。
const userTodos = new Collection([Todo], {
argsKey: ({ userId }: { userId?: string }) => ({ userId }),
nestKey: (parent: User) => ({ userId: parent.id }),
});
nonFilterArgumentKeys
デフォルトの createCollectionFilter は、コレクションをマッチングする際に非フィルター引数を除外するために nonFilterArgumentKeys (デフォルト: 'order' で始まるキー) を使用します。これは、push/unshift/assign/move によってどの既存コレクションが新しいアイテムを受け取るかに影響します。
関数、RegExp、または string[] でオーバーライドできます:
new Collection([Todo], { nonFilterArgumentKeys: /orderBy|sortDir/ })
エクステンダー
すべて ctrl.set() (ローカルのみ) または RestEndpoint エクステンダー (ネットワーク) 経由で使用できます。
| Method | Type | Description |
|---|---|---|
push | Array | Entity |
unshift | Array | Entity |
assign | Values | エントリをマップにマージ |
remove | Both | 一致するコレクションから値でアイテムを削除 |
move | Both | 既存の状態に一致するコレクションから削除し、新しい状態に一致するコレクションに追加 |
addWith(merge, filter?) | Both | カスタム作成スキーマ (push/unshift/assign が内部的に使用) |
moveWith(merge) | Both | カスタム移動スキーマ (挿入順序を制御、例: 先頭に追加するための unshift マージ) |
6. 補助エンドポイント (既存エンティティを拡充)
すでにキャッシュにあるエンティティに対して、エンドポイントが部分的または異なる形のデータを返す場合 (例: メタデータエンドポイント、統計エンドポイント、遅延読み込み拡張エンドポイント)、 スキーマとして同じ Entity を使用してください — ラッパーエンティティを作成しないでください。
パターンと例については partial-entities を参照してください。
7. ベストプラクティスと注意事項
- 正規化のために、すべてのリソース/エンティティ/コレクションで必ず
schemaを設定してください - 適切なスキーマを定義して、深くネストされたデータやリレーショナルデータを正規化してください
- クライアントサイドの結合には
Entity.schemaを使用してください - InstanceType<> の代わりに rest/endpoint/graphql の
Denormalize<>型を使用してください。これは Entity だけでなく、Union などのすべてのスキーマを処理します。
8. 避けるべきよくある間違い
- 正規化キャッシュはプレーンな JSON シリアライズ可能なオブジェクト (POJO) を格納し、クラスインスタンスは格納しません。
- クラスフィールドに
fromJS()を使用するか、デフォルトプロパティを割り当てるのを忘れないでください — 素の TS フィールド型はランタイムのデフォルトを生成しないため、スキーマ推論が壊れます - 手動でデータをマージまたは「拡充」する。代わりにクライアントサイドの結合には
Entity.schemaを使用してください
リファレンス
詳細な API ドキュメントについては、references ディレクトリを参照してください:
- Entity - 正規化データクラス
- EntityMixin - 任意のクラスを Entity に変換
- Collection - ミュータブル/拡張可能なリスト
- Union - ポリモーフィックスキーマ
- Query - プログラムによるセレクター
- Invalidate - エンティティを削除
- Lazy - 遅延 / 循環スキーマ
- Scalar - Lens に依存するエンティティフィールド
- Values - マップスキーマ
- All - ある種類のすべてのエンティティを一覧表示
- Array - イミュータブルリストスキーマ
- Object - オブジェクトスキーマ
- schema - スキーマの概要
- relational-data - リレーショナルデータガイド
- computed-properties - 計算プロパティガイド
- partial-entities - 部分エンティティガイド
- side-effects - 副作用ガイド
- sorting-client-side - クライアントサイドソートガイド