mcpField
S
single-or-array-pattern

single-or-array-pattern

github.com/epicenterhq開発4.8k63 回インストール2 週間前に更新
リポジトリ

single-or-array-pattern について

単一項目と配列の入力を関数の入口で正規化し、共通の処理経路で扱う TypeScript API 設計ガイドです。CRUD、バッチ API、依存関係を受け取るファクトリー関数に適用できます。

主な機能

  1. 01単一項目または配列の受け入れ
  2. 02関数入口での入力正規化
  3. 03共通経路での全項目処理
  4. 04CRUD とバッチ処理の例
  5. 05適用場面とアンチパターンの提示

活用シーン

  1. 01単一・一括操作に対応する API の設計
  2. 02ファクトリー関数で単一または複数の依存関係を受け入れる
  3. 03レコード作成や削除の処理を統一

インストール

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

$npx skills add https://github.com/epicenterhq/epicenter --skill single-or-array-pattern

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

詳細

単一または配列のパターン

単一の項目と配列の両方を受け付け、最初に正規化してから統一的に処理します。

クイックリファレンス

function create(itemOrItems: T | T[]): Promise<Result<void, E>> {
	const items = Array.isArray(itemOrItems) ? itemOrItems : [itemOrItems];
	// ... implementation works on items array
}

構造

  1. T | T[] を受け付けるパラメーター型にします
  2. 関数の先頭で Array.isArray() を使って正規化します
  3. すべてのロジックを配列に対して動作させます。コードパスは1つです
function createServer(clientOrClients: Client | Client[], options?: Options) {
	const clients = Array.isArray(clientOrClients)
		? clientOrClients
		: [clientOrClients];

	// All real logic here, working with the array
	for (const client of clients) {
		// ...
	}
}

命名規則

パラメーター正規化後の変数
recordingOrRecordingsrecordings
clientOrClientsclients
itemOrItemsitems
paramsOrParamsArrayparamsArray

使用する場面

適しているケース:

  • CRUD 操作(作成、更新、削除)
  • バッチ処理 API
  • 依存関係を受け付けるファクトリー関数
  • 1つまたは複数の対象に同じ処理を行うあらゆるケース

避けるべきケース:

  • 単一とバッチで意味が異なる
  • 戻り値の型が大きく異なる
  • 配列版で異なるオプションが必要

コードベースの例

サーバーファクトリー(packages/server/src/server.ts)
function createServer(
	clientOrClients: AnyWorkspaceClient | AnyWorkspaceClient[],
	options?: ServerOptions,
) {
	const clients = Array.isArray(clientOrClients)
		? clientOrClients
		: [clientOrClients];

	// All server setup logic directly here
	const workspaces: Record<string, AnyWorkspaceClient> = {};
	for (const client of clients) {
		workspaces[client.id] = client;
	}
	// ...
}
サービスメソッド
delete: async (recordingOrRecordings) => {
  const recordings = Array.isArray(recordingOrRecordings)
    ? recordingOrRecordings
    : [recordingOrRecordings];
  const ids = recordings.map((r) => r.id);
  return tryAsync({
    try: () => store.bulkDelete(ids),
    catch: (error) => RecordingStoreError.MutationFailed({ cause: error }),
  });
},
RPC ミューテーション
export const recordingKeys = defineKeys({
	delete: ['recordings', 'delete'],
});

type DeleteRecordingsInput = Recording | Recording[];

export const recordings = {
	delete: defineMutation({
		mutationKey: recordingKeys.delete,
		mutationFn: async (recordings: DeleteRecordingsInput) => {
			const recordingsArray = Array.isArray(recordings)
				? recordings
				: [recordings];

			for (const recording of recordingsArray) {
				services.blobs.audio.revokePlaybackUrl(recording.id);
			}

			const { error } = await services.recordings.delete(recordingsArray);
			if (error) return Err(error);
			return Ok(undefined);
		},
	}),
};

アンチパターン

避ける:単一用と配列用の関数を分ける
// Harder to maintain, users must remember two APIs
function createRecording(recording: Recording): Promise<...>;
function createRecordings(recordings: Recording[]): Promise<...>;
避ける:どこでも配列を強制する
// Awkward for single items
createRecordings([recording]); // Ugly
避ける:オーバーロードでロジックを重複させる
// BAD: Logic duplicated
function create(item: T) {
	return db.insert(item); // Duplicated
}
function create(items: T[]) {
	return db.bulkInsert(items); // Different code path
}

// GOOD: Single implementation
function create(itemOrItems: T | T[]) {
	const items = Array.isArray(itemOrItems) ? itemOrItems : [itemOrItems];
	return db.bulkInsert(items); // One code path
}

参考資料

コメント