@carl.fyi/runtime
v0.1.0
Published
Keep application plumbing predictable with retries, debouncing, typed results, caching and secure random codes.
Readme
@carl.fyi/runtime
Keep application plumbing predictable with retries, debouncing, typed results, caching and secure random codes.
Install
npm install @carl.fyi/runtimeQuick start
import { debounce } from "@carl.fyi/runtime";
const save = debounce((value: string) => console.log(value), 300);
save("draft");
if (save.pending()) save.flush();Entry points
| Import | Purpose |
| -------------------------- | ---------------------- |
| @carl.fyi/runtime | All public exports |
| @carl.fyi/runtime/async | Focused subpath import |
| @carl.fyi/runtime/cache | Focused subpath import |
| @carl.fyi/runtime/random | Focused subpath import |
| @carl.fyi/runtime/result | Focused subpath import |
All entry points are ESM, side-effect-free, and include TypeScript declarations.
Public API
This is the complete export inventory. The detailed reference below mirrors the APIs demonstrated on the documentation website; its signatures, descriptions, defaults, errors, and examples are compiled from the package source.
| Export | Kind | Import from | Description |
| --------------------------- | --------- | -------------------------- | ------------------------------------------------------------------------------- |
| backoffDelay | function | @carl.fyi/runtime/async | Calculate one bounded exponential-backoff delay. |
| BackoffOptions | interface | @carl.fyi/runtime/async | Public interface exported by this package. |
| debounce | function | @carl.fyi/runtime/async | Create a trailing-edge function that runs after calls become quiet. |
| DebouncedFunction | interface | @carl.fyi/runtime/async | Public interface exported by this package. |
| withBackoff | function | @carl.fyi/runtime/async | Retry an asynchronous operation with bounded exponential backoff. |
| AsyncCache | interface | @carl.fyi/runtime/cache | Public interface exported by this package. |
| createCachedAsyncFunction | function | @carl.fyi/runtime/cache | Wrap an async function with read-through caching and in-flight deduplication. |
| HashInput | type | @carl.fyi/runtime/cache | Public type exported by this package. |
| MemoryCache | class | @carl.fyi/runtime/cache | Public class exported by this package. |
| sha256Hex | function | @carl.fyi/runtime/cache | Hash text or bytes with SHA-256 and encode the digest as lowercase hexadecimal. |
| randomCode | function | @carl.fyi/runtime/random | Generate an unbiased human-readable random code. |
| randomString | function | @carl.fyi/runtime/random | Public function exported by this package. |
| RandomStringOptions | interface | @carl.fyi/runtime/random | Public interface exported by this package. |
| READABLE_CODE_ALPHABET | value | @carl.fyi/runtime/random | Public value exported by this package. |
| err | value | @carl.fyi/runtime/result | Public value exported by this package. |
| fromPromise | function | @carl.fyi/runtime/result | Capture a rejected promise in a typed Result. |
| fromThrowable | function | @carl.fyi/runtime/result | Capture a synchronous exception in a typed Result. |
| isErr | value | @carl.fyi/runtime/result | Public value exported by this package. |
| isOk | value | @carl.fyi/runtime/result | Public value exported by this package. |
| ok | value | @carl.fyi/runtime/result | Public value exported by this package. |
| Result | type | @carl.fyi/runtime/result | Public type exported by this package. |
API reference
@carl.fyi/runtime/async
debounce
debounce<Arguments extends unknown[], Result>(fn: (...args: Arguments) => Result, waitMs: number): DebouncedFunction<Arguments, Result>Create a trailing-edge function that runs after calls become quiet.
The wrapper retains only the latest arguments and receiver. Use cancel, flush, and pending to control scheduled work.
| Parameter | Type | Description |
| --------- | -------------------------------- | ------------------------------------------ |
| fn | (...args: Arguments) => Result | Function to invoke after the delay. |
| waitMs | number | Non-negative finite delay in milliseconds. |
Returns: A debounced callable with cancellation, flushing, and pending-state helpers.
Throws
- A
RangeErrorwhenwaitMsis negative or non-finite.
Example
import { debounce } from "@carl.fyi/runtime";
const save = debounce((value: string) => console.log(value), 300);
save("draft");
if (save.pending()) save.flush();@carl.fyi/runtime/async
backoffDelay
backoffDelay(attempt: number, options?: Pick<BackoffOptions, "baseDelayMs" | "factor" | "maxDelayMs" | "jitter" | "random">): numberCalculate one bounded exponential-backoff delay.
attempt is zero-based. Jitter is clamped to the inclusive range from zero to one.
| Parameter | Type | Description |
| -------------------------------- | ----------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| attempt | number | Zero-based retry index. |
| options (optional) | Pick<BackoffOptions, "baseDelayMs" \| "factor" \| "maxDelayMs" \| "jitter" \| "random"> | Timing, cap, jitter, and random-source settings. |
| options.baseDelayMs (optional) | number | Delay before the first retry, in milliseconds. Default: 200. |
| options.factor (optional) | number | Multiplier applied for each subsequent retry. Default: 2. |
| options.maxDelayMs (optional) | number | Upper bound for any delay, in milliseconds. Default: 30000. |
| options.jitter (optional) | number | Symmetric random variation from zero to one. Default: 0.2. |
| options.random (optional) | () => number | Random-number source used to make jitter testable. Default: Math.random. |
Returns: The rounded delay in milliseconds.
Throws
- A
RangeErrorwhenattemptis not a non-negative integer.
Example
import { backoffDelay } from "@carl.fyi/runtime";
const delays = [0, 1, 2].map((attempt) => backoffDelay(attempt, { baseDelayMs: 250, jitter: 0 }));
// [250, 500, 1000]withBackoff
withBackoff<T>(operation: (attempt: number) => Promise<T>, options?: BackoffOptions): Promise<T>Retry an asynchronous operation with bounded exponential backoff.
The first call is attempt zero and does not wait. Injectable timing hooks make tests deterministic.
| Parameter | Type | Description |
| -------------------------------- | ------------------------------------------------------------ | -------------------------------------------------------------------------- |
| operation | (attempt: number) => Promise<T> | Async operation receiving its zero-based attempt number. |
| options (optional) | BackoffOptions | Retry count, timing, cancellation, and lifecycle hooks. |
| options.retries (optional) | number | Number of retries after the initial attempt. Default: 3. |
| options.baseDelayMs (optional) | number | Delay before the first retry, in milliseconds. Default: 200. |
| options.factor (optional) | number | Multiplier applied for each subsequent retry. Default: 2. |
| options.maxDelayMs (optional) | number | Upper bound for any delay, in milliseconds. Default: 30000. |
| options.jitter (optional) | number | Symmetric random variation from zero to one. Default: 0.2. |
| options.random (optional) | () => number | Random-number source used to make jitter testable. Default: Math.random. |
| options.sleep (optional) | (milliseconds: number) => Promise<void> | Injectable delay implementation. |
| options.shouldRetry (optional) | (error: unknown, attempt: number) => boolean | Predicate that can stop retries for a particular error. |
| options.onRetry (optional) | (error: unknown, attempt: number, delayMs: number) => void | Callback invoked immediately before each retry delay. |
| options.signal (optional) | AbortSignal | Abort signal checked before every attempt. |
Returns: A promise for the first successful operation result.
Throws
- The last operation error, a cancellation reason, or an error from a supplied hook.
Example
import { withBackoff } from "@carl.fyi/runtime";
const value = await withBackoff((attempt) => fetch(`/api/data?attempt=${attempt}`), {
retries: 4,
baseDelayMs: 250,
});@carl.fyi/runtime/result
fromThrowable
fromThrowable<T, E = unknown>(operation: () => T, mapError?: (error: unknown) => E): Result<T, E>Capture a synchronous exception in a typed Result.
Successful values and mapped failures are returned as serialisable discriminated unions.
| Parameter | Type | Description |
| --------------------- | ----------------------- | --------------------------------------------------------------------------- |
| operation | () => T | Synchronous operation to execute once. |
| mapError (optional) | (error: unknown) => E | Optional conversion from an unknown thrown value to the desired error type. |
Returns: An ok result with the value or an error result with the captured failure.
Example
import { fromThrowable } from "@carl.fyi/runtime";
const parsed = fromThrowable(() => JSON.parse('{"ok":true}'), String);
if (parsed.ok) console.log(parsed.value);@carl.fyi/runtime/cache
sha256Hex
sha256Hex(input: HashInput): Promise<string>Hash text or bytes with SHA-256 and encode the digest as lowercase hexadecimal.
The input is copied before Web Crypto receives it, so callers may safely reuse their original buffer.
| Parameter | Type | Description |
| --------- | ----------- | ---------------------------------------------------- |
| input | HashInput | UTF-8 text, an ArrayBuffer, or a typed byte array. |
Returns: A promise for the 64-character hexadecimal digest.
Example
import { sha256Hex } from "@carl.fyi/runtime";
const key = await sha256Hex("Useful things for making things.");
console.log(key.length); // 64@carl.fyi/runtime/random
randomCode
randomCode(length?: number, options?: Omit<RandomStringOptions, "length" | "alphabet">): stringGenerate an unbiased human-readable random code.
The alphabet omits visually ambiguous characters; rejection sampling avoids modulo bias.
| Parameter | Type | Description |
| -------------------------------- | --------------------------------------------------- | ------------------------------------------------------------- |
| length (optional) | number | Number of characters to generate. Default: 8. |
| options (optional) | Omit<RandomStringOptions, "length" \| "alphabet"> | Optional injectable random-byte source. |
| options.randomBytes (optional) | (length: number) => Uint8Array | Injectable cryptographic byte source for deterministic tests. |
Returns: A cryptographically random code from READABLE_CODE_ALPHABET.
Throws
- A
RangeErrorwhen length is outside one to 4096.
Example
import { randomCode } from "@carl.fyi/runtime";
const inviteCode = randomCode(8);
// e.g. "7KMW3QPX"