A modern TypeScript library for batching async operations
npm install batchkitTiny batching library. Supports various scheduling and deduplication strategies.
``bash`
npm install batchkitor
bun add batchkit
`typescript
import { batch } from 'batchkit'
const users = batch(
(ids) => db.users.findMany({ where: { id: { in: ids } } }),
'id'
)
// These calls are batched into one database query
const [alice, bob] = await Promise.all([
users.get(1),
users.get(2),
])
`
Creates a batcher.
`typescript`
const users = batch(
// The batch function - receives keys and an AbortSignal
async (ids: number[], signal: AbortSignal) => {
return api.getUsers(ids, { signal })
},
// How to match results - just the field name
'id',
// Optional configuration
{
wait: 10, // ms to wait before dispatch (default: 0 = microtask)
max: 100, // max batch size
name: 'users', // for debugging
}
)
Get one or many items:
`typescript
// Single item
const user = await users.get(1)
// Multiple items (batched together)
const [a, b] = await Promise.all([users.get(1), users.get(2)])
// Array syntax
const team = await users.get([1, 2, 3, 4, 5])
`
Cancel a request:
`typescript
const controller = new AbortController()
const user = await users.get(1, { signal: controller.signal })
// Later...
controller.abort() // Rejects with AbortError
`
Execute pending batch immediately:
`typescript`
users.get(1)
users.get(2)
await users.flush() // Don't wait for scheduler
Abort the in-flight batch:
`typescript`
users.abort() // All pending and in-flight requests reject with AbortError
`typescript`
batch(fn, 'id')
// Matches results where result.id === requestedKey
`typescript
import { batch, indexed } from 'batchkit'
const users = batch(
async (ids) => {
// Returns { "1": {...}, "2": {...} }
return fetchUsersAsRecord(ids)
},
indexed
)
`
`typescript`
batch(
fn,
(results, key) => results.find(r => r.externalId === key)
)
Batches all calls within the same event loop tick:
`typescript
const users = batch(fn, 'id')
// these synchronous invocations are batched into one request
users.get(1)
users.get(2)
users.get(3)
`
Wait before dispatching:
`typescript`
batch(fn, 'id', { wait: 10 }) // 10ms window
Sync with rendering:
`typescript
import { batch, onAnimationFrame } from 'batchkit'
batch(fn, 'id', { schedule: onAnimationFrame })
`
Background/low-priority work:
`typescript
import { batch, onIdle } from 'batchkit'
batch(fn, 'id', { schedule: onIdle({ timeout: 100 }) })
`
Duplicate keys in the same batch are automatically deduplicated:
`typescript`
// Only ONE request for id=1
await Promise.all([
users.get(1),
users.get(1),
users.get(1),
])
For complex keys, provide a key function:
`typescript`
batch(fn, match, {
key: (query) => query.id // Dedupe by query.id
})
Debug batch behavior:
`typescript`
batch(fn, 'id', {
name: 'users',
trace: (event) => {
console.log(event.type, event)
// 'get', 'schedule', 'dispatch', 'resolve', 'error', 'abort'
}
})
`typescript
import { batch } from 'batchkit'
import { useQuery } from '@tanstack/react-query'
const users = batch(
(ids, signal) => fetch(/api/users?ids=${ids.join(',')}, { signal }).then(r => r.json()),
'id'
)
function UserAvatar({ userId }: { userId: string }) {
const { data } = useQuery({
queryKey: ['user', userId],
queryFn: ({ signal }) => users.get(userId, { signal })
})
return
}
// Rendering 100 UserAvatars from a service -> 1 HTTP request
`
`typescript
const products = batch(
(ids) => shopify.products.list({ ids }),
'id',
{ max: 50 } // Shopify's limit
)
// 200 product requests = 4 API calls (50 each)
`
Correctly infers types based on call site
`typescript
type User = { id: number; name: string }
const users = batch(
async (ids: number[]): Promise
'id'
)
const user = await users.get(1) // user: User
const many = await users.get([1, 2]) // many: User[]
``
MIT