seriatim
v0.1.1
Published
Serialize async operations per key — same key runs strictly in order, different keys run in parallel. Zero deps.
Downloads
24
Maintainers
Readme
seriatim
Serialize async operations per key — same key runs strictly in order, different keys run in parallel. Zero deps.
The Problem
You need operations that share a resource to not overlap, but only when they touch the same thing. Two requests updating cart #42 must serialize to prevent lost updates, while cart #7 should proceed in parallel. A single global lock kills throughput; no lock corrupts state with race conditions.
Current solutions fall short. p-limit limits concurrency but isn't keyed. async-mutex requires building and garbage-collecting a map per key yourself — getting the "delete the mutex when idle without dropping a waiter" logic right is the hard part.
Install
npm install seriatim
# or
pnpm add seriatim
# or
yarn add seriatimUse
import seriatim from "seriatim";
const lock = seriatim();
// Serial per cart, parallel across carts
await lock(`cart:${id}`, async () => {
await updateCart(id);
});// Real-world: Shopping cart updates
import seriatim from "seriatim";
const cartLock = seriatim();
// User A updates cart-123
cartLock("cart-123", async () => {
const cart = await db.cart.find("123");
cart.items.push({ productId: "abc", quantity: 1 });
await db.cart.save(cart);
});
// User B tries to update same cart concurrently
cartLock("cart-123", async () => {
// This waits for User A's update to complete
// No lost updates, no race conditions
const cart = await db.cart.find("123");
cart.items.push({ productId: "def", quantity: 2 });
await db.cart.save(cart);
});
// User C updates different cart - runs in parallel
cartLock("cart-456", async () => {
// This doesn't wait for cart-123 updates
const cart = await db.cart.find("456");
cart.items.push({ productId: "ghi", quantity: 1 });
await db.cart.save(cart);
});API
import seriatim from "seriatim";
const lock = seriatim();
// Execute function per key, serializing same-key calls
await lock<T>(key: string, fn: () => T | Promise<T>): Promise<T>;
// Instance properties
lock.size: number; // Keys with active/queued tasks
lock.pending(key): number; // Queued tasks for key (excludes running)
lock.isLocked(key): boolean; // Whether key has active/queued tasksshared
import { shared } from "seriatim";Convenience shared instance. Use when you don't need isolation.
Non-goals
Seriatim is intentionally focused on keyed serialization only. It does NOT provide:
- Global concurrency limits (compose with
p-limit) - Priorities or fairness guarantees
- Timeouts or cancellation
- Reentrancy detection (calling same key from within itself deadlocks — caller's responsibility)
- Cross-process or distributed locks
For global concurrency limits, compose with p-limit:
import seriatim from "seriatim";
import pLimit from "p-limit";
const lock = seriatim();
const globalLimit = pLimit(10); // Max 10 concurrent operations total
async function safeUpdate(id: string) {
return globalLimit(() => lock(`cart:${id}`, async () => {
await updateCart(id);
}));
}TypeScript
import seriatim from "seriatim";
const lock = seriatim();
// Full type safety with generics
const result: Cart = await lock(`cart:${id}`, async () => {
return await db.cart.find(id);
});
// Sync functions also work
const count: number = lock("counter", () => db.users.count());Related Packages
Caching & Concurrency:
- @azghr/filterkit — Framework-agnostic, type-safe filtering for TypeScript
- @azghr/singlet — Deduplicate concurrent async calls
- staleness — Stale-while-revalidate caching for async functions
Text Processing:
- @azghr/shorn — Truncate strings by byte budget without breaking graphemes
HTTP & Network:
- forbear — Read server rate-limit instructions from HTTP responses
- forestall — Delay execution until a condition is met
- obviate — Render operations unnecessary through caching
System & Process:
- quiesce — Ordered, timeboxed graceful shutdown for Node
- sortition — Deterministic percentage rollouts and A/B bucketing
- stanch — Stop flows or operations based on conditions
Utilities:
- expunge — Remove or exclude items from collections
- occlude — Hide or mask data and functionality
- placemark — Geographic location and mapping utilities
- specie — Currency and financial calculations
License
MIT
