A focused collection of TypeScript utilities for modern web development.
- Fully typed - strict TypeScript across every module, literal-type preserving where it matters
- Tree-shakeable - ESM + CJS + per-module subpath exports,
"sideEffects": false - Zero dependencies - React is an optional peer dep only for the
useEventBushook - Well tested - 200+ tests, real edge cases (circular refs, typed arrays, Buffers, symbols)
bun add @pivanov/utilsnpm install @pivanov/utilsyarn add @pivanov/utilspnpm add @pivanov/utilsimport { camelCase, snakeCase, slugify } from '@pivanov/utils/string';
import { pick, groupBy, deepMerge } from '@pivanov/utils/object';
import { isString, isNil, isDefined } from '@pivanov/utils/assertion';
import { sleep, timeout, retry, parallelLimit } from '@pivanov/utils/promise';
import { deepClone, isEqual, busDispatch, useEventBus } from '@pivanov/utils/tools';| Module | Surface |
|---|---|
assertion |
isString, isNumber, isBoolean, isFunction, isObject, isRecord, isNull, isUndefined, isNil, isDefined, isArray, isDate, isRegExp, isError, isPromise, isMap, isSet, isPrimitive, isEmpty |
object |
pick, omit, pickBy, omitBy, merge, deepMerge, mapValues, mapKeys, groupBy, invert, hasOwn, keysOf, entriesOf, fromEntries |
promise |
sleep, timeout, retry, defer, parallelLimit - all AbortSignal-aware where relevant |
string |
camelCase, pascalCase, kebabCase, snakeCase, titleCase, slugify, capitalize, uncapitalize, capitalizeFirstLetter, truncate, escapeHtml, escapeRegExp, words, lines |
tools/deepClone |
Rich deep clone (prototypes, getters/setters, symbols, Buffers, TypedArrays, circular refs) |
tools/isEqual |
Deep equality with cycle detection; compares RegExp, Error, TypedArrays, ArrayBuffer |
tools/dom |
isBrowser, checkVisibility, isInViewport, setStyleProperties, calculateRenderedTextWidth |
tools/cache-api |
Typed wrapper over the browser Cache API, with TTL support |
tools/eventBus |
busDispatch, busSubscribe, busOnce, useEventBus - typed topics, optional error handler |
types |
TDict, TObjType, DeepPartial, DeepReadonly, Mutable, Prettify |
See the full API documentation.
Subpath imports give the smallest bundles:
import { camelCase } from '@pivanov/utils/string';
import { deepClone } from '@pivanov/utils/tools';Top-level imports tree-shake fine in modern bundlers:
import { camelCase, deepClone } from '@pivanov/utils';import { sleep, timeout, retry } from '@pivanov/utils/promise';
const ctrl = new AbortController();
await sleep(1000, ctrl.signal); // cancellable
await timeout(fetch('/slow'), 3000); // race with timer
await retry(() => fetch('/api'), { attempts: 5, backoff: (n) => 100 * 2 ** n });import { busDispatch, useEventBus, type IEventBus } from '@pivanov/utils/tools';
interface UserLoggedIn extends IEventBus<{ id: number; name: string }> {
topic: 'user:logged-in';
}
useEventBus<UserLoggedIn>('user:logged-in', (user) => console.log(user.name));
busDispatch<UserLoggedIn>('user:logged-in', { id: 1, name: 'Ada' });See the Typed Events guide for an event-map pattern at scale.
import {
storageSetItemWithTTL,
storageGetItemWithTTL,
} from '@pivanov/utils/tools';
await storageSetItemWithTTL('app', 'token', 'abc123', 10 * 60 * 1000);
const token = await storageGetItemWithTTL<string>('app', 'token');
// null if missing or expired; expired entries are deleted on readimport { deepClone } from '@pivanov/utils/tools';
class User {
constructor(public name: string) {}
greet() { return `hi ${this.name}`; }
}
const clone = deepClone(new User('Ada'));
clone instanceof User; // true
clone.greet(); // 'hi Ada'- Modern browsers (ES2022)
- Bun, Node 18+ (ESM or CJS)
- Cache API requires browser support (Chrome 40+, Firefox 41+, Safari 11.1+)
- React hook requires React 18+
bun install
bun test # run tests
bun run test:coverage # with coverage (lcov)
bun run typecheck
bun run lint
bun run build # ESM + CJS + .d.ts
bun run docs:dev # run VitePress docs site locallySupported by LogicStar AI
MIT © Pavel Ivanov