mcpField
S
single-or-array-pattern

single-or-array-pattern

github.com/epicenterhqDevelopment4.8k63 installsUpdated 2w ago
Repository

About single-or-array-pattern

A TypeScript API design guide that recommends normalizing single-item and array inputs at the function boundary, then processing them through one code path. It applies to CRUD, batch APIs, and dependency factories.

Key features

  1. 01Accept single items or arrays
  2. 02Normalize inputs at the boundary
  3. 03Process all items through one path
  4. 04Show CRUD and batch examples
  5. 05Identify suitable cases and anti-patterns

Use cases

  1. 01Design APIs for single and batch operations
  2. 02Accept one or many factory dependencies
  3. 03Unify record creation or deletion logic

Install

Copy the command and run it in a terminal.

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

After installing, type/single-or-array-patternin Claude Code to confirm it loaded.

Details

From the project README

Single-or-Array Pattern

Accept both single items and arrays, normalize at the top, process uniformly.

Quick Reference

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

The Structure

  1. Accept T | T[] as the parameter type
  2. Normalize with Array.isArray() at the top of the function
  3. All logic works against the array : one code path
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) {
		// ...
	}
}

Naming Conventions

ParameterNormalized Variable
recordingOrRecordingsrecordings
clientOrClientsclients
itemOrItemsitems
paramsOrParamsArrayparamsArray

When to Use

Good fit:

  • CRUD operations (create, update, delete)
  • Batch processing APIs
  • Factory functions accepting dependencies
  • Any "do this to one or many" scenario

Skip when:

  • Single vs batch have different semantics
  • Return types vary significantly
  • Array version needs different options

Codebase Examples

Server Factory (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;
	}
	// ...
}
Service Method
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 Mutation
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);
		},
	}),
};

Anti-Patterns

Don't: Separate functions for single vs array
// Harder to maintain, users must remember two APIs
function createRecording(recording: Recording): Promise<...>;
function createRecordings(recordings: Recording[]): Promise<...>;
Don't: Force arrays everywhere
// Awkward for single items
createRecordings([recording]); // Ugly
Don't: Duplicate logic in overloads
// 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
}

References

Comments

Markdown supported