EpicenterHQ / single-or-array-pattern
Install for your project team
Run this command in your project directory to install the skill for your entire team:
mkdir -p .claude/skills/single-or-array-pattern && curl -L -o skill.zip "https://fastmcp.me/Skills/Download/3463" && unzip -o skill.zip -d .claude/skills/single-or-array-pattern && rm skill.zip
Project Skills
This skill will be saved in .claude/skills/single-or-array-pattern/ and checked into git. All team members will have access to it automatically.
Important: Please verify the skill by reviewing its instructions before using it.
Pattern for functions that accept either a single item or an array. Use when creating CRUD operations, batch processing APIs, or factory functions that should flexibly handle one or many inputs.
0 views
0 installs
Skill Content
---
name: single-or-array-pattern
description: Pattern for functions that accept either a single item or an array. Use when creating CRUD operations, batch processing APIs, or factory functions that should flexibly handle one or many inputs.
metadata:
author: epicenter
version: '1.0'
---
# Single-or-Array Pattern
Accept both single items and arrays, normalize at the top, process uniformly.
## Quick Reference
```typescript
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
```typescript
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
| Parameter | Normalized Variable |
| ----------------------- | ------------------- |
| `recordingOrRecordings` | `recordings` |
| `clientOrClients` | `clients` |
| `itemOrItems` | `items` |
| `paramsOrParamsArray` | `paramsArray` |
## 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`)
```typescript
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;
}
// ...
}
```
### Database Service (`apps/whispering/src/lib/services/isomorphic/db/web.ts`)
```typescript
delete: async (recordingOrRecordings) => {
const recordings = Array.isArray(recordingOrRecordings)
? recordingOrRecordings
: [recordingOrRecordings];
const ids = recordings.map((r) => r.id);
return tryAsync({
try: () => db.recordings.bulkDelete(ids),
catch: (error) => DbError.MutationFailed({ cause: error }),
});
},
```
### Query Mutations (`apps/whispering/src/lib/query/isomorphic/db.ts`)
```typescript
delete: defineMutation({
mutationFn: async (recordings: Recording | Recording[]) => {
const recordingsArray = Array.isArray(recordings)
? recordings
: [recordings];
for (const recording of recordingsArray) {
services.db.recordings.revokeAudioUrl(recording.id);
}
const { error } = await services.db.recordings.delete(recordingsArray);
if (error) return Err(error);
return Ok(undefined);
},
}),
```
## Anti-Patterns
### Don't: Separate functions for single vs array
```typescript
// Harder to maintain, users must remember two APIs
function createRecording(recording: Recording): Promise<...>;
function createRecordings(recordings: Recording[]): Promise<...>;
```
### Don't: Force arrays everywhere
```typescript
// Awkward for single items
createRecordings([recording]); // Ugly
```
### Don't: Duplicate logic in overloads
```typescript
// 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
- [Full article](../../docs/articles/single-or-array-overload-pattern.md) — detailed explanation with more examples