Shared utilities: FP helpers, formatting, slugs, caching, and logging.

Functional Programming

Curried utilities for data transformation: pipe, filter, map, reduce, compact, unique, and more.

Formatting

Currency formatting, phone normalization, markdown rendering, and timezone-aware date/time display.

Caching

TTL and LRU caches with a global registry for admin stats.

Functions

f
addPendingWork(p: Promise<unknown>): void

Queue a promise that must complete before the response is sent

f
asString(value: unknown): string

Narrow an unknown value to string, defaulting to "" if not a string. Replaces typeof x === "string" ? x : "" at type boundaries.

f
bracket<R>(
acquire: () => R | Promise<R>,
release: (r: R) => void | Promise<void>
)

Resource management pattern (like Haskell's bracket or try-with-resources). Ensures cleanup happens even if the operation throws.

f
chunk(size: number)

Split an array into chunks of a given size

f
collectionCache<T>(
fetchAll: () => Promise<T[]>,
ttlMs: number,
now?: () => number
): CollectionCache<T>

Create an in-memory collection cache with TTL. Loads all items via fetchAll on first access or after invalidation/expiry, then serves from memory until the TTL expires or invalidate() is called. Accepts an optional clock function for testing.

f
compact<T>(array: (T | null | undefined)[]): T[]

Remove null and undefined values from array

f
createRequestTimer(): (() => number)

Create a request timer for measuring duration

f
flushPendingWork(): Promise<void>

Await all queued work. Call before returning the response.

f
formatCurrency(minorUnits: number | string): string

Format an amount in minor units (pence/cents) as a currency string. e.g. formatCurrency(1050) → "£10.50" (when currency is GBP)

f
formatDatetimeInTz(
utcIso: string,
tz: string
): string

Format a UTC ISO datetime string for display in the given timezone. Returns e.g. "Monday 15 June 2026 at 14:00 BST"

f
formatDatetimeShortInTz(
utcIso: string,
tz: string
): string

Compact format for table cells: "yyyy-MM-dd HH:mm" in the given timezone. Delegates to the browser-compatible formatIsoForPreview helper so the same formatting runs on the server and in the admin JS bundle.

f
formatErrorMessage(context: ErrorContext): string

Format an error context into a human-readable activity log message

f
formatRequestError(
method: string,
path: string,
error: unknown
): string

Format an error detail string with request context and error message

f
generateSlug(): string

Generate a random slug with at least 2 digits and 2 letters. Uses Fisher-Yates shuffle on the fixed positions to avoid bias.

f
getAllCacheStats(): CacheStat[]

Collect stats from all registered caches

f
getDecimalPlaces(currencyCode: string): number

Get the number of decimal places for a currency code

f
getRequestId(): string

Get the current request ID, or empty string if outside request context

f
hasPendingWorkScope(): boolean

True when running inside a runWithPendingWork scope (i.e. a request).

f
isValidDatetime(value: string): boolean

Check if a naive datetime-local string is a parseable datetime. Does not interpret timezone — purely a format check.

f
isValidTimezone(tz: string): boolean

Validate that a string is a valid IANA timezone identifier.

f
lazyRef<T>(fn: () => T): [() => T, (value: T | null) => void]

Resettable lazy reference - like once() but can be reset for testing. Returns [get, set] tuple where set(null) resets to uncomputed state.

f
localToUtc(
naive: string,
tz: string
): string

Convert a naive datetime-local value (YYYY-MM-DDTHH:MM) to a UTC ISO string, interpreting the value as local time in the given timezone.

f
logDebug(
category: LogCategory,
message: string
): void

Log a debug message with category prefix For detailed debugging during development

f
logError(context: ErrorContext): void

Log a classified error to console.error and persist to the activity log. Console output uses error codes and safe metadata (never PII). Activity log entry is encrypted and visible to admins on the log pages.

f
logErrorLocal(context: ErrorContext): void

Log a classified error to console.error only (no ntfy, no activity log). Use this where calling logError would cause infinite recursion (e.g. ntfy.ts).

f
logRequest(entry: RequestLogEntry): void

Log a completed request to console.debug Path is automatically redacted for privacy

f
mapParallel<T, U>(fn: (item: T) => Promise<U>)

Map over a promise-returning function in parallel (Promise.all)

f
normalizePhone(
phone: string,
prefix: string
): string

Strip non-numeric characters from a phone number and normalize to +{prefix}{local}

f
normalizeSlug(input: string): string

Normalize a user-provided slug: trim, lowercase, replace spaces with hyphens

f
now(): Date

Current time as a Date

f
nowIso(): string

Full ISO-8601 timestamp for created/logged_at fields

f
nowMs(): number

Epoch milliseconds for numeric comparisons

f
once<T>(fn: () => T): (() => T)

Lazy evaluation - compute once on first call, cache forever. Use instead of let x = null; const getX = () => x ??= compute();

f
pipe(...fns: Array<(arg: unknown) => unknown>): (value: unknown) => unknown
2 overloads

Compose functions left-to-right (pipe) Uses recursive conditional types for arbitrary-length type safety.

f
redactPath(path: string): string

Redact dynamic segments from paths for privacy-safe logging Replaces:

f
registerCache(provider: CacheStatProvider): void

Register a cache stat provider (called at module load time)

f
renderMarkdown(text: string): string

Render markdown to HTML (block-level: paragraphs, lists, etc.). Raw HTML is escaped.

f
resetCacheRegistry(): void

Reset the registry (for testing)

f
runWithPendingWork<T>(fn: () => T): T

Run a function within a pending-work scope

f
runWithRequestId<T>(fn: () => T): T

Run a function with a request-scoped random ID for log correlation

f
setSuppressDebugLogs(value: boolean | null): void

Set module-level debug log suppression (avoids env race in parallel tests).

f
setSuppressRequestLogs(value: boolean | null): void

Set module-level request log suppression (avoids env race in parallel tests).

f
sort<T>(comparator: (
a: T,
b: T
) => number
)

Non-mutating sort with comparator

f
todayInTz(tz: string): string

Get today's date as YYYY-MM-DD in the given timezone.

f
toMajorUnits(minorUnits: number): string

Convert minor units to major units string for form display. e.g. toMajorUnits(1050) → "10.50" (for GBP)

f
toMinorUnits(majorUnits: number): number

Convert major units (decimal) to minor units (integer). e.g. toMinorUnits(10.50) → 1050 (for GBP)

f
ttlCache<K, V>(
ttlMs: number,
now?: () => number
): TtlCache<K, V>

Create a TTL (Time-To-Live) cache. Entries expire after ttlMs milliseconds. Accepts an optional clock function for testing.

f
unique<T>(array: T[]): T[]

Remove duplicate values (by reference/value equality)

f
uniqueBy<T>(fn: (item: T) => unknown)

Remove duplicates by a key function

f
utcToLocalInput(
utcIso: string,
tz: string
): string

Convert a UTC ISO datetime string to a datetime-local input value (YYYY-MM-DDTHH:MM) in the given timezone. Used for pre-populating form inputs with timezone-adjusted values.

f
validatePrice(
raw: string,
minPrice: number,
maxPrice: number
): PriceResult

Validate and convert a raw price string to minor units. Returns ok with 0 if raw is empty and minPrice is 0 (pay-what-you-want with no input). Returns error if raw is empty and minPrice > 0, or if parsed value is out of range.

f
validateSlug(slug: string): string | null

Validate a normalized slug. Returns error message or null.

Type Aliases

Variables

v
DEFAULT_TIMEZONE: "Europe/London"

Default timezone when none is configured

v
ErrorCode: [K in keyof ErrorDefs]: ErrorDefs[K][0]

Error code strings for use in logError calls

v
errorCodeLabel: Record<ErrorCodeType, string>

Human-readable labels for error codes (shown in admin activity log)

v
joinStrings

Join an array of strings into a single string (curried reduce shorthand). Replaces the common pattern: reduce((acc: string, s: string) => acc + s, "")