@depthbomb/common
v2.3.0
Published
A set of common utilities for TypeScript/JavaScript
Downloads
474
Readme
@depthbomb/common
A set of common utilities for TypeScript/JavaScript that I use in my projects.
Looking for utilities for Node.js specifically? See @depthbomb/node-common!
Modules
timing
Timing and timeout flow-control helpers.
import {
timeout,
rejectionTimeout,
pollUntil,
withTimeout,
retry,
abortAfter,
withAbort,
raceSignals,
RetryJitter,
} from '@depthbomb/common/timing';
await timeout(250);
await rejectionTimeout(100).catch(() => {});
let ready = false;
setTimeout(() => { ready = true; }, 200);
await pollUntil(() => ready, 50, 1_000);
const value = await withTimeout(Promise.resolve('ok'), 500);
const data = await retry(
async (attempt) => {
const response = await fetch('https://example.com/data');
if (!response.ok) {
throw new Error(`request failed on attempt ${attempt}`);
}
return response.json();
},
{
attempts: 4,
baseMs: 100,
maxMs: 1_000,
jitter: RetryJitter.Full,
}
);
const signal = abortAfter(1_000);
await withAbort(fetch('https://example.com/slow'), signal);
const parent = new AbortController();
const child = abortAfter(500);
const combined = raceSignals(parent.signal, child);promise
Promise composition helpers, including detailed settled results and concurrency-limited execution.
import { allSettledSuccessful, allSettledDetailed, sequential, pool, pMap } from '@depthbomb/common/promise';
const successful = await allSettledSuccessful([
Promise.resolve(1),
Promise.reject(new Error('x')),
Promise.resolve(2)
]); // [1, 2]
const ordered = await sequential([
async () => 'first',
async () => 'second'
]); // ['first', 'second']
const detailed = await allSettledDetailed([
Promise.resolve('ok'),
Promise.reject(new Error('x')),
]); // { results, fulfilled: ['ok'], rejected: [Error('x')] }
const pooled = await pool([
async () => 1,
async () => 2,
async () => 3,
], { concurrency: 2 });
const mapped = await pMap([1, 2, 3], async (v) => v * 10, { concurrency: 2 });decorators
TypeScript decorators for reusable behavior. Currently includes a TTL-based cache decorator for memoizing method results per instance and argument set.
import { cache } from '@depthbomb/common/decorators';
class Service {
calls = 0;
@cache(1_000)
getUser(id: string) {
this.calls++;
return { id, calls: this.calls };
}
}
const s = new Service();
s.getUser('1');
s.getUser('1'); // cached for 1 secondstate
State primitives: ResettableValue, Flag, resettableLazy, and resettableLazyAsync.
import { Flag, ResettableValue, resettableLazy, resettableLazyAsync } from '@depthbomb/common/state';
const flag = new Flag();
flag.setTrue();
flag.toggle();
flag.reset();
const retries = new ResettableValue(3);
retries.set(1);
retries.reset(); // back to 3
const counter = resettableLazy(() => Math.random());
const first = counter.get();
counter.reset();
const second = counter.get(); // recomputed
const tokenCache = resettableLazyAsync(async () => {
const response = await fetch('https://example.com/token');
return response.text();
});
const token = await tokenCache.get();
tokenCache.reset();functional
General function utilities. Currently includes once, which ensures a function runs only on its first invocation and then reuses the same result.
import { once } from '@depthbomb/common/functional';
const init = once(() => ({ startedAt: Date.now() }));
const a = init();
const b = init();
console.log(a === b); // truelazy
Lazy initialization utilities for sync and async values.
import { lazy, lazyAsync } from '@depthbomb/common/lazy';
const getConfig = lazy(() => ({ env: 'prod' }));
const config = getConfig(); // factory runs once
const getToken = lazyAsync(async () => 'token');
const token = await getToken(); // promise created oncecollections
A lightweight generic FIFO queue with enqueue, dequeue, peek, iteration support, and internal compaction to keep long-running usage efficient. Also includes BoundedQueue for fixed-capacity use cases.
import { Queue, BoundedQueue, BoundedQueueOverflow } from '@depthbomb/common/collections';
const q = new Queue<number>([1, 2]);
q.enqueue(3);
console.log(q.peek()); // 1
console.log(q.dequeue()); // 1
console.log(q.toArray()); // [2, 3]
const bounded = new BoundedQueue<number>({ maxSize: 3, overflow: BoundedQueueOverflow.DropOldest }, [1, 2, 3]);
bounded.enqueue(4);
console.log(bounded.toArray()); // [2, 3, 4]number
Numeric helpers for clamping, range checks, rounding, and aggregation.
import { clamp, inRange, roundTo, sum, average } from '@depthbomb/common/number';
const bounded = clamp(12, 0, 10); // 10
const valid = inRange(5, 1, 10); // true
const rounded = roundTo(3.14159, 2); // 3.14
const total = sum([1, 2, 3, 4]); // 10
const mean = average([1, 2, 3, 4]); // 2.5random
Cross-environment random helpers for ranges and selection.
import { randomFloat, randomInt, pickRandom, pickWeighted } from '@depthbomb/common/random';
const f = randomFloat(5, 10); // 5 <= f < 10
const i = randomInt(1, 6); // inclusive
const choice = pickRandom(['red', 'green', 'blue']);
const weighted = pickWeighted([
{ value: 'small', weight: 1 },
{ value: 'medium', weight: 3 },
{ value: 'large', weight: 6 },
]);guards
Runtime type guards for common narrowing patterns.
import { isRecord, isNonEmptyString, isNumber, isDateLike } from '@depthbomb/common/guards';
const input: unknown = { name: 'Ada', createdAt: '2026-01-01' };
if (isRecord(input) && isNonEmptyString(input.name) && isDateLike(input.createdAt)) {
// narrowed shape at runtime
}
const maybeCount: unknown = 42;
if (isNumber(maybeCount)) {
console.log(maybeCount + 1);
}typing
Shared type aliases and type-oriented helpers such as Awaitable, Maybe, Nullable, Result, cast, assume, typedEntries, ok, err, isOk, mapOk, mapErr, and tryCatchAsync.
import {
cast, assume, typedEntries,
ok, err, isOk, mapOk, mapErr, tryCatchAsync,
type Awaitable, type Maybe, type Result
} from '@depthbomb/common/typing';
const v = cast<object, { id: string }>({ id: 'a' });
const unknownValue: unknown = 'hello';
assume<string>(unknownValue); // assertion helper
const entries = typedEntries({ a: 1, b: 2 }); // typed key/value tuples
const maybeName: Maybe<string> = undefined;
const task: Awaitable<number> = Promise.resolve(1);
const initial: Result<number, string> = ok(2);
const doubled = mapOk(initial, (value) => value * 2);
const message = mapErr(err('bad'), (e) => `error: ${e}`);
const parsed = await tryCatchAsync(async () => JSON.parse('{"x":1}'));
if (isOk(parsed)) {
console.log(parsed.value.x);
}url
URL-focused utilities centered around URLPath, an ergonomic wrapper for URL parsing, path composition, query/hash manipulation, and request dispatch via fetch.
import { URLPath, url } from '@depthbomb/common/url';
const api = new URLPath('https://example.com/api');
const usersUrl = api
.joinpath('users', '42')
.withQuery({ include: ['roles', 'profile'] })
.withQueryPatch({ page: 1 })
.appendQuery({ include: 'permissions' })
.withoutEmptyQuery()
.withHash('details');
const userPath = url`/users/${'john/doe'}/posts/${'my first post'}`;
console.log(usersUrl.toString());
// https://example.com/api/users/42?include=roles&include=profile#details
console.log(userPath);
// /users/john%2Fdoe/posts/my%20first%20post