import { isBigIntStringValid, isEmailValid, isIntegerValid, isStringValid } from 'jg-utilities';isStringValid(name, 1, 100); // a non-blank string of 1 to 100 charactersisIntegerValid(page, 1); // an integer of at least 1isEmailValid(email); // a valid address without a forbidden extensionisBigIntStringValid(id, 1n); // a positive integer string that fits PostgreSQL's BIGINT
Format values for people:
ts
import { escapeHtml, pluralize, prettifyDate, prettifyFileSize, prettifyNumber, toSlug,} from 'jg-utilities';prettifyNumber(1234567.891); // '1,234,567.89'prettifyNumber(-5, { prefix: '$' }); // '-$5'prettifyFileSize(1536); // '1.50 kB'prettifyDate(createdAt, 'date-medium'); // 'October 17, 2026'toSlug('Héllo, World! 2026'); // 'hello-world-2026'`${count} ${pluralize(count, 'file')}`; // '1 file', '3 files'escapeHtml(name); // safe to put into HTML, such as an email's body
Send someone back where they were after signing in, only within the site and never back to the sign-in page:
Keep secrets out of logs, and read the YouTube links people paste:
ts
import { parseYouTubeVideoId, redactSecrets, redactUrlQueryParams, toYouTubeEmbedUrl,} from 'jg-utilities';redactUrlQueryParams(request.url, ['token']); // the path alone when the query holds a tokenredactSecrets(logLine).text; // GitHub tokens, sk- keys, and private keys become [redacted]const videoId = parseYouTubeVideoId(pastedUrl); // the id, or null for anything but one videoconst embedUrl = videoId === null ? undefined : toYouTubeEmbedUrl(videoId);
Generate secure random values, IDs, and timestamps:
ts
import { createTimestamp, generateRandomString, generateUuid } from 'jg-utilities';generateRandomString(16); // drawn with crypto.getRandomValues, fit for secretsgenerateUuid(7); // a time-ordered UUIDcreateTimestamp(); // '2026-10-17T08:30:00.000Z'
Work with UTC calendar dates, such as the days a report covers, and fit text into a byte budget, such as a push notification’s payload limit:
ts
import { generateUtcDateSequence, getTrailingUtcDateRange, toUtcDate, truncateToUtf8Bytes,} from 'jg-utilities';toUtcDate(createdAt); // '2026-10-17', the UTC day of an instantconst range = getTrailingUtcDateRange(30); // the last 30 UTC days, today includedgenerateUtcDateSequence(range); // one 'YYYY-MM-DD' per day, oldest firsttruncateToUtf8Bytes(body, 2048); // whole code points only, at most 2048 bytes in UTF-8
Run side effects away from the code that triggers them with an in-process event bus:
ts
import { createEventBus } from 'jg-utilities';type IAppEvents = { userSignedUp: { userId: string } };const events = createEventBus<IAppEvents>({ onError: (error) => logger.error(error) });events.subscribe('userSignedUp', ({ userId }) => sendWelcomeEmail(userId));events.emit('userSignedUp', { userId }); // returns at once; each handler runs later, on its own
Paginate with opaque cursors, and wait or retry with timing in seconds:
Give each list a cursor codec that validates its cursors and accepts them only for the filters they were made for, and keep a rate-limited API’s requests within its limits:
ts
import { createCursorCodec, createRequestCoordinator } from 'jg-utilities';const paymentCursors = createCursorCodec({ schema: PaymentCursorSchema }); // a Zod schema, sayconst after = paymentCursors.decode(request.query.cursor, { status }); // INVALID_CURSOR otherwiseconst github = createRequestCoordinator({ maxConcurrent: 4, maxQueued: 64 });const issues = await github.run(() => fetchIssues(), { caller: installationId, maxQueuedForCaller: 8,});github.deferUntil(retryAt); // queued requests wait out a Retry-After, without holding a slot
Generated from TSDoc
API reference
Every export of [email protected], 125 in all, with its signature, its errors, and an example.
Applies substitutions to a string based on a provided object. The string can contain placeholders in the format of {{key}}, which will be replaced by the corresponding value from the substitutions object. If a placeholder does not have a corresponding key in the substitutions object, it will remain unchanged in the output string.
The input string containing placeholders in the format of {{key}}.
substitutions?Record<string, unknown>
No description.
Defaults to {}.
options?ISubstitutionOptions
No description.
Defaults to ....
Returns
string
A string with the placeholders replaced by their corresponding values from the substitutions object.
Example
import { applySubstitutions } from 'jg-utilities';applySubstitutions('Hi {{name}}, you have {{count}} messages.', { name: 'Ada', count: 3 }); // => 'Hi Ada, you have 3 messages.'
Creates a codec for a list's pagination cursors: encode validates a continuation, such as the last row's sort keys and the list's filters, with the schema and encodes it as an opaque base64url cursor; decode reverses that for a cursor a client sent, and accepts it only for the list it came from. Without matchesScope, every key of the scope must equal the payload's.
decode returns undefined for a missing cursor, meaning the first page, and throws INVALID_CURSOR for any cursor it refuses, with data.reason saying whether its encoding, its shape, or its scope was wrong, and the schema's own error, if any, as cause: for a client, a cursor that fails its schema is the same mistake as a malformed one.
Creates an in-process event bus that decouples the code that emits an event from the handlers that react to it, such as sending a welcome email after a sign-up. emit returns at once, and each event is delivered later, in the order it was emitted. Each handler runs on its own, so one that throws or rejects doesn't stop the others: its error goes to onError. A handler unsubscribed before it's called isn't called, and subscribing the same handler twice to one event registers it once. Declare the event map with type, because an interface has no index signature.
Creates a coordinator that runs tasks, such as requests to a rate-limited API, at most maxConcurrent at a time, and queues the rest, first in, first out, up to maxQueued. A caller can be capped too, with caller and maxQueuedForCaller, so one caller can't fill the queue.
deferUntil starts a cooldown, such as from a Retry-After header: queued tasks wait until it ends, without holding a slot, and tasks run with rejectDuringCooldown reject at once instead. A task whose signal aborts while it waits leaves the queue. A task's own result or error passes through unchanged, and its slot is released either way. No timer is left once nothing waits.
run rejects with REQUEST_QUEUE_FULL when the queue, or the caller's share of it, is full, with REQUEST_COOLDOWN_ACTIVE when the task may not wait out a cooldown, with REQUEST_COORDINATOR_CLOSED after close, and with INVALID_QUEUE_LIMIT for a maxQueuedForCaller that isn't an integer of at least 0. deferUntil throws INVALID_DATE for a deadline that isn't a valid date.
Creates an ISO 8601 timestamp, in UTC, from a date, a millisecond epoch, or a date string, or from the current time when no value is given.
Signature
createTimestamp(value?: string | number | Date): string
Parameters
value?string | number | Date
The moment to format. Defaults to now.
Returns
string
The timestamp, such as 2026-10-17T08:30:00.000Z.
Throws
ErrorInvalid time value
Example
import { createTimestamp } from 'jg-utilities';createTimestamp(); // the current time, such as '2026-10-17T08:30:00.000Z'createTimestamp(0); // => '1970-01-01T00:00:00.000Z'
Decodes a base64url value, without padding, into the UTF-8 string it encodes. Only the canonical encoding of a string is accepted, so every string has exactly one valid encoding.
Signature
decodeBase64Url(value: string): string
Parameters
valuestring
The base64url value to decode.
Returns
string
The decoded string.
Throws
INVALID_BASE64URLif the value isn't a string of base64url characters with a valid length.
INVALID_BASE64URLif the decoded bytes aren't valid UTF-8.
INVALID_BASE64URLif the value isn't the canonical encoding of its result.
Example
import { decodeBase64Url } from 'jg-utilities';decodeBase64Url('eyJwYWdlIjoyfQ'); // => '{"page":2}'
Decodes a cursor made by encodeCursor. The cursor comes from a client, so it's validated before decoding: a non-empty base64url string no longer than maxLength. The caller still validates the decoded value's shape, such as with a schema.
Escapes text for HTML, so it shows as written instead of becoming markup, such as a name in an email template or a Telegram message. It's safe in element content and quoted attribute values, not in a URL, a script, or a style.
Signature
escapeHtml(text: string): string
Parameters
textstring
The text.
Returns
string
The escaped text.
Example
import { escapeHtml } from 'jg-utilities';escapeHtml('<b>"Tom" & Jerry\'s</b>'); // => '<b>"Tom" & Jerry's</b>'
Generates a string of characters picked at random from a set. Each character is drawn with crypto.getRandomValues and rejection sampling, so every character in the set is equally likely and the result is fit for secrets such as passwords.
The characters to pick from. Defaults to ASCII letters and digits.
Defaults to 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'.
Returns
string
The random string, or an empty string when the length or the set is empty.
Example
import { generateRandomString } from 'jg-utilities';generateRandomString(12); // such as 'aZ3kPq9TbW0x'generateRandomString(6, '0123456789'); // such as '402913'
Lists every UTC calendar date in an inclusive range, in ascending order, such as the days of a chart's axis. It gives one entry per day, so bound the range before passing one from a client.
import { generateUuid } from 'jg-utilities';generateUuid(4); // such as '9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d'generateUuid(7); // time-ordered, so later IDs sort after earlier ones
Gets the value of a property from the last item when the array length reaches the initial page size.
Signature
getNextPageParam<T extends object, K extends string | number | symbol>( propName: K, items: T[], initialPageSize: number,): T[K] | undefined
Parameters
propNameK
The property name to read from the last item.
itemsT[]
The array of items from which to read the last property value.
initialPageSizenumber
The expected page size required before reading the last property value.
Returns
T[K] | undefined
The last item's property value, or undefined when the array length is below the initial page size.
Example
import { getNextPageParam } from 'jg-utilities';// with a page size of 2, a full page means there may be moregetNextPageParam('id', [{ id: 1 }, { id: 2 }], 2); // => 2getNextPageParam('id', [{ id: 3 }], 2); // => undefined
Checks whether a global navigator object exists. It reports presence only and doesn't identify a browser: Node.js 21 and later define a global navigator too.
Signature
hasNavigator(): boolean
Returns
boolean
True when the navigator global is defined.
Example
import { hasNavigator } from 'jg-utilities';hasNavigator(); // true in browsers and in Node.js 21 or later
Verifies if a value has a valid Authorization Header format based on the RFC6750. Example: Authorization: Bearer eyJhbGciOiJIUzI1NiIXVCJ9TJV...r7E20RMHrHDcEfxjoYZgeFONFh7HgQ More info: - https://stackoverflow.com/questions/33265812/best-http-authorization-header-type-for-jwt - https://www.rfc-editor.org/rfc/rfc6750
Signature
isAuthorizationHeaderValid(value: unknown): value is string
Parameters
valueunknown
The value to be validated.
Returns
value is string
A boolean indicating whether the value is a valid Authorization header.
Verifies that a value is an integer written in plain decimal digits, as PostgreSQL returns a BIGINT, and that it fits a range: a signed 64-bit integer by default. Leading zeros, a plus sign, -0, decimals, exponents, and padding are rejected, so one number has one spelling.
Signature
isBigIntStringValid(value: unknown, min?: bigint, max?: bigint): value is string
Parameters
valueunknown
The value to be validated.
min?bigint
No description.
Defaults to INT64_MIN.
max?bigint
No description.
Defaults to INT64_MAX.
Returns
value is string
A boolean indicating whether the value is such a string, within the range.
Verifies if a value is a valid email address with a maximum length of 320 characters. Important: when providing forbiddenExtensions, ensure to include the dot (.) at the beginning. For example, to forbid .con, use ['.con'].
Signature
isEmailValid(value: unknown, forbiddenExtensions?: string[]): value is string
Parameters
valueunknown
The value to be validated.
forbiddenExtensions?string[]
No description.
Defaults to ....
Returns
value is string
A boolean indicating whether the value is a valid email address and does not have a forbidden extension.
Compares two values. Primitives, null, and undefined are compared with Object.is, so NaN equals NaN and 0 doesn't equal -0. Two objects or arrays are compared deeply, ignoring key order, through their deterministic JSON. An object never equals a primitive.
Verifies if a value is a file name that is valid on Windows, macOS and Linux: one path component whose UTF-8 size is between 1 and maxBytes. It rejects ".", "..", control characters, the characters < > : " / \ | ? *, a trailing space or period, and Windows device names such as "con" or "lpt1.txt", in any letter case.
Signature
isFileNameValid(value: unknown, maxBytes?: number): value is string
Parameters
valueunknown
The value to be validated.
maxBytes?number
No description.
Defaults to MAX_FILE_NAME_BYTES.
Returns
value is string
A boolean indicating whether the value is a portable file name.
Verifies if a value is a valid integer and is within a range (optional). If a range is not provided, it will use the properties Number.MIN_SAFE_INTEGER & Number.MAX_SAFE_INTEGER.
Signature
isIntegerValid(value: unknown, min?: number, max?: number): value is number
Parameters
valueunknown
The value to be validated.
min?number
No description.
max?number
No description.
Returns
value is number
A boolean indicating whether the value is a valid integer and within the specified range.
Verifies if a value is a valid number and is within a range (optional). The minimum value defaults to Number.MIN_SAFE_INTEGER (-9007199254740991) while the maximum value defaults to Number.MAX_SAFE_INTEGER (9007199254740991).
Signature
isNumberValid(value: unknown, min?: number, max?: number): value is number
Parameters
valueunknown
The value to be validated.
min?number
No description.
Defaults to Number.MIN_SAFE_INTEGER.
max?number
No description.
Defaults to Number.MAX_SAFE_INTEGER.
Returns
value is number
A boolean indicating whether the value is a valid number and within the specified range.
Verifies if a password meets the following requirements: - Meets a length range (Defaults to 8 - 2048) - At least one uppercase letter - At least one lowercase letter - At least one number - At least one special character
Signature
isPasswordValid( value: unknown, minLength?: number, maxLength?: number,): value is string
Parameters
valueunknown
The value to be validated.
minLength?number
No description.
Defaults to 8.
maxLength?number
No description.
Defaults to 2048.
Returns
value is string
A boolean indicating whether the value is a valid password.
Verifies if a value is a valid unix timestamp in milliseconds. The smallest value is set for the beginning of the Unix epoch (January 1st, 1970 - 14400000) while the largest value is based on the numeric limit established by JavaScript (9007199254740991).
Signature
isTimestampValid(value: unknown): value is number
Parameters
valueunknown
The value to be validated.
Returns
value is number
A boolean indicating whether the value is a valid unix timestamp in milliseconds.
Normalizes a query string by removing control characters, zero-width characters, and extra spaces, then converts it to lowercase. Optionally, it can truncate the string to a specified maximum length.
Reads the id of the single YouTube video a URL points at, such as one a user pasted. It accepts https URLs without credentials or a port, on these hosts and paths, and ignores the query, apart from v, and the hash: - youtu.be/<id> - youtube.com/watch?v=<id>, /shorts/<id>, /embed/<id>, and /live/<id> - m.youtube.com/watch?v=<id> - youtube-nocookie.com/embed/<id>
The www. form of each host works too. Anything else, such as a playlist, a channel, or a value that isn't a URL, returns null rather than throwing.
Checks whether the user asked the system to reduce motion. The view defaults to the global window when one exists; pass another one, such as an element's ownerDocument.defaultView, to check a different window.
Formats the number that will be inserted in a badge so it doesn't take too much space. If the current count is 0, it returns undefined as the badge shouldn't be displayed.
Formats a date instance based on a template. The output is always en-US, whatever the runtime's locale, and hours have no leading zero. - date-short: 12/05/2024 (Default) - date-medium: December 5, 2024 - date-long: Thursday, December 5, 2024 - time-short: 9:05 PM - time-medium: 9:05:20 PM - datetime-short: 12/5/2024, 9:05 PM - datetime-medium: December 5, 2024 at 9:05 PM - datetime-long: Thursday, December 5, 2024 at 9:05:20 PM
Without a time zone, the runtime's local zone is used, so a server and a browser can format the same instant differently. Pass 'UTC' for calendar dates and for data defined in UTC periods, and an explicit time zone for server-rendered output. Templates with a time end with the zone's short name when a time zone is given, for example "December 5, 2024 at 11:30 PM UTC". Prefer date-medium, datetime-medium and time-medium in UI text over the numeric templates.
Formats a bytes value into a human readable format. Bigints, such as BIGINT database values, are accepted too; values beyond Number.MAX_SAFE_INTEGER are formatted without losing precision.
Signature
prettifyFileSize(value: number | bigint, decimalPlaces?: number): string
Parameters
valuenumber | bigint
The bytes value to be formatted, as a number or a bigint.
decimalPlaces?number
The number of decimal places to include. Defaults to 2.
Replaces the secrets in free text, such as a log line or a file headed for an AI model: complete PEM private key blocks first, then every match of the token patterns, which default to SECRET_TOKEN_PATTERNS. It runs in linear time over the key blocks, matches each pattern globally whatever its flags, and never throws for any text, so a logger can call it safely.
With preserveLineBreaks, a private key becomes the replacement followed by its line breaks, or preceded by them when it ends the text, so line numbers in the text stay the same.
Removes a URL's query and fragment when the query holds any of the named parameters, such as a token, so the URL can be logged. A name matches exactly, after decoding, or as the start of an array parameter, such as token[]. A name that can't be decoded could hide one of them, so the whole URL becomes [redacted], as does a URL that is only a query.
Checks an untrusted path, such as a returnTo parameter after sign-in, and returns it only when it stays on the same site and outside the excluded paths, so a link can't send someone to another site or back into a sign-in loop. It must start with one /; a protocol-relative URL, an absolute URL, a backslash, or a path that can't be decoded is rejected. An excluded path matches itself and everything under it, by whole segments, case-insensitively, and after decoding, as a router matches routes.
Executes an asynchronous function persistently, retrying on error with incremental delays defined in retryScheduleDuration (seconds). When the signal aborts, the returned promise rejects immediately, even while an attempt is pending. The attempt's own work isn't cancelled; pass the signal into it as well when it supports one.
Creates a shallow copy of the input array and shuffles it with the Fisher-Yates algorithm, drawing each position with crypto.getRandomValues, so every order is equally likely.
Signature
shuffleArray<T>(input: T[]): T[]
Parameters
inputT[]
The array to be shuffled.
Returns
T[]
A new array with the values of the input array in a random order.
Throws
INVALID_OR_EMPTY_ARRAYif the input is not array or it is empty
Example
import { shuffleArray } from 'jg-utilities';shuffleArray([1, 2, 3, 4]); // a new array, such as [3, 1, 4, 2]
The value to create a Date instance from. It can be a Date object, a timestamp, or a date string.
Returns
Date
A Date instance.
Example
import { toDate } from 'jg-utilities';toDate('2026-10-17T08:30:00.000Z'); // a Date for that instanttoDate(1_792_217_505_000); // a Date from a millisecond epoch
Converts a time string into milliseconds. The time string should be in the format of "{value} {unit}", where the value is a number and the unit can be milliseconds, seconds, minutes, hours, days, weeks, months, or years. For example: "2 days", "5 minutes", "2 hours".
Converts a string value into a slug, meeting the following requirements: - lowercase - removes accents/diacritics - replaces spaces and separators with "-" - removes invalid URL characters - collapses repeated "-" - trims leading/trailing "-"
Signature
toSlug(value: string): string
Parameters
valuestring
The string value to convert.
Returns
string
A string converted to a slug.
Throws
UNABLE_TO_SLUGIFY_STRINGif the resulting slug is empty after processing the input string
Gives the UTC calendar date of an instant, as YYYY-MM-DD, such as the day a UTC report or analytics period counts it in. The instant must fall in the years 0000 to 9999.
Signature
toUtcDate(value?: IDateValue): string
Parameters
value?IDateValue
No description.
Returns
string
The UTC date, such as 2026-10-17.
Throws
INVALID_DATEThe value is not a valid date in the years 0000 to 9999.
Example
import { toUtcDate } from 'jg-utilities';toUtcDate('2026-10-17T23:30:00.000-05:00'); // => '2026-10-18'toUtcDate(0); // => '1970-01-01'toUtcDate(); // today's UTC date
Shortens a string to the longest prefix of whole code points that fits a number of UTF-8 bytes, such as a push notification's body under the provider's payload limit. A character made of several code points, such as an emoji with a skin tone, can lose its last ones, but a code point is never split.
Probes a condition until it returns a value other than null, undefined or false, and resolves with that value. It probes immediately, then after every interval, and one final time at the deadline. The timeout bounds the whole call, so it rejects even while a probe is still pending. A running probe isn't cancelled when the call stops waiting; pass your own signal into it when it supports one.
The single-line secrets redactSecrets finds by default: GitHub's personal, OAuth, user, server, and refresh tokens, its fine-grained personal access tokens, and sk- API keys, such as OpenAI's, each with at least 20 characters after its prefix.
validates the payload when it's encoded and when a cursor is decoded
maxLengthproperty
number
the longest cursor, in characters, that's encoded or decoded. Defaults to 2048
matchesScopeproperty
(payload: TPayload, scope: TScope) => boolean
Whether a decoded payload belongs to the list being read. Without it, every key of the scope must equal the payload's, with null and undefined counting as the same value.
Date Template A date can be prettified by choosing a template that meets the user's requirements. - date-short: 12/05/2024 - date-medium: December 5, 2024 - date-long: Thursday, December 5, 2024 - time-short: 9:05 PM - time-medium: 9:05:20 PM - datetime-short: 12/5/2024, 9:05 PM - datetime-medium: December 5, 2024 at 9:05 PM - datetime-long: Thursday, December 5, 2024 at 9:05:20 PM
A value that survives a trip through JSON. An object's values may also be undefined, because JSON.stringify drops them, so an object with optional fields fits. Interfaces and classes don't fit, because they have no index signature: pass an interface-typed value as { ...value }, or declare its type with type.
Declaration
type IJsonValue = string | number | boolean | null | IJsonValue[] | { [key: string]: IJsonValue | undefined;};