Typical reusable utilities shared across projects — the canonical home for the small helpers.
- ESM-only,
sideEffects: false, fully tree-shakable - Zero-dependency core — heavier worlds (React, Tailwind) are isolated behind subpath exports with optional peer dependencies
pnpm add @entwico/dash| Entry | Contents | Peer dependencies |
|---|---|---|
@entwico/dash |
zero-dependency utilities and types | — |
@entwico/dash/async |
AsyncIterable primitives and backoff | — |
@entwico/dash/match |
performant string pattern matching | — |
@entwico/dash/cn |
cn() class name merging |
clsx ≥2, tailwind-merge ≥2 |
@entwico/dash/react |
React hooks | react ≥18 |
All peer dependencies are optional — install only what the entry points you use need.
| Export | Purpose |
|---|---|
assert(expr, message?) |
assertion with asserts expr narrowing; message may be a string or an Error |
capitalize(str) |
uppercase the first letter |
createBatcher(onFlush, delayMs) |
batch calls into one flush after a delay |
debounce(fn, wait) |
classic trailing debounce |
deepFreeze(value) |
recursive Object.freeze, returns ReadonlyDeep<T> |
defined(val) / truthy(val) |
type-guard filters for null / undefined / falsy |
indexBy(array, keyFn, valueFn?) |
index an array into a Map; key/value selectors are property names or functions |
mapConcurrent(items, fn, concurrency) |
order-preserving concurrent map with a bounded worker pool |
maybeThen(value, fn) / maybeCatch(value, fn) |
chain transformations on MaybePromise values without unwrapping |
maybeAll(values) / maybeAllSettled(values) |
Promise.all / Promise.allSettled over MaybePromise values, synchronous when all values are |
noop / markAsUsed |
no-op functions |
omitUndefined(obj, recursive?) |
strip undefined properties (deep by default) |
optionalize(fn, options?) |
lift a function to accept null / undefined; defaultValue / strategy ('nullish' | 'falsy') options |
retry(fn, options?) |
retry with a fixed delay; retries / delayMs / signal / onError options |
sleep(ms, signal?) |
promise-based delay, abortable via AbortSignal |
Types: MaybePromise<T>, ReadonlyDeep<T>.
Zero-dependency streaming and retry primitives — the rxjs patterns without rxjs:
| Export | Purpose |
|---|---|
createAsyncIterableSubject() / AsyncIterableSubject<T> |
multicast source with next / error / complete, consumable via for await or subscribe(observerOrNext) |
firstAsync(iter) |
first yielded value, then disposes the iterator; throws if the iterable completes empty (≈ firstValueFrom) |
mapAsync(iter, fn) / filterAsync(iter, predicate) |
lazy operators over an AsyncIterable |
concatAsync(iter) |
concatenate all yielded arrays into one (plain collection: Array.fromAsync) |
mergeAsync(sources) |
merge multiple AsyncIterables into one; completes when all complete, errors when any errors (≈ merge) |
debounceAsync(iter, ms) |
rolling debounce: yield the latest value after ms of silence; a slow consumer only sees the most recent settled value (≈ debounceTime) |
exponentialBackoff(attempt, options?) |
jittered exponential delay, abortable; bounds configurable via maxDelayMs / jitterMinMs / jitterMaxMs |
retryWithBackoff(fn, options?) |
retry fn with exponential backoff indefinitely until it resolves, abortable via signal |
intervalAsync(ms) |
periodic tick stream yielding 0, 1, 2, … every ms; disposing the iterator clears the timer (≈ interval) |
keepalive(factory, options?) |
keep a long-running AsyncIterable alive: re-subscribe with backoff on error or completion, stop on signal abort |
A pattern union that keeps cheap checks cheap — startsWith beats a regex when a prefix is all you need:
import { type StringPattern, createMatcher } from "@entwico/dash/match";
const isExcluded = createMatcher([{ exact: "/favicon.ico" }, { prefix: "/_astro/" }, { suffix: ".map" }, { includes: "/internal/" }, { pattern: /^\/api\/v\d+\// }, (path) => path.length > 2000]);
isExcluded("/_astro/chunk.js"); // truecreateMatcher buckets patterns by check cost at creation time: all exact patterns collapse into a single Set lookup, then prefixes / suffixes / substrings, then regexps and functions. For one-shot checks there are matches(value, pattern) and matchesAny(value, patterns).
import { type Classable, cn } from "@entwico/dash/cn";
cn("p-2", condition && "p-4"); // tailwind conflicts resolved in favor of the last classClassable is the { className?: string } contract for component props designed to be merged via cn().
useDebouncedValue(value, delay)— the value, updated only after it has stayed unchanged for the given delayuseEffectAfterMount(effect, deps?)— run the effect on updates, skip the initial mountuseEffectOnce(effect)— run the effect exactly once on mountuseIsMobile()— whether the viewport is below the mobile breakpoint (768px);falseduring SSR
MIT