@repo/utils provides shared non-React utility functions. Code is organized into subpath exports: @repo/utils/time (timestamp formatting and date helpers using date-fns and @internationalized/date), @repo/utils/constants, @repo/utils/labels (human-readable labels), @repo/utils/objects, and @repo/utils/async.
graph TD
utils["@repo/utils"] --> db["@repo/db"]
utils --> errors["@repo/errors"]
utils -.-> typescript_config["@repo/typescript-config"]
utils -.-> vitest_config["@repo/vitest-config"]@repo/utils/time — timestamp formatting and date helpersformatTimestampFormats a date/timestamp into a string using named format keys. Returns a placeholder ("—" by default) when the value is falsy or invalid.
import { formatTimestamp } from "@repo/utils/time";
formatTimestamp(new Date());
// => "2026-03-05 12:00:00" (default: SQL_DATETIME)
formatTimestamp(new Date(), { format: "DISPLAY_DATE_LONG" });
// => "March 5, 2026"
formatTimestamp(new Date(), { format: "DISPLAY_DATETIME_SHORT" });
// => "Mar 5, 2026, 12:00 PM"
formatTimestamp(new Date(), { format: "FEED_DATETIME" });
// => "Mar 5 at 12:00 PM"
Handling missing values:
formatTimestamp(undefined);
// => "—" (EMPTY_VALUE_LABEL)
formatTimestamp(null, { placeholder: "No date" });
// => "No date"
formatTimestamp(null, { placeholder: null });
// => null (useful for conditional rendering)
Available format keys:
| Key | Example output | Use case |
|---|---|---|
SQL_DATETIME (default) |
2026-03-05 12:00:00 |
Storage, APIs |
ISO_DATETIME |
2026-03-05T12:00:00 |
ISO 8601 |
ISO_DATE |
2026-03-05 |
Date-only storage |
ISO_TIME |
12:00:00 |
Time-only storage |
SORTABLE_DATETIME |
20260305_120000 |
Filenames, sorting |
DISPLAY_DATE_LONG |
March 5, 2026 |
Full date in UI |
DISPLAY_DATE_SHORT |
Mar 5, 2026 |
Compact date in UI |
DISPLAY_DATE_NUMERIC |
03/05/2026 |
Numeric date in UI |
DISPLAY_TIME |
12:00 PM |
Time in UI |
DISPLAY_TIME_WITH_SECONDS |
12:00:00 PM |
Precise time in UI |
DISPLAY_DATETIME_LONG |
March 5, 2026 at 12:00 PM |
Full datetime in UI |
DISPLAY_DATETIME_SHORT |
Mar 5, 2026, 12:00 PM |
Compact datetime in UI |
DISPLAY_DATETIME_COMPACT |
03/05/26, 12:00 PM |
Minimal datetime |
LOG_TIMESTAMP |
2026-03-05 12:00:00.000 |
Log files |
AUDIT_DATETIME |
2026-03-05T12:00:00.000 |
Audit trails |
FEED_DATETIME |
Mar 5 at 12:00 PM |
Activity feeds (current year) |
FEED_DATETIME_WITH_YEAR |
Mar 5, 2026 at 12:00 PM |
Activity feeds (cross-year) |
MONTH_YEAR |
March 2026 |
Month headers |
MONTH_YEAR_SHORT |
Mar '26 |
Compact month headers |
DAY_MONTH |
Thursday, March 5 |
Day-of-week display |
dateStringToCalendarDate / dateValueToStringConvert between ISO date strings and @internationalized/date CalendarDate objects:
import { dateStringToCalendarDate, dateValueToString } from "@repo/utils/time";
const calendarDate = dateStringToCalendarDate("2026-03-05");
// => CalendarDate { year: 2026, month: 3, day: 5 }
const isoString = dateValueToString({ year: 2026, month: 3, day: 5 });
// => "2026-03-05"
@repo/utils/labels — human-readable labelsgetPersonLabelBuilds a display name from person data. Accepts camelCase objects, snake_case objects, or positional arguments:
import { getPersonLabel } from "@repo/utils/labels";
getPersonLabel({ firstName: "Jane", lastName: "Doe" });
// => "Jane Doe"
getPersonLabel({ first_name: "Jane", last_name: "Doe" });
// => "Jane Doe" (snake_case works too)
getPersonLabel("Jane", "Doe", "MD");
// => "Jane Doe, MD"
getPersonLabel({ firstName: "Jane", lastName: undefined });
// => "Jane"
getPersonLabel(undefined, undefined);
// => null
capitalizeimport { capitalize } from "@repo/utils/labels";
capitalize("hello");
// => "Hello"
@repo/utils/objects/* — object helperscompactObjectRemoves falsy values from an object (useful for building query params or optional payloads):
import { compactObject } from "@repo/utils/objects/compactObject";
compactObject({ name: "Jane", email: "", phone: undefined, active: true });
// => { name: "Jane", active: true }
@repo/utils/async/* — async helperswaitPromise-based delay:
import { wait } from "@repo/utils/async/wait";
await wait(1000); // pause for 1 second
mapWithConcurrencyRuns a worker over a list with at most limit in flight, resolving to the results in input order:
import { mapWithConcurrency } from "@repo/utils/async/mapWithConcurrency";
const pages = await mapWithConcurrency(urls, 8, (url) => fetchPage(url));
retryWhile / exponentialBackoffMsRetries on the returned value rather than a throw, so it pairs with safe():
import { exponentialBackoffMs, retryWhile } from "@repo/utils/async/retry";
import { MS_PER_SECOND } from "@repo/utils/time";
const { result, attempts } = await retryWhile(() => safe(() => fetch(url)), {
maxAttempts: 3,
shouldRetry: (sent) => sent.error !== null || isTransientHttpStatus(sent.data.status),
delayMs: (attemptsSoFar) => exponentialBackoffMs(attemptsSoFar, MS_PER_SECOND, 250),
});
@repo/utils/http — HTTP helpersisTransientHttpStatus (429 or 5xx), isFetchTimeout (the rejection AbortSignal.timeout() gives a fetch) and retryAfterMs, which reads a Retry-After header in seconds or as a date. It returns the full wait, so a caller with a limit decides whether to wait or give up:
import { isFetchTimeout, isTransientHttpStatus, retryAfterMs } from "@repo/utils/http";
isTransientHttpStatus(503); // true
isFetchTimeout(error); // true when AbortSignal.timeout() cut the request off
retryAfterMs(response.headers); // 3000 for "Retry-After: 3", null when absent
@repo/utils/validation — schema-issue loggingdescribeIssues turns zod issues into path: code (expected type) lines with no values, safe to log for PHI payloads:
import { describeIssues } from "@repo/utils/validation";
throw new VendorResponseError("invalid-response").with({ issues: describeIssues(parsed.error.issues) });
// issues: ["result.vitals.height: invalid_type (expected string)"]
@repo/utils/constants — shared constantsPlaceholder strings for consistent UI rendering when values are missing:
import { EMPTY_VALUE_LABEL, UNKNOWN_VALUE_LABEL, N_A_VALUE_LABEL } from "@repo/utils/constants";
EMPTY_VALUE_LABEL; // "—"
UNKNOWN_VALUE_LABEL; // "Unknown"
N_A_VALUE_LABEL; // "N/A"
EMPTY_VALUE_LABEL is the default placeholder used by formatTimestamp when a value is falsy.
@repo/utils/dom — browser event and layout helpersfindVerticalScrollParentThe nearest ancestor whose overflow-y scrolls and whose content overflows it, or null when only the page scrolls. Measure against it to know whether an element fits in the pane that will scroll it.
const paneHeight = findVerticalScrollParent(row)?.clientHeight ?? document.documentElement.clientHeight;
isComposingKeyEventWhether a key event belongs to an IME composition: the event's isComposing flag, or the key code Safari sends for the Enter that ends one. Check it before treating Enter or Escape in a text field as an action.
if (event.key === "Enter" && !isComposingKeyEvent(event)) {
save();
}
keepTextEntryFocusedFor a control's onMouseDown: prevents the default while an input or textarea is focused, so pressing a toolbar button does not blur an editor shut.
<HStack onMouseDown={keepTextEntryFocused}>…</HStack>
| Script | Description |
|---|---|
test |
Runs Vitest with coverage. |
test:watch |
Runs Vitest in watch mode. |
check-types |
Runs tsc --noEmit to typecheck the package. |