🎯

single-or-array-pattern

🎯Skill

from epicenterhq/epicenter

VibeIndex|
What it does

single-or-array-pattern skill from epicenterhq/epicenter

πŸ“¦

Part of

epicenterhq/epicenter(28 items)

single-or-array-pattern

Installation

git cloneClone repository
git clone https://github.com/EpicenterHQ/epicenter.git
BunRun with Bun
bun install # Will prompt to upgrade if your Bun version is too old
BunRun with Bun
bun dev
BunRun with Bun
bun install # Reinstall dependencies
BunRun with Bun
bun install
πŸ“– Extracted from docs: epicenterhq/epicenter
14Installs
4,012
-
Last UpdatedJan 26, 2026

Skill Details

SKILL.md

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.

Overview

# Single-or-Array Overload Pattern

Accept both single items and arrays, normalize internally, delegate to array-only implementation.

Quick Reference

```typescript

// Option 1: Explicit overloads (cleaner IDE signatures)

function create(item: T): Promise>;

function create(items: T[]): Promise>;

function create(itemOrItems: T | T[]): Promise> {

const items = Array.isArray(itemOrItems) ? itemOrItems : [itemOrItems];

return createInternal(items);

}

// Option 2: Union type (less boilerplate)

function create(itemOrItems: T | T[]): Promise> {

const items = Array.isArray(itemOrItems) ? itemOrItems : [itemOrItems];

// ... implementation

}

```

The Structure

  1. Public API: Accepts T | T[]
  2. Normalization: Array.isArray() check, wrap single in array
  3. Internal function: Only handles arrays

```typescript

// Public: flexible API

function createServer(clientOrClients: Client | Client[], options?: Options) {

const clients = Array.isArray(clientOrClients)

? clientOrClients

: [clientOrClients];

return createServerInternal(clients, options);

}

// Internal: array-only, all real logic here

function createServerInternal(clients: Client[], options?: Options) {

// Implementation only handles arrays

}

```

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/epicenter/src/server/server.ts`)

```typescript

function createServer(

client: AnyWorkspaceClient,

options?: ServerOptions,

): ReturnType;

function createServer(

clients: AnyWorkspaceClient[],

options?: ServerOptions,

): ReturnType;

function createServer(

clientOrClients: AnyWorkspaceClient | AnyWorkspaceClient[],

options?: ServerOptions,

) {

const clients = Array.isArray(clientOrClients)

? clientOrClients

: [clientOrClients];

return createServerInternal(clients, options);

}

```

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) => DbServiceErr({ message: Error deleting: ${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