mcpField
S
single-or-array-pattern

single-or-array-pattern

github.com/epicenterhq开发4.8k63 次安装2 周前更新
代码仓库

关于 single-or-array-pattern

一种 TypeScript API 设计模式指南,建议单项和数组输入在函数入口统一规范化,再由同一逻辑处理。适用于 CRUD、批处理和接收依赖项的工厂函数。

主要功能

  1. 01接受单项或数组输入
  2. 02在入口统一规范化参数
  3. 03用同一逻辑处理所有项
  4. 04涵盖 CRUD 与批处理示例
  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. 所有逻辑都以数组为基础:只使用一条代码路径
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
  • 接受依赖项的工厂函数
  • 任何“对一个或多个对象执行某操作”的场景

不适合:

  • 单项和批量操作语义不同
  • 返回类型差异很大
  • 数组版本需要不同的选项

代码库示例

服务端工厂(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
}

参考资料

评论

支持 Markdown