# jg-utilities

> Validations, formatters, secure random generators, pagination cursors, and collection, async, and environment helpers.

- **Version:** `1.4.0`
- **Runtimes:** Universal
- **Install:** `npm install jg-utilities`
- **On npm:** [jg-utilities](https://www.npmjs.com/package/jg-utilities)
- **Source:** [packages/jg-utilities](https://github.com/jesusgraterol/jg-packages/tree/main/packages/jg-utilities)
- **Recipes:** [jg-utilities recipes](https://jg-packages.jesusgraterol.dev/packages/jg-utilities/recipes/index.md)

## Usage

Validate input before using it:

```ts
import { isBigIntStringValid, isEmailValid, isIntegerValid, isStringValid } from 'jg-utilities';

isStringValid(name, 1, 100); // a non-blank string of 1 to 100 characters
isIntegerValid(page, 1); // an integer of at least 1
isEmailValid(email); // a valid address without a forbidden extension
isBigIntStringValid(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:

```ts
import { resolveSafeInternalPath } from 'jg-utilities';

const returnTo = resolveSafeInternalPath(searchParams.get('returnTo'), ['/sign-in']) ?? '/';
```

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 token
redactSecrets(logLine).text; // GitHub tokens, sk- keys, and private keys become [redacted]

const videoId = parseYouTubeVideoId(pastedUrl); // the id, or null for anything but one video
const 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 secrets
generateUuid(7); // a time-ordered UUID
createTimestamp(); // '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 instant
const range = getTrailingUtcDateRange(30); // the last 30 UTC days, today included
generateUtcDateSequence(range); // one 'YYYY-MM-DD' per day, oldest first
truncateToUtf8Bytes(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:

```ts
import { decodeCursor, encodeCursor, retryAsyncFunction, waitUntil } from 'jg-utilities';

const cursor = encodeCursor({ createdAt: lastRow.createdAt, id: lastRow.id });
const continuation = decodeCursor(cursorFromClient); // throws INVALID_CURSOR for anything else

await retryAsyncFunction(() => fetchData(), [3, 5]); // retries after 3, then 5 seconds
await waitUntil(() => readStatus() === 'ready' || undefined, { timeoutSeconds: 30 });
```

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, say
const after = paymentCursors.decode(request.query.cursor, { status }); // INVALID_CURSOR otherwise

const 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
```

## <a id="api-reference"></a>jg-utilities API reference

Every export of `jg-utilities@1.4.0`, 125 in all, with its signature, its errors, and an example. It's generated from the package's TSDoc.

- Functions: [`applyDefaults`](#api-applyDefaults), [`applySubstitutions`](#api-applySubstitutions), [`capitalizeFirst`](#api-capitalizeFirst), [`createCursorCodec`](#api-createCursorCodec), [`createEventBus`](#api-createEventBus), [`createRequestCoordinator`](#api-createRequestCoordinator), [`createTimestamp`](#api-createTimestamp), [`decodeBase64Url`](#api-decodeBase64Url), [`decodeCursor`](#api-decodeCursor), [`delay`](#api-delay), [`encodeBase64Url`](#api-encodeBase64Url), [`encodeCursor`](#api-encodeCursor), [`escapeHtml`](#api-escapeHtml), [`estimateReadingTime`](#api-estimateReadingTime), [`extractEmailUsername`](#api-extractEmailUsername), [`extractFirstMarkdownHeadingName`](#api-extractFirstMarkdownHeadingName), [`extractSubstitutionPlaceholderNames`](#api-extractSubstitutionPlaceholderNames), [`extractTokenFromAuthorizationHeader`](#api-extractTokenFromAuthorizationHeader), [`filterByQuery`](#api-filterByQuery), [`generateDateId`](#api-generateDateId), [`generateRandomFloat`](#api-generateRandomFloat), [`generateRandomInteger`](#api-generateRandomInteger), [`generateRandomString`](#api-generateRandomString), [`generateSequence`](#api-generateSequence), [`generateUtcDateSequence`](#api-generateUtcDateSequence), [`generateUuid`](#api-generateUuid), [`getInitials`](#api-getInitials), [`getNextPageParam`](#api-getNextPageParam), [`getTrailingUtcDateRange`](#api-getTrailingUtcDateRange), [`hasNavigator`](#api-hasNavigator), [`hasWindow`](#api-hasWindow), [`isArrayValid`](#api-isArrayValid), [`isAuthorizationHeaderValid`](#api-isAuthorizationHeaderValid), [`isBigIntStringValid`](#api-isBigIntStringValid), [`isEmailValid`](#api-isEmailValid), [`isEqual`](#api-isEqual), [`isFileNameValid`](#api-isFileNameValid), [`isIndexedDbAvailable`](#api-isIndexedDbAvailable), [`isIntegerValid`](#api-isIntegerValid), [`isJwtValid`](#api-isJwtValid), [`isNumberValid`](#api-isNumberValid), [`isNumeric`](#api-isNumeric), [`isObjectValid`](#api-isObjectValid), [`isOTPSecretValid`](#api-isOTPSecretValid), [`isOTPTokenValid`](#api-isOTPTokenValid), [`isPasswordValid`](#api-isPasswordValid), [`isSemverValid`](#api-isSemverValid), [`isSlugValid`](#api-isSlugValid), [`isStringValid`](#api-isStringValid), [`isTimestampValid`](#api-isTimestampValid), [`isUrlValid`](#api-isUrlValid), [`isUuidValid`](#api-isUuidValid), [`isYouTubeVideoIdValid`](#api-isYouTubeVideoIdValid), [`maskMiddle`](#api-maskMiddle), [`normalizeQuery`](#api-normalizeQuery), [`omitProps`](#api-omitProps), [`parseJson`](#api-parseJson), [`parseYouTubeVideoId`](#api-parseYouTubeVideoId), [`pickProps`](#api-pickProps), [`pluralize`](#api-pluralize), [`prefersReducedMotion`](#api-prefersReducedMotion), [`prettifyBadgeCount`](#api-prettifyBadgeCount), [`prettifyDate`](#api-prettifyDate), [`prettifyFileSize`](#api-prettifyFileSize), [`prettifyNumber`](#api-prettifyNumber), [`prettifyPercentage`](#api-prettifyPercentage), [`prettifyTime`](#api-prettifyTime), [`pruneJson`](#api-pruneJson), [`redactSecrets`](#api-redactSecrets), [`redactUrlQueryParams`](#api-redactUrlQueryParams), [`resolveSafeInternalPath`](#api-resolveSafeInternalPath), [`retryAsyncFunction`](#api-retryAsyncFunction), [`shuffleArray`](#api-shuffleArray), [`sortPrimitives`](#api-sortPrimitives), [`sortRecords`](#api-sortRecords), [`sortRecordsWithBigIntString`](#api-sortRecordsWithBigIntString), [`sortRecordsWithDateValue`](#api-sortRecordsWithDateValue), [`splitArrayIntoBatches`](#api-splitArrayIntoBatches), [`stringifyJson`](#api-stringifyJson), [`stringifyJsonDeterministically`](#api-stringifyJsonDeterministically), [`stringifyValue`](#api-stringifyValue), [`toDate`](#api-toDate), [`toMS`](#api-toMS), [`toSlug`](#api-toSlug), [`toTitleCase`](#api-toTitleCase), [`toUtcDate`](#api-toUtcDate), [`toYouTubeEmbedUrl`](#api-toYouTubeEmbedUrl), [`toYouTubeWatchUrl`](#api-toYouTubeWatchUrl), [`truncateText`](#api-truncateText), [`truncateToUtf8Bytes`](#api-truncateToUtf8Bytes), [`waitUntil`](#api-waitUntil)
- Constants: [`ERRORS`](#api-ERRORS), [`MAX_EMAIL_LENGTH`](#api-MAX_EMAIL_LENGTH), [`ONE_DAY_IN_MILLISECONDS`](#api-ONE_DAY_IN_MILLISECONDS), [`ONE_HOUR_IN_MILLISECONDS`](#api-ONE_HOUR_IN_MILLISECONDS), [`ONE_MINUTE_IN_MILLISECONDS`](#api-ONE_MINUTE_IN_MILLISECONDS), [`ONE_MONTH_IN_MILLISECONDS`](#api-ONE_MONTH_IN_MILLISECONDS), [`ONE_SECOND_IN_MILLISECONDS`](#api-ONE_SECOND_IN_MILLISECONDS), [`ONE_WEEK_IN_MILLISECONDS`](#api-ONE_WEEK_IN_MILLISECONDS), [`ONE_YEAR_IN_MILLISECONDS`](#api-ONE_YEAR_IN_MILLISECONDS), [`SECRET_TOKEN_PATTERNS`](#api-SECRET_TOKEN_PATTERNS)
- Types: [`ICursorCodec`](#api-ICursorCodec), [`ICursorCodecOptions`](#api-ICursorCodecOptions), [`ICursorValue`](#api-ICursorValue), [`IDateTemplate`](#api-IDateTemplate), [`IDateValue`](#api-IDateValue), [`IDelayOptions`](#api-IDelayOptions), [`IEventBus`](#api-IEventBus), [`IEventBusOptions`](#api-IEventBusOptions), [`IFilterByQueryOptions`](#api-IFilterByQueryOptions), [`IJsonValue`](#api-IJsonValue), [`INumberFormatConfig`](#api-INumberFormatConfig), [`IRedactSecretsOptions`](#api-IRedactSecretsOptions), [`IRedactSecretsResult`](#api-IRedactSecretsResult), [`IRequestCoordinator`](#api-IRequestCoordinator), [`IRequestCoordinatorOptions`](#api-IRequestCoordinatorOptions), [`IRetryAsyncFunctionOptions`](#api-IRetryAsyncFunctionOptions), [`IRunRequestOptions`](#api-IRunRequestOptions), [`ISchema`](#api-ISchema), [`ISortDirection`](#api-ISortDirection), [`ISubstitutionOptions`](#api-ISubstitutionOptions), [`ITimeString`](#api-ITimeString), [`IUtcDateRange`](#api-IUtcDateRange), [`IUuidVersion`](#api-IUuidVersion), [`IWaitUntilOptions`](#api-IWaitUntilOptions)

### <a id="api-functions"></a>Functions

#### <a id="api-applyDefaults"></a>`applyDefaults`

*Function*

Applies non-nullish overrides to a defaults object and returns a new shallow object.

```ts
applyDefaults<T extends Record<string, unknown>>(
  defaults: T,
  overrides: { [Key in string | number | symbol]?: T[Key] | null },
): T
```

**Parameters**

- `defaults` `T`: The object containing the default properties and values.
- `overrides` `{ [Key in string | number | symbol]?: T[Key] | null }`: The values that should replace matching defaults when they are not nullish.

**Returns** `T`: A new object containing the default keys and resolved values.

**Example**

```ts
import { applyDefaults } from 'jg-utilities';

applyDefaults({ retries: 3, timeout: 15 }, { retries: 5, timeout: null }); // => { retries: 5, timeout: 15 }
```

#### <a id="api-applySubstitutions"></a>`applySubstitutions`

*Function*

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.

```ts
applySubstitutions(
  input: string,
  substitutions?: Record<string, unknown>,
  options?: ISubstitutionOptions,
): string
```

**Parameters**

- `input` `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**

```ts
import { applySubstitutions } from 'jg-utilities';

applySubstitutions('Hi {{name}}, you have {{count}} messages.', { name: 'Ada', count: 3 }); // => 'Hi Ada, you have 3 messages.'
```

#### <a id="api-capitalizeFirst"></a>`capitalizeFirst`

*Function*

Capitalizes the first letter of a string and returns the new value.

```ts
capitalizeFirst(value: string): string
```

**Parameters**

- `value` `string`: The string value to capitalize.

**Returns** `string`: A string with the first letter capitalized.

**Example**

```ts
import { capitalizeFirst } from 'jg-utilities';

capitalizeFirst('hello world'); // => 'Hello world'
```

#### <a id="api-createCursorCodec"></a>`createCursorCodec`

*Function*

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.

```ts
createCursorCodec<TPayload extends { [key: string]: IJsonValue }, TScope extends object = Partial<TPayload>>(
  options: ICursorCodecOptions<TPayload, TScope>,
): ICursorCodec<TPayload, TScope>
```

**Parameters**

- `options` `ICursorCodecOptions<TPayload, TScope>`: The schema, the longest cursor, and the scope rule.

**Returns** `ICursorCodec<TPayload, TScope>`: The codec.

**Throws**

- `CURSOR_TOO_LONG`: The cursor is longer than the codec's maximum length.
- `INVALID_CURSOR`: The cursor is not valid for this list.

**Example**

```ts
import { createCursorCodec } from 'jg-utilities';

type IPostCursor = { authorId: string; lastId: number };
const isPostCursor = (value: unknown): value is IPostCursor =>
  typeof value === 'object' && value !== null && 'authorId' in value && 'lastId' in value;

const postCursors = createCursorCodec<IPostCursor, { authorId: string }>({
  schema: {
    parse: (value) => {
      if (!isPostCursor(value)) {
        throw new TypeError('Not a post cursor.');
      }
      return value;
    },
  },
});
const cursor = postCursors.encode({ authorId: 'ada', lastId: 42 });
postCursors.decode(cursor, { authorId: 'ada' }); // => { authorId: 'ada', lastId: 42 }
postCursors.decode(undefined, { authorId: 'ada' }); // => undefined
```

#### <a id="api-createEventBus"></a>`createEventBus`

*Function*

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.

```ts
createEventBus<TEvents extends Record<string, unknown>>(
  options: IEventBusOptions<TEvents>,
): IEventBus<TEvents>
```

**Parameters**

- `options` `IEventBusOptions<TEvents>`: The `onError` that receives each handler's error.

**Returns** `IEventBus<TEvents>`: The bus.

**Example**

```ts
import { createEventBus } from 'jg-utilities';

type IAppEvents = { userSignedUp: { userId: string } };

const events = createEventBus<IAppEvents>({
  onError: (error, { name }) => console.error(`A ${String(name)} handler failed`, error),
});
const welcomedUserIds: string[] = [];
const unsubscribe = events.subscribe('userSignedUp', ({ userId }) => {
  welcomedUserIds.push(userId);
});

// emit returns at once, and the handler runs later
events.emit('userSignedUp', { userId: '42' });
welcomedUserIds; // => []

// once the handler is no longer needed
setTimeout(unsubscribe, 1_000);
```

#### <a id="api-createRequestCoordinator"></a>`createRequestCoordinator`

*Function*

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.

```ts
createRequestCoordinator(
  options: IRequestCoordinatorOptions,
): IRequestCoordinator
```

**Parameters**

- `options` `IRequestCoordinatorOptions`: The concurrency and queue limits.

**Returns** `IRequestCoordinator`: The coordinator.

**Throws**

- `INVALID_CONCURRENCY_LIMIT`: The concurrency limit must be an integer of at least 1.
- `INVALID_QUEUE_LIMIT`: The queue limit must be an integer of at least 0.

**Example**

```ts
import { createRequestCoordinator } from 'jg-utilities';

const github = createRequestCoordinator({ maxConcurrent: 4, maxQueued: 64 });
const fetchIssues = (): Promise<Response> =>
  github.run(() => fetch('https://api.github.com/repos/octocat/hello-world/issues'));
```

#### <a id="api-createTimestamp"></a>`createTimestamp`

*Function*

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.

```ts
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**

- Invalid time value

**Example**

```ts
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'
```

#### <a id="api-decodeBase64Url"></a>`decodeBase64Url`

*Function*

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.

```ts
decodeBase64Url(value: string): string
```

**Parameters**

- `value` `string`: The base64url value to decode.

**Returns** `string`: The decoded string.

**Throws**

- `INVALID_BASE64URL`: if the value isn't a string of base64url characters with a valid length.
- `INVALID_BASE64URL`: if the decoded bytes aren't valid UTF-8.
- `INVALID_BASE64URL`: if the value isn't the canonical encoding of its result.

**Example**

```ts
import { decodeBase64Url } from 'jg-utilities';

decodeBase64Url('eyJwYWdlIjoyfQ'); // => '{"page":2}'
```

#### <a id="api-decodeCursor"></a>`decodeCursor`

*Function*

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.

```ts
decodeCursor(cursor: string, maxLength?: number): ICursorValue
```

**Parameters**

- `cursor` `string`: The cursor to decode.
- `maxLength?` `number`: The longest cursor accepted, in characters. Defaults to `2048`.

**Returns** `ICursorValue`: The decoded object or array.

**Throws**

- `INVALID_CURSOR`: The cursor is not a valid encoded value.

**Example**

```ts
import { decodeCursor } from 'jg-utilities';

const searchParams = new URLSearchParams('?cursor=eyJpZCI6NDJ9');
decodeCursor(searchParams.get('cursor') ?? ''); // => { id: 42 }
```

#### <a id="api-delay"></a>`delay`

*Function*

Creates an asynchronous delay that resolves once the provided seconds have passed.

```ts
delay(seconds: number, options?: IDelayOptions): Promise<void>
```

**Parameters**

- `seconds` `number`: The number of seconds to delay before the promise resolves.
- `options?` `IDelayOptions`: No description.

**Returns** `Promise<void>`: A promise that resolves after the specified number of seconds has passed.

**Throws**

- The signal's reason when the signal is already aborted or aborts during the wait.

**Example**

```ts
import { delay } from 'jg-utilities';

await delay(0.5); // resumes after half a second
await delay(10, { signal: AbortSignal.timeout(100) }).catch(() => 'stopped'); // => 'stopped'
```

#### <a id="api-encodeBase64Url"></a>`encodeBase64Url`

*Function*

Encodes a string as UTF-8 base64url without padding. For ASCII input, the output equals the btoa-based and Buffer-based base64url encodings.

```ts
encodeBase64Url(value: string): string
```

**Parameters**

- `value` `string`: The string to encode.

**Returns** `string`: The base64url encoding of the string's UTF-8 bytes.

**Example**

```ts
import { encodeBase64Url } from 'jg-utilities';

encodeBase64Url('{"page":2}'); // => 'eyJwYWdlIjoyfQ'
```

#### <a id="api-encodeCursor"></a>`encodeCursor`

*Function*

Encodes a pagination continuation, such as the sort keys of the last row, as an opaque cursor: its JSON in base64url, safe for URLs and query strings.

```ts
encodeCursor(value: ICursorValue): string
```

**Parameters**

- `value` `ICursorValue`: The object or array to encode.

**Returns** `string`: The cursor.

**Throws**

- `UNSUPPORTED_DATA_TYPE`: if the value isn't an object or an array
- `UNABLE_TO_SERIALIZE_JSON`: if the value contains data that cannot be serialized

**Example**

```ts
import { encodeCursor } from 'jg-utilities';

encodeCursor({ id: 42 }); // => 'eyJpZCI6NDJ9'
```

#### <a id="api-escapeHtml"></a>`escapeHtml`

*Function*

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.

```ts
escapeHtml(text: string): string
```

**Parameters**

- `text` `string`: The text.

**Returns** `string`: The escaped text.

**Example**

```ts
import { escapeHtml } from 'jg-utilities';

escapeHtml('<b>"Tom" & Jerry\'s</b>'); // => '&lt;b&gt;&quot;Tom&quot; &amp; Jerry&#39;s&lt;/b&gt;'
```

#### <a id="api-estimateReadingTime"></a>`estimateReadingTime`

*Function*

Estimates how long text takes to read at 200 words per minute.

```ts
estimateReadingTime(text: unknown): number
```

**Parameters**

- `text` `unknown`: The text whose reading time will be estimated.

**Returns** `number`: The estimated reading time in milliseconds, or 0 for non-string or empty input.

**Example**

```ts
import { estimateReadingTime, prettifyTime } from 'jg-utilities';

const article = 'word '.repeat(600);
estimateReadingTime(article); // milliseconds, about 3 minutes for 600 words
prettifyTime(estimateReadingTime(article)); // => '3m'
```

#### <a id="api-extractEmailUsername"></a>`extractEmailUsername`

*Function*

Validates the format of an email address and extracts the username from it.

```ts
extractEmailUsername(email: string): string
```

**Parameters**

- `email` `string`: The email address to validate and extract the username from.

**Returns** `string`: The extracted username from the email address.

**Throws**

- `INVALID_EMAIL_ADDRESS`: If the email address is not valid or has a forbidden extension.

**Example**

```ts
import { extractEmailUsername } from 'jg-utilities';

extractEmailUsername('Ada.Lovelace@example.com'); // => 'ada.lovelace'
```

#### <a id="api-extractFirstMarkdownHeadingName"></a>`extractFirstMarkdownHeadingName`

*Function*

Extracts the name of the first markdown heading in the given content.

```ts
extractFirstMarkdownHeadingName(markdownContent: string): string | null
```

**Parameters**

- `markdownContent` `string`: The markdown-formatted content.

**Returns** `string | null`: The first markdown heading text found in the content, or null if none is found.

**Example**

```ts
import { extractFirstMarkdownHeadingName } from 'jg-utilities';

extractFirstMarkdownHeadingName('Intro\n\n## Getting started\n\nText'); // => 'Getting started'
```

#### <a id="api-extractSubstitutionPlaceholderNames"></a>`extractSubstitutionPlaceholderNames`

*Function*

Extracts the names of substitution placeholders in the given text.

```ts
extractSubstitutionPlaceholderNames(text: string): string[]
```

**Parameters**

- `text` `string`: The text containing substitution placeholders.

**Returns** `string[]`: An array of substitution placeholder names.

**Example**

```ts
import { extractSubstitutionPlaceholderNames } from 'jg-utilities';

extractSubstitutionPlaceholderNames('Hi {{name}}, you have {{count}} messages, {{name}}.'); // => ['name', 'count']
```

#### <a id="api-extractTokenFromAuthorizationHeader"></a>`extractTokenFromAuthorizationHeader`

*Function*

Validates the format of an authorization header and extracts the token from it.

```ts
extractTokenFromAuthorizationHeader(header: string): string
```

**Parameters**

- `header` `string`: The authorization header to validate and extract the token from.

**Returns** `string`: The extracted token from the authorization header.

**Throws**

- `INVALID_AUTHORIZATION_HEADER`: If the header does not comply with the expected format.

**Example**

```ts
import { extractTokenFromAuthorizationHeader } from 'jg-utilities';

extractTokenFromAuthorizationHeader('Bearer eyJhbGciOi.payload.signature'); // => 'eyJhbGciOi.payload.signature'
```

#### <a id="api-filterByQuery"></a>`filterByQuery`

*Function*

Filters an array of primitives based on a given query and returns a shallow copy.

```ts
filterByQuery<T>(
  items: T[],
  query: string,
  options?: IFilterByQueryOptions<T>,
): T[]
```

**Parameters**

- `items` `T[]`: The array of items to be filtered by the query.
- `query` `string`: The query string to filter the items by.
- `options?` `IFilterByQueryOptions<T>`: No description.

**Returns** `T[]`: A new array containing the items that match the query based on the provided options.

**Example**

```ts
import { filterByQuery } from 'jg-utilities';

filterByQuery([{ name: 'Ada' }, { name: 'Alan' }, { name: 'Grace' }], 'al', { queryProp: 'name' }); // => [{ name: 'Alan' }]
```

#### <a id="api-generateDateId"></a>`generateDateId`

*Function*

Generates a date ID string in the format "YYYY_MM_DD".

```ts
generateDateId(value?: IDateValue): string
```

**Parameters**

- `value?` `IDateValue`: The date value to generate the ID from. Defaults to the current date if not provided.

**Returns** `string`: The generated date ID string.

**Example**

```ts
import { generateDateId } from 'jg-utilities';

generateDateId(new Date(2026, 9, 17)); // => '2026_10_17'
generateDateId(); // today's ID
```

#### <a id="api-generateRandomFloat"></a>`generateRandomFloat`

*Function*

Generates a random decimal in a range, with 53 bits of randomness from `crypto.getRandomValues`.

```ts
generateRandomFloat(min: number, max: number): number
```

**Parameters**

- `min` `number`: The lowest value, included.
- `max` `number`: The highest value, excluded.

**Returns** `number`: A random number greater than or equal to `min` and less than `max`.

**Example**

```ts
import { generateRandomFloat } from 'jg-utilities';

generateRandomFloat(0, 1); // 0.7312...
```

#### <a id="api-generateRandomInteger"></a>`generateRandomInteger`

*Function*

Generates a random integer in an inclusive range. Each integer is equally likely: values come from `crypto.getRandomValues` with rejection sampling.

```ts
generateRandomInteger(min: number, max: number): number
```

**Parameters**

- `min` `number`: The lowest value, included.
- `max` `number`: The highest value, included.

**Returns** `number`: A random integer from `min` to `max`.

**Example**

```ts
import { generateRandomInteger } from 'jg-utilities';

generateRandomInteger(1, 6); // such as 4
```

#### <a id="api-generateRandomString"></a>`generateRandomString`

*Function*

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.

```ts
generateRandomString(length: number, characters?: string): string
```

**Parameters**

- `length` `number`: The length of the string.
- `characters?` `string`: 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**

```ts
import { generateRandomString } from 'jg-utilities';

generateRandomString(12); // such as 'aZ3kPq9TbW0x'
generateRandomString(6, '0123456789'); // such as '402913'
```

#### <a id="api-generateSequence"></a>`generateSequence`

*Function*

Generates a sequence of numbers within a range based on a number of steps.

```ts
generateSequence(start: number, stop: number, step?: number): number[]
```

**Parameters**

- `start` `number`: The starting value of the sequence.
- `stop` `number`: The ending value of the sequence.
- `step?` `number`: No description. Defaults to `1`.

**Returns** `number[]`: An array of numbers representing the sequence.

**Example**

```ts
import { generateSequence } from 'jg-utilities';

generateSequence(1, 5); // => [1, 2, 3, 4, 5]
generateSequence(0, 10, 5); // => [0, 5, 10]
```

#### <a id="api-generateUtcDateSequence"></a>`generateUtcDateSequence`

*Function*

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.

```ts
generateUtcDateSequence(range: IUtcDateRange): string[]
```

**Parameters**

- `range` `IUtcDateRange`: The first and last dates, as `YYYY-MM-DD`.

**Returns** `string[]`: The dates, from `fromDate` to `toDate`.

**Throws**

- `INVALID_DATE_RANGE`: The range must hold two real dates as YYYY-MM-DD, the first no later than the second.

**Example**

```ts
import { generateUtcDateSequence } from 'jg-utilities';

generateUtcDateSequence({ fromDate: '2028-02-28', toDate: '2028-03-01' }); // => ['2028-02-28', '2028-02-29', '2028-03-01']
```

#### <a id="api-generateUuid"></a>`generateUuid`

*Function*

Generates a UUID based on a version.

```ts
generateUuid(version: IUuidVersion): string
```

**Parameters**

- `version` `IUuidVersion`: The version of the UUID to be generated.

**Returns** `string`: A UUID string of the specified version.

**Example**

```ts
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
```

#### <a id="api-getInitials"></a>`getInitials`

*Function*

Extracts a string of initials from the provided value.

```ts
getInitials(value: string, initialsCount?: number): string
```

**Parameters**

- `value` `string`: The string value from which to extract initials.
- `initialsCount?` `number`: No description. Defaults to `1`.

**Returns** `string`: A string with up to the requested number of letter-only initials, or "A" if none exist.

**Example**

```ts
import { getInitials } from 'jg-utilities';

getInitials('Ada Lovelace'); // => 'A'
getInitials('Ada Lovelace', 2); // => 'AL'
```

#### <a id="api-getNextPageParam"></a>`getNextPageParam`

*Function*

Gets the value of a property from the last item when the array length reaches the initial page size.

```ts
getNextPageParam<T extends object, K extends string | number | symbol>(
  propName: K,
  items: T[],
  initialPageSize: number,
): T[K] | undefined
```

**Parameters**

- `propName` `K`: The property name to read from the last item.
- `items` `T[]`: The array of items from which to read the last property value.
- `initialPageSize` `number`: 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**

```ts
import { getNextPageParam } from 'jg-utilities';

// with a page size of 2, a full page means there may be more
getNextPageParam('id', [{ id: 1 }, { id: 2 }], 2); // => 2
getNextPageParam('id', [{ id: 3 }], 2); // => undefined
```

#### <a id="api-getTrailingUtcDateRange"></a>`getTrailingUtcDateRange`

*Function*

Gives the inclusive range of the last `dayCount` UTC days, ending on the UTC date of an instant, such as the 30 days a report covers.

```ts
getTrailingUtcDateRange(dayCount: number, value?: IDateValue): IUtcDateRange
```

**Parameters**

- `dayCount` `number`: The number of days, a positive safe integer; 1 gives a range of one day.
- `value?` `IDateValue`: No description.

**Returns** `IUtcDateRange`: The first and last UTC dates, as `YYYY-MM-DD`.

**Throws**

- `INVALID_DAY_COUNT`: The day count must be a positive safe integer whose range starts in the year 0000 or later.
- `INVALID_DATE`: The value is not a valid date in the years 0000 to 9999.

**Example**

```ts
import { getTrailingUtcDateRange } from 'jg-utilities';

getTrailingUtcDateRange(7, '2026-10-17T08:30:00.000Z'); // => { fromDate: '2026-10-11', toDate: '2026-10-17' }
getTrailingUtcDateRange(30); // the last 30 days, today included
```

#### <a id="api-hasNavigator"></a>`hasNavigator`

*Function*

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.

```ts
hasNavigator(): boolean
```

**Returns** `boolean`: True when the navigator global is defined.

**Example**

```ts
import { hasNavigator } from 'jg-utilities';

hasNavigator(); // true in browsers and in Node.js 21 or later
```

#### <a id="api-hasWindow"></a>`hasWindow`

*Function*

Checks whether a global window object exists. It reports presence only and doesn't identify a browser.

```ts
hasWindow(): boolean
```

**Returns** `boolean`: True when the window global is defined.

**Example**

```ts
import { hasWindow } from 'jg-utilities';

hasWindow(); // true in a browser, false in Node.js
```

#### <a id="api-isArrayValid"></a>`isArrayValid`

*Function*

Verifies if a value is an array. It also validates if it has elements inside (optional).

```ts
isArrayValid(value: unknown, allowEmpty?: boolean): value is any[]
```

**Parameters**

- `value` `unknown`: The value to be validated.
- `allowEmpty?` `boolean`: No description.

**Returns** `value is any[]`: A boolean indicating whether the value is a valid array and meets the element requirements (if provided).

**Example**

```ts
import { isArrayValid } from 'jg-utilities';

isArrayValid([1]); // => true
isArrayValid([]); // => false
isArrayValid([], true); // => true
```

#### <a id="api-isAuthorizationHeaderValid"></a>`isAuthorizationHeaderValid`

*Function*

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

```ts
isAuthorizationHeaderValid(value: unknown): value is string
```

**Parameters**

- `value` `unknown`: The value to be validated.

**Returns** `value is string`: A boolean indicating whether the value is a valid Authorization header.

**Example**

```ts
import { isAuthorizationHeaderValid } from 'jg-utilities';

isAuthorizationHeaderValid('Bearer eyJhbGciOi.payload.signature'); // => true
isAuthorizationHeaderValid('Basic dXNlcg=='); // => false
```

#### <a id="api-isBigIntStringValid"></a>`isBigIntStringValid`

*Function*

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.

```ts
isBigIntStringValid(value: unknown, min?: bigint, max?: bigint): value is string
```

**Parameters**

- `value` `unknown`: 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.

**Example**

```ts
import { isBigIntStringValid } from 'jg-utilities';

isBigIntStringValid('9223372036854775807'); // => true
isBigIntStringValid('9223372036854775808'); // => false
isBigIntStringValid('007'); // => false
isBigIntStringValid('5', 1n); // => true
```

#### <a id="api-isEmailValid"></a>`isEmailValid`

*Function*

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'].

```ts
isEmailValid(value: unknown, forbiddenExtensions?: string[]): value is string
```

**Parameters**

- `value` `unknown`: 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.

**Example**

```ts
import { isEmailValid } from 'jg-utilities';

isEmailValid('ada@example.com'); // => true
isEmailValid('ada@example.con'); // => false
```

#### <a id="api-isEqual"></a>`isEqual`

*Function*

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.

```ts
isEqual(firstValue: unknown, secondValue: unknown): boolean
```

**Parameters**

- `firstValue` `unknown`: The first value.
- `secondValue` `unknown`: The second value.

**Returns** `boolean`: Whether the values are equal.

**Throws**

- `UNABLE_TO_SERIALIZE_JSON`: if either object contains data that cannot be serialized

**Example**

```ts
import { isEqual } from 'jg-utilities';

isEqual({ a: 1, b: [2] }, { b: [2], a: 1 }); // => true
isEqual('a', 'a'); // => true
isEqual({}, null); // => false
```

#### <a id="api-isFileNameValid"></a>`isFileNameValid`

*Function*

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.

```ts
isFileNameValid(value: unknown, maxBytes?: number): value is string
```

**Parameters**

- `value` `unknown`: 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.

**Example**

```ts
import { isFileNameValid } from 'jg-utilities';

isFileNameValid('notes.md'); // => true
isFileNameValid('con.txt'); // => false
isFileNameValid('a/b.txt'); // => false
```

#### <a id="api-isIndexedDbAvailable"></a>`isIndexedDbAvailable`

*Function*

Checks whether the IndexedDB API exists in the current environment.

```ts
isIndexedDbAvailable(): boolean
```

**Returns** `boolean`: True when the indexedDB global is defined.

**Example**

```ts
import { isIndexedDbAvailable } from 'jg-utilities';

isIndexedDbAvailable(); // true in browsers that support IndexedDB
```

#### <a id="api-isIntegerValid"></a>`isIntegerValid`

*Function*

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.

```ts
isIntegerValid(value: unknown, min?: number, max?: number): value is number
```

**Parameters**

- `value` `unknown`: 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.

**Example**

```ts
import { isIntegerValid } from 'jg-utilities';

isIntegerValid(5); // => true
isIntegerValid(5.5); // => false
isIntegerValid(5, 1, 3); // => false
```

#### <a id="api-isJwtValid"></a>`isJwtValid`

*Function*

Verifies if a value has a correct JWT Format: [Base64-URL Encoded Header].[Base64-URL Encoded Payload].[Signature]

```ts
isJwtValid(value: unknown): value is string
```

**Parameters**

- `value` `unknown`: The value to be validated.

**Returns** `value is string`: A boolean indicating whether the value is a valid JWT.

**Example**

```ts
import { isJwtValid } from 'jg-utilities';

isJwtValid('eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIn0.c2lnbmF0dXJl'); // => true
isJwtValid('not-a-token'); // => false
```

#### <a id="api-isNumberValid"></a>`isNumberValid`

*Function*

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).

```ts
isNumberValid(value: unknown, min?: number, max?: number): value is number
```

**Parameters**

- `value` `unknown`: 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.

**Example**

```ts
import { isNumberValid } from 'jg-utilities';

isNumberValid(12.5); // => true
isNumberValid(Number.NaN); // => false
isNumberValid(5, 10); // => false
```

#### <a id="api-isNumeric"></a>`isNumeric`

*Function*

Verifies if a value is a valid numeric string.

```ts
isNumeric(value: string): boolean
```

**Parameters**

- `value` `string`: The value to be validated.

**Returns** `boolean`: A boolean indicating whether the value is a valid numeric string.

**Example**

```ts
import { isNumeric } from 'jg-utilities';

isNumeric('-12.5'); // => true
isNumeric('1,000'); // => false
```

#### <a id="api-isObjectValid"></a>`isObjectValid`

*Function*

Verifies if a value is an actual object. It also validates if it has keys (optional).

```ts
isObjectValid(
  value: unknown,
  allowEmpty?: boolean,
): value is Record<string, any>
```

**Parameters**

- `value` `unknown`: The value to be validated.
- `allowEmpty?` `boolean`: No description.

**Returns** `value is Record<string, any>`: A boolean indicating whether the value is a valid object and meets the key requirements (if provided).

**Example**

```ts
import { isObjectValid } from 'jg-utilities';

isObjectValid({ id: 1 }); // => true
isObjectValid({}); // => false
isObjectValid({}, true); // => true
isObjectValid([]); // => false
```

#### <a id="api-isOTPSecretValid"></a>`isOTPSecretValid`

*Function*

Verifies if a value has the correct OTP Secret Format.

```ts
isOTPSecretValid(value: unknown): value is string
```

**Parameters**

- `value` `unknown`: The value to be validated.

**Returns** `value is string`: A boolean indicating whether the value is a valid OTP secret.

**Example**

```ts
import { isOTPSecretValid } from 'jg-utilities';

isOTPSecretValid('JBSWY3DPEHPK3PXP'); // => true
```

#### <a id="api-isOTPTokenValid"></a>`isOTPTokenValid`

*Function*

Verifies if a value has the correct OTP Token Format.

```ts
isOTPTokenValid(value: unknown): value is string
```

**Parameters**

- `value` `unknown`: The value to be validated.

**Returns** `value is string`: A boolean indicating whether the value is a valid OTP token.

**Example**

```ts
import { isOTPTokenValid } from 'jg-utilities';

isOTPTokenValid('123456'); // => true
isOTPTokenValid('12345'); // => false
```

#### <a id="api-isPasswordValid"></a>`isPasswordValid`

*Function*

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

```ts
isPasswordValid(
  value: unknown,
  minLength?: number,
  maxLength?: number,
): value is string
```

**Parameters**

- `value` `unknown`: 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.

**Example**

```ts
import { isPasswordValid } from 'jg-utilities';

isPasswordValid('Sup3r$ecret'); // => true
isPasswordValid('password'); // => false
```

#### <a id="api-isSemverValid"></a>`isSemverValid`

*Function*

Verifies if a value complies with semantic versioning.

```ts
isSemverValid(value: unknown): value is string
```

**Parameters**

- `value` `unknown`: The value to be validated.

**Returns** `value is string`: A boolean indicating whether the value is a valid semantic version.

**Example**

```ts
import { isSemverValid } from 'jg-utilities';

isSemverValid('1.2.0'); // => true
isSemverValid('1.2'); // => false
```

#### <a id="api-isSlugValid"></a>`isSlugValid`

*Function*

Verifies that a slug contains lowercase letters or digits separated by single hyphens and meets a length range.

```ts
isSlugValid(
  value: unknown,
  minLength?: number,
  maxLength?: number,
): value is string
```

**Parameters**

- `value` `unknown`: The value to be validated.
- `minLength?` `number`: No description. Defaults to `2`.
- `maxLength?` `number`: No description. Defaults to `16`.

**Returns** `value is string`: A boolean indicating whether the value is a valid slug.

**Example**

```ts
import { isSlugValid } from 'jg-utilities';

isSlugValid('jg-utilities'); // => true
isSlugValid('JG Utils'); // => false
```

#### <a id="api-isStringValid"></a>`isStringValid`

*Function*

Verifies if a value is a valid string and its length is within a range (optional).

```ts
isStringValid(
  value: unknown,
  minLength?: number,
  maxLength?: number,
  disallowWhitespaceOnly?: boolean,
): value is string
```

**Parameters**

- `value` `unknown`: The value to be validated.
- `minLength?` `number`: No description. Defaults to `1`.
- `maxLength?` `number`: No description.
- `disallowWhitespaceOnly?` `boolean`: No description. Defaults to `true`.

**Returns** `value is string`: A boolean indicating whether the value is a valid string and meets the length requirements (if provided).

**Example**

```ts
import { isStringValid } from 'jg-utilities';

isStringValid('hello'); // => true
isStringValid('   '); // => false
isStringValid('hello', 1, 3); // => false
```

#### <a id="api-isTimestampValid"></a>`isTimestampValid`

*Function*

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).

```ts
isTimestampValid(value: unknown): value is number
```

**Parameters**

- `value` `unknown`: The value to be validated.

**Returns** `value is number`: A boolean indicating whether the value is a valid unix timestamp in milliseconds.

**Example**

```ts
import { isTimestampValid } from 'jg-utilities';

isTimestampValid(Date.UTC(2026, 9, 17)); // => true
isTimestampValid(-1); // => false
```

#### <a id="api-isUrlValid"></a>`isUrlValid`

*Function*

Verifies if a value is a valid URL.

```ts
isUrlValid(value: unknown): value is string
```

**Parameters**

- `value` `unknown`: The value to be validated.

**Returns** `value is string`: A boolean indicating whether the value is a valid URL.

**Example**

```ts
import { isUrlValid } from 'jg-utilities';

isUrlValid('https://jg-packages.jesusgraterol.dev'); // => true
isUrlValid('not a url'); // => false
```

#### <a id="api-isUuidValid"></a>`isUuidValid`

*Function*

Verifies if a value is a valid UUID and that it matches a specific version.

```ts
isUuidValid(value: unknown, version: IUuidVersion): value is string
```

**Parameters**

- `value` `unknown`: The value to be validated.
- `version` `IUuidVersion`: The UUID version to be validated against.

**Returns** `value is string`: A boolean indicating whether the value is a valid UUID of the specified version.

**Example**

```ts
import { isUuidValid } from 'jg-utilities';

isUuidValid('0199f5e2-8f5a-7b3c-9d2e-4f1a6b8c0d7e', 7); // => true
isUuidValid('0199f5e2-8f5a-7b3c-9d2e-4f1a6b8c0d7e', 4); // => false
```

#### <a id="api-isYouTubeVideoIdValid"></a>`isYouTubeVideoIdValid`

*Function*

Verifies if a value is a YouTube video id: 11 characters of letters, digits, `-`, and `_`.

```ts
isYouTubeVideoIdValid(value: unknown): value is string
```

**Parameters**

- `value` `unknown`: The value to be validated.

**Returns** `value is string`: A boolean indicating whether the value is a YouTube video id.

**Example**

```ts
import { isYouTubeVideoIdValid } from 'jg-utilities';

isYouTubeVideoIdValid('M7lc1UVf-VE'); // => true
isYouTubeVideoIdValid('M7lc1UVf-V'); // => false
```

#### <a id="api-maskMiddle"></a>`maskMiddle`

*Function*

Masks the middle of a string, keeping a specified number of visible characters at the start and end.

```ts
maskMiddle(text: string, visibleChars: number, mask?: string): string
```

**Parameters**

- `text` `string`: The string value to mask.
- `visibleChars` `number`: The number of characters to keep visible at the start and end of the string.
- `mask?` `string`: No description. Defaults to `'...'`.

**Returns** `string`: A string with the middle masked, keeping the specified number of visible characters at the start.

**Example**

```ts
import { maskMiddle } from 'jg-utilities';

maskMiddle('0x1234567890abcdef', 4); // => '0x12...cdef'
```

#### <a id="api-normalizeQuery"></a>`normalizeQuery`

*Function*

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.

```ts
normalizeQuery(query: string, maxLength?: number): string
```

**Parameters**

- `query` `string`: The query string to normalize.
- `maxLength?` `number`: No description.

**Returns** `string`: The normalized query string.

**Example**

```ts
import { normalizeQuery } from 'jg-utilities';

normalizeQuery('  Héllo   WORLD  '); // => 'héllo world'
```

#### <a id="api-omitProps"></a>`omitProps`

*Function*

Omits a list of properties from an object and returns a new object (shallow) with only those keys that weren't omitted

```ts
omitProps<T extends object, K extends string | number | symbol>(
  input: T,
  propKeys: K[],
): Omit<T, K>
```

**Parameters**

- `input` `T`: The object from which to omit properties.
- `propKeys` `K[]`: The keys of the properties to omit.

**Returns** `Omit<T, K>`: A new object containing only the properties that were not omitted.

**Throws**

- `INVALID_OR_EMPTY_OBJECT`: if the input is not a valid object or it is empty
- `INVALID_OR_EMPTY_ARRAY`: if the keys to be omitted are not a valid array or it is empty

**Example**

```ts
import { omitProps } from 'jg-utilities';

omitProps({ id: 1, name: 'Ada', email: 'ada@example.com' }, ['email']); // => { id: 1, name: 'Ada' }
```

#### <a id="api-parseJson"></a>`parseJson`

*Function*

Deserializes a JSON string with the JSON.parse method.

```ts
parseJson<T>(value: string): T
```

**Parameters**

- `value` `string`: The JSON string to be deserialized.

**Returns** `T`: The deserialized object or array.

**Throws**

- `UNSUPPORTED_DATA_TYPE`: if the provided value is not a non-empty string
- `UNABLE_TO_DESERIALIZE_JSON`: if the result of JSON.parse is not a valid object or array
- `UNABLE_TO_DESERIALIZE_JSON`: if an error is thrown during parsing

**Example**

```ts
import { parseJson } from 'jg-utilities';

const user = parseJson<{ id: number }>('{"id":1}'); // { id: 1 }
```

#### <a id="api-parseYouTubeVideoId"></a>`parseYouTubeVideoId`

*Function*

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.

```ts
parseYouTubeVideoId(url: unknown): string | null
```

**Parameters**

- `url` `unknown`: The value to read, trimmed when it's a string.

**Returns** `string | null`: The 11-character video id, or null.

**Example**

```ts
import { parseYouTubeVideoId } from 'jg-utilities';

parseYouTubeVideoId('https://youtu.be/M7lc1UVf-VE'); // => 'M7lc1UVf-VE'
parseYouTubeVideoId('https://www.youtube.com/watch?v=M7lc1UVf-VE&t=30'); // => 'M7lc1UVf-VE'
parseYouTubeVideoId('https://www.youtube.com/playlist?list=PL123'); // => null
```

#### <a id="api-pickProps"></a>`pickProps`

*Function*

Picks a list of properties from an object and returns a new object (shallow) with the provided keys.

```ts
pickProps<T extends object, K extends string | number | symbol>(
  input: T,
  propKeys: K[],
): Pick<T, K>
```

**Parameters**

- `input` `T`: The object from which to pick properties.
- `propKeys` `K[]`: The keys of the properties to pick.

**Returns** `Pick<T, K>`: A new object containing only the picked properties.

**Throws**

- `INVALID_OR_EMPTY_OBJECT`: if the input is not a valid object or it is empty
- `INVALID_OR_EMPTY_ARRAY`: if the keys to be picked are not a valid array or it is empty

**Example**

```ts
import { pickProps } from 'jg-utilities';

pickProps({ id: 1, name: 'Ada', email: 'ada@example.com' }, ['id', 'name']); // => { id: 1, name: 'Ada' }
```

#### <a id="api-pluralize"></a>`pluralize`

*Function*

Picks the singular or plural form of a word for a count, such as "1 file" and "3 files".

```ts
pluralize(count: number, singular: string, plural?: string): string
```

**Parameters**

- `count` `number`: The count.
- `singular` `string`: The word for exactly one.
- `plural?` `string`: The word for any other count. Defaults to the singular with an "s". Defaults to `...`.

**Returns** `string`: The word alone, so other words can sit between it and the count.

**Example**

```ts
import { pluralize } from 'jg-utilities';

pluralize(1, 'file'); // => 'file'
pluralize(0, 'file'); // => 'files'
pluralize(2, 'entry', 'entries'); // => 'entries'
```

#### <a id="api-prefersReducedMotion"></a>`prefersReducedMotion`

*Function*

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.

```ts
prefersReducedMotion(view?: IMediaQueryView | null): boolean
```

**Parameters**

- `view?` `IMediaQueryView | null`: No description. Defaults to `...`.

**Returns** `boolean`: True only when the view reports a reduced-motion preference.

**Example**

```ts
import { prefersReducedMotion } from 'jg-utilities';

prefersReducedMotion(); // true when the user asked the system to reduce motion
```

#### <a id="api-prettifyBadgeCount"></a>`prettifyBadgeCount`

*Function*

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.

```ts
prettifyBadgeCount(value: number, maxValue?: number): string | undefined
```

**Parameters**

- `value` `number`: The current count to be displayed in the badge.
- `maxValue?` `number`: The maximum value to display before showing a "+" sign. Defaults to 9. Defaults to `9`.

**Returns** `string | undefined`: A string representing the formatted badge count or undefined if the count is 0.

**Example**

```ts
import { prettifyBadgeCount } from 'jg-utilities';

prettifyBadgeCount(3); // => '3'
prettifyBadgeCount(42); // => '9+'
prettifyBadgeCount(0); // => undefined
```

#### <a id="api-prettifyDate"></a>`prettifyDate`

*Function*

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.

```ts
prettifyDate(
  value: IDateValue,
  template?: IDateTemplate,
  timeZone?: string,
): string
```

**Parameters**

- `value` `IDateValue`: The date value to be formatted. It can be a Date object, a timestamp, or a date string.
- `template?` `IDateTemplate`: The template to use for formatting the date. Defaults to date-short. Defaults to `'date-short'`.
- `timeZone?` `string`: No description.

**Returns** `string`: A string representing the formatted date.

**Throws**

- A RangeError if the time zone is unknown.

**Example**

```ts
import { prettifyDate } from 'jg-utilities';

prettifyDate('2026-10-17T08:30:00.000Z', 'date-medium', 'UTC'); // => 'October 17, 2026'
prettifyDate('2026-10-17T08:30:00.000Z', 'datetime-short', 'UTC'); // => '10/17/2026, 8:30 AM UTC'
```

#### <a id="api-prettifyFileSize"></a>`prettifyFileSize`

*Function*

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.

```ts
prettifyFileSize(value: number | bigint, decimalPlaces?: number): string
```

**Parameters**

- `value` `number | 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. Defaults to `2`.

**Returns** `string`: A string representing the formatted file size.

**Example**

```ts
import { prettifyFileSize } from 'jg-utilities';

prettifyFileSize(1536); // => '1.50 kB'
prettifyFileSize(3_250_000_000, 1); // => '3.0 GB'
```

#### <a id="api-prettifyNumber"></a>`prettifyNumber`

*Function*

Formats a numeric value based on the user's default language. A negative value's sign goes before the prefix, as in `-$5`.

```ts
prettifyNumber(
  value: number,
  configuration?: Partial<INumberFormatConfig>,
): string
```

**Parameters**

- `value` `number`: The number to be formatted.
- `configuration?` `Partial<INumberFormatConfig>`: No description.

**Returns** `string`: A string representing the formatted number.

**Example**

```ts
import { prettifyNumber } from 'jg-utilities';

prettifyNumber(1234567.891); // => '1,234,567.89'
prettifyNumber(19.5, { minimumFractionDigits: 2, prefix: '$' }); // => '$19.50'
prettifyNumber(-5, { prefix: '$' }); // => '-$5'
```

#### <a id="api-prettifyPercentage"></a>`prettifyPercentage`

*Function*

Formats a numeric value as a percentage based on the user's default language.

```ts
prettifyPercentage(
  value: number,
  configuration?: Partial<INumberFormatConfig>,
): string
```

**Parameters**

- `value` `number`: The number to be formatted as a percentage - whole value (e.g., 50 for 50%)
- `configuration?` `Partial<INumberFormatConfig>`: No description.

**Returns** `string`: A string representing the formatted percentage.

**Example**

```ts
import { prettifyPercentage } from 'jg-utilities';

prettifyPercentage(12.345); // => '12.35%'
prettifyPercentage(-3); // => '-3%'
```

#### <a id="api-prettifyTime"></a>`prettifyTime`

*Function*

Formats a duration in milliseconds into a human-readable string.

```ts
prettifyTime(milliseconds: number): string
```

**Parameters**

- `milliseconds` `number`: The duration in milliseconds.

**Returns** `string`: A string representing the formatted duration.

**Example**

```ts
import { prettifyTime } from 'jg-utilities';

prettifyTime(3_725_000); // => '1h 2m'
prettifyTime(45_000); // => '45s'
```

#### <a id="api-pruneJson"></a>`pruneJson`

*Function*

Removes null, undefined, empty objects, and empty arrays from the given data recursively.

```ts
pruneJson<T extends IJsonValue>(value: T): T | null
```

**Parameters**

- `value` `T`: The value to be pruned. It should be an object, array, or primitive value.

**Returns** `T | null`: The pruned value, or null if the value is empty or invalid.

**Example**

```ts
import { pruneJson } from 'jg-utilities';

pruneJson({ a: 1, b: null, c: {}, d: [], e: { f: [] } }); // => { a: 1 }
```

#### <a id="api-redactSecrets"></a>`redactSecrets`

*Function*

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.

```ts
redactSecrets(
  text: string,
  options?: IRedactSecretsOptions,
): IRedactSecretsResult
```

**Parameters**

- `text` `string`: The text to redact.
- `options?` `IRedactSecretsOptions`: The patterns, the replacement, and whether key blocks keep their line breaks. Defaults to `{}`.

**Returns** `IRedactSecretsResult`: The redacted text, and how many secrets were replaced.

**Example**

```ts
import { redactSecrets } from 'jg-utilities';

redactSecrets('auth: ghp_abcdefghijklmnopqrstuvwxyz'); // => { text: 'auth: [redacted]', count: 1 }
redactSecrets('port: 3000'); // => { text: 'port: 3000', count: 0 }
```

#### <a id="api-redactUrlQueryParams"></a>`redactUrlQueryParams`

*Function*

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.

```ts
redactUrlQueryParams(url: string, names: readonly string[]): string
```

**Parameters**

- `url` `string`: The request target or absolute URL.
- `names` `readonly string[]`: The names of the parameters that make the query unsafe to log.

**Returns** `string`: The URL when it holds none of them, otherwise the part before its query.

**Example**

```ts
import { redactUrlQueryParams } from 'jg-utilities';

redactUrlQueryParams('/reset?token=abc#form', ['token']); // => '/reset'
redactUrlQueryParams('/reset?tok%65n=abc', ['token']); // => '/reset'
redactUrlQueryParams('/search?q=shoes', ['token']); // => '/search?q=shoes'
```

#### <a id="api-resolveSafeInternalPath"></a>`resolveSafeInternalPath`

*Function*

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.

```ts
resolveSafeInternalPath(
  candidate: unknown,
  excludedPaths?: readonly string[],
): string | null
```

**Parameters**

- `candidate` `unknown`: The untrusted value.
- `excludedPaths?` `readonly string[]`: Paths it must not lead to, such as `['/sign-in']`. Defaults to `[]`.

**Returns** `string | null`: The path, normalized, with its query and fragment, or null when it isn't safe.

**Example**

```ts
import { resolveSafeInternalPath } from 'jg-utilities';

resolveSafeInternalPath('/projects/a?tab=files', ['/sign-in']); // => '/projects/a?tab=files'
resolveSafeInternalPath('//evil.example', ['/sign-in']); // => null
resolveSafeInternalPath('/sign-in/step', ['/sign-in']); // => null
resolveSafeInternalPath('/sign-in-help', ['/sign-in']); // => '/sign-in-help'
```

#### <a id="api-retryAsyncFunction"></a>`retryAsyncFunction`

*Function*

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.

```ts
retryAsyncFunction<T>(
  fn: () => Promise<T>,
  retryScheduleDuration?: number[],
  options?: IRetryAsyncFunctionOptions,
): Promise<T>
```

**Parameters**

- `fn` `() => Promise<T>`: The asynchronous function to be executed persistently.
- `retryScheduleDuration?` `number[]`: No description. Defaults to `...`.
- `options?` `IRetryAsyncFunctionOptions`: No description.

**Returns** `Promise<T>`: A promise that resolves with the result of the asynchronous function or rejects with the last encountered error.

**Throws**

- The last error thrown by the function, or the first one shouldRetry declines to retry.
- The signal's reason when the signal aborts.

**Example**

```ts
import { retryAsyncFunction } from 'jg-utilities';

let attempts = 0;
const readRate = async (): Promise<number> => {
  attempts += 1;
  if (attempts < 2) {
    throw new Error('The service is busy.');
  }
  return 1.08;
};

// up to 3 attempts, 0.1 and 0.5 seconds apart
await retryAsyncFunction(readRate, [0.1, 0.5]); // => 1.08
attempts; // => 2
```

#### <a id="api-shuffleArray"></a>`shuffleArray`

*Function*

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.

```ts
shuffleArray<T>(input: T[]): T[]
```

**Parameters**

- `input` `T[]`: 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_ARRAY`: if the input is not array or it is empty

**Example**

```ts
import { shuffleArray } from 'jg-utilities';

shuffleArray([1, 2, 3, 4]); // a new array, such as [3, 1, 4, 2]
```

#### <a id="api-sortPrimitives"></a>`sortPrimitives`

*Function*

Sorts a list of primitive values based on their type and a sort direction.

```ts
sortPrimitives(
  direction: ISortDirection,
): (firstValue: T, secondValue: T) => number
```

**Parameters**

- `direction` `ISortDirection`: The direction to sort the values.

**Returns** `(firstValue: T, secondValue: T) => number`: A number indicating the sort order based on the primitive type.

**Throws**

- `MIXED_OR_UNSUPPORTED_DATA_TYPES`: if the values are mixed or are different to string | number | bigint

**Example**

```ts
import { sortPrimitives } from 'jg-utilities';

[3, 1, 2].sort(sortPrimitives('asc')); // => [1, 2, 3]
['b', 'c', 'a'].sort(sortPrimitives('desc')); // => ['c', 'b', 'a']
```

#### <a id="api-sortRecords"></a>`sortRecords`

*Function*

Sorts a list of record values by key based on their type and a sort direction.

```ts
sortRecords(
  key: string,
  direction: ISortDirection,
): (firstRecord: T, secondRecord: T) => number
```

**Parameters**

- `key` `string`: The key of the record to sort by.
- `direction` `ISortDirection`: The direction to sort the values.

**Returns** `(firstRecord: T, secondRecord: T) => number`: A number indicating the sort order based on the primitive type.

**Throws**

- `MIXED_OR_UNSUPPORTED_DATA_TYPES`: if the values are mixed or are different to string | number | bigint

**Example**

```ts
import { sortRecords } from 'jg-utilities';

[{ age: 40 }, { age: 25 }].sort(sortRecords('age', 'asc')); // => [{ age: 25 }, { age: 40 }]
```

#### <a id="api-sortRecordsWithBigIntString"></a>`sortRecordsWithBigIntString`

*Function*

Sorts a list of record values by key, treating stringified bigints as actual bigints, based on a sort direction.

```ts
sortRecordsWithBigIntString<T extends Record<string, unknown>>(
  key: keyof T,
  direction: ISortDirection,
): (firstRecord: T, secondRecord: T) => number
```

**Parameters**

- `key` `keyof T`: The key of the record to sort by.
- `direction` `ISortDirection`: The direction to sort the values.

**Returns** `(firstRecord: T, secondRecord: T) => number`: A number indicating the sort order based on the primitive type.

**Throws**

- `MIXED_OR_UNSUPPORTED_DATA_TYPES`: if the values are mixed or are different to string | number | bigint

**Example**

```ts
import { sortRecordsWithBigIntString } from 'jg-utilities';

[{ amount: '900000000000000000001' }, { amount: '5' }].sort(sortRecordsWithBigIntString('amount', 'asc')); // => [{ amount: '5' }, { amount: '900000000000000000001' }]
```

#### <a id="api-sortRecordsWithDateValue"></a>`sortRecordsWithDateValue`

*Function*

Sorts a list of record values by key, treating date values as actual Date objects, based on a sort direction.

```ts
sortRecordsWithDateValue<T extends Record<string, unknown>>(
  key: keyof T,
  direction: ISortDirection,
): (firstRecord: T, secondRecord: T) => number
```

**Parameters**

- `key` `keyof T`: The key of the record to sort by.
- `direction` `ISortDirection`: The direction to sort the values.

**Returns** `(firstRecord: T, secondRecord: T) => number`: A number indicating the sort order based on the date values.

**Throws**

- `MIXED_OR_UNSUPPORTED_DATA_TYPES`: if either record has a missing, null, or invalid date value

**Example**

```ts
import { sortRecordsWithDateValue } from 'jg-utilities';

[{ at: '2026-10-17' }, { at: '2025-01-01' }].sort(sortRecordsWithDateValue('at', 'asc')); // => [{ at: '2025-01-01' }, { at: '2026-10-17' }]
```

#### <a id="api-splitArrayIntoBatches"></a>`splitArrayIntoBatches`

*Function*

Splits an array into smaller arrays (batches) of a given size.

```ts
splitArrayIntoBatches<T>(items: T[], batchSize: number): T[][]
```

**Parameters**

- `items` `T[]`: The array to be split into batches.
- `batchSize` `number`: The size of each batch.

**Returns** `T[][]`: An array of arrays, where each inner array is a batch of the specified size.

**Throws**

- `INVALID_BATCH_SIZE`: if the batch size is not a valid integer greater than 0

**Example**

```ts
import { splitArrayIntoBatches } from 'jg-utilities';

splitArrayIntoBatches([1, 2, 3, 4, 5], 2); // => [[1, 2], [3, 4], [5]]
```

#### <a id="api-stringifyJson"></a>`stringifyJson`

*Function*

Serializes a JSON object with the JSON.stringify method.

```ts
stringifyJson<T>(value: T): string
```

**Parameters**

- `value` `T`: The value to be serialized. It should be an object or an array.

**Returns** `string`: A string representing the serialized JSON object.

**Throws**

- `UNSUPPORTED_DATA_TYPE`: if the provided value is not an object or an array
- `UNABLE_TO_SERIALIZE_JSON`: if the result of JSON.stringify is not a valid string
- `UNABLE_TO_SERIALIZE_JSON`: if an error is thrown during stringification

**Example**

```ts
import { stringifyJson } from 'jg-utilities';

stringifyJson({ id: 1, tags: ['a'] }); // => '{"id":1,"tags":["a"]}'
```

#### <a id="api-stringifyJsonDeterministically"></a>`stringifyJsonDeterministically`

*Function*

Stringifies a JSON object in a deterministic way, ensuring that the keys are sorted and the output is consistent.

```ts
stringifyJsonDeterministically<T>(value: T): string
```

**Parameters**

- `value` `T`: The value to be serialized. It should be an object or an array.

**Returns** `string`: A string representing the serialized JSON object with sorted keys.

**Throws**

- `UNSUPPORTED_DATA_TYPE`: if the provided value is not an object or an array
- `UNABLE_TO_SERIALIZE_JSON`: if the result of JSON.stringify is not a valid string
- `UNABLE_TO_SERIALIZE_JSON`: if an error is thrown during stringification

**Example**

```ts
import { stringifyJsonDeterministically } from 'jg-utilities';

stringifyJsonDeterministically({ b: 1, a: { d: 2, c: 3 } }); // => '{"a":{"c":3,"d":2},"b":1}'
```

#### <a id="api-stringifyValue"></a>`stringifyValue`

*Function*

Converts any value into a string. If the value is an object or an array, it will be stringified with JSON.stringify.

```ts
stringifyValue(value: unknown, jsonIndent?: number): string
```

**Parameters**

- `value` `unknown`: The value to convert into a string.
- `jsonIndent?` `number`: No description. Defaults to `0`.

**Returns** `string`: A string representation of the value.

**Example**

```ts
import { stringifyValue } from 'jg-utilities';

stringifyValue({ id: 1 }); // => '{"id":1}'
stringifyValue(42); // => '42'
stringifyValue(null); // => 'null'
```

#### <a id="api-toDate"></a>`toDate`

*Function*

Creates an instance of Date based on a value.

```ts
toDate(value: IDateValue): Date
```

**Parameters**

- `value` `IDateValue`: 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**

```ts
import { toDate } from 'jg-utilities';

toDate('2026-10-17T08:30:00.000Z'); // a Date for that instant
toDate(1_792_217_505_000); // a Date from a millisecond epoch
```

#### <a id="api-toMS"></a>`toMS`

*Function*

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".

```ts
toMS(
  str: `${number} years` | `${number} year` | `${number} months` | `${number} month` | `${number} weeks` | `${number} week` | `${number} days` | `${number} day` | `${number} hours` | `${number} hour` | `${number} minutes` | `${number} minute` | `${number} seconds` | `${number} second` | `${number} milliseconds` | `${number} millisecond`,
): number
```

**Parameters**

- `str` `` `${number} years` | `${number} year` | `${number} months` | `${number} month` | `${number} weeks` | `${number} week` | `${number} days` | `${number} day` | `${number} hours` | `${number} hour` | `${number} minutes` | `${number} minute` | `${number} seconds` | `${number} second` | `${number} milliseconds` | `${number} millisecond` ``: The time string to convert into milliseconds.

**Returns** `number`: The equivalent time in milliseconds.

**Throws**

- `INVALID_TIME_STRING`: if the provided time string is not a valid string.
- `INVALID_TIME_STRING`: if the chunks do not match the expected format.
- `INVALID_TIME_STRING`: if the value is not a valid positive integer.
- `INVALID_TIME_STRING`: if the unit is not one of the accepted values.

**Example**

```ts
import { toMS } from 'jg-utilities';

toMS('2 days'); // => 172800000
toMS('30 minutes'); // => 1800000
```

#### <a id="api-toSlug"></a>`toSlug`

*Function*

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 "-"

```ts
toSlug(value: string): string
```

**Parameters**

- `value` `string`: The string value to convert.

**Returns** `string`: A string converted to a slug.

**Throws**

- `UNABLE_TO_SLUGIFY_STRING`: if the resulting slug is empty after processing the input string

**Example**

```ts
import { toSlug } from 'jg-utilities';

toSlug('Héllo, World! 2026'); // => 'hello-world-2026'
```

#### <a id="api-toTitleCase"></a>`toTitleCase`

*Function*

Converts a string value into Title Case.

```ts
toTitleCase(value: string): string
```

**Parameters**

- `value` `string`: The string value to convert.

**Returns** `string`: A string converted to Title Case.

**Example**

```ts
import { toTitleCase } from 'jg-utilities';

toTitleCase('the QUICK brown fox'); // => 'The Quick Brown Fox'
```

#### <a id="api-toUtcDate"></a>`toUtcDate`

*Function*

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.

```ts
toUtcDate(value?: IDateValue): string
```

**Parameters**

- `value?` `IDateValue`: No description.

**Returns** `string`: The UTC date, such as `2026-10-17`.

**Throws**

- `INVALID_DATE`: The value is not a valid date in the years 0000 to 9999.

**Example**

```ts
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
```

#### <a id="api-toYouTubeEmbedUrl"></a>`toYouTubeEmbedUrl`

*Function*

Builds the URL that embeds a YouTube video in an iframe, on YouTube's privacy-enhanced domain, which sets no cookies until the video plays.

```ts
toYouTubeEmbedUrl(videoId: string): string
```

**Parameters**

- `videoId` `string`: The video id, such as one parseYouTubeVideoId read.

**Returns** `string`: The URL, `https://www.youtube-nocookie.com/embed/<id>`.

**Throws**

- `INVALID_YOUTUBE_VIDEO_ID`: The YouTube video id must be 11 letters, digits, '-', or '_'.

**Example**

```ts
import { toYouTubeEmbedUrl } from 'jg-utilities';

toYouTubeEmbedUrl('M7lc1UVf-VE'); // => 'https://www.youtube-nocookie.com/embed/M7lc1UVf-VE'
```

#### <a id="api-toYouTubeWatchUrl"></a>`toYouTubeWatchUrl`

*Function*

Builds the canonical watch page URL of a YouTube video, such as for a link.

```ts
toYouTubeWatchUrl(videoId: string): string
```

**Parameters**

- `videoId` `string`: The video id, such as one parseYouTubeVideoId read.

**Returns** `string`: The URL, `https://www.youtube.com/watch?v=<id>`.

**Throws**

- `INVALID_YOUTUBE_VIDEO_ID`: The YouTube video id must be 11 letters, digits, '-', or '_'.

**Example**

```ts
import { toYouTubeWatchUrl } from 'jg-utilities';

toYouTubeWatchUrl('M7lc1UVf-VE'); // => 'https://www.youtube.com/watch?v=M7lc1UVf-VE'
```

#### <a id="api-truncateText"></a>`truncateText`

*Function*

Truncates a string to a specified length and appends an ellipsis if it exceeds that length.

```ts
truncateText(text: string, maxLength: number): string
```

**Parameters**

- `text` `string`: The string value to truncate.
- `maxLength` `number`: The maximum length of the string before truncation.

**Returns** `string`: A string truncated to the specified length with an ellipsis if necessary.

**Example**

```ts
import { truncateText } from 'jg-utilities';

truncateText('The quick brown fox jumps', 12); // => 'The quick...'
```

#### <a id="api-truncateToUtf8Bytes"></a>`truncateToUtf8Bytes`

*Function*

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.

```ts
truncateToUtf8Bytes(text: string, maxBytes: number): string
```

**Parameters**

- `text` `string`: The string.
- `maxBytes` `number`: The most bytes the result may take in UTF-8, a non-negative safe integer.

**Returns** `string`: The string itself when it fits, otherwise its longest prefix that does.

**Throws**

- `INVALID_BYTE_LIMIT`: The byte limit must be a non-negative safe integer.

**Example**

```ts
import { truncateToUtf8Bytes } from 'jg-utilities';

truncateToUtf8Bytes('Hello, world', 5); // => 'Hello'
truncateToUtf8Bytes('café au lait', 4); // => 'caf'
truncateToUtf8Bytes('café au lait', 5); // => 'café'
```

#### <a id="api-waitUntil"></a>`waitUntil`

*Function*

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.

```ts
waitUntil<T>(
  probe: () => T | Promise<T>,
  options: IWaitUntilOptions,
): Promise<Exclude<T, false | null | undefined>>
```

**Parameters**

- `probe` `() => T | Promise<T>`: The function that checks the condition.
- `options` `IWaitUntilOptions`: The timeout, the interval between probes and the signal that stops the wait.

**Returns** `Promise<Exclude<T, false | null | undefined>>`: A promise that resolves with the first ready value.

**Throws**

- `WAIT_UNTIL_TIMED_OUT`: if the condition isn't met before the timeout.
- The probe's error when a probe throws or rejects.
- The signal's reason when the signal aborts.

**Example**

```ts
import { waitUntil } from 'jg-utilities';

const startedAt = Date.now();
const readStatus = (): string => (Date.now() - startedAt > 200 ? 'ready' : 'starting');

// the probe runs every 0.1 seconds until it returns something other than undefined
await waitUntil(() => (readStatus() === 'ready' ? readStatus() : undefined), {
  timeoutSeconds: 5,
  intervalSeconds: 0.1,
}); // => 'ready'
```

### <a id="api-constants"></a>Constants

#### <a id="api-ERRORS"></a>`ERRORS`

*Constant*

the codes for all the errors thrown by the library

```ts
const ERRORS: { MIXED_OR_UNSUPPORTED_DATA_TYPES: "MIXED_OR_UNSUPPORTED_DATA_TYPES"; UNSUPPORTED_DATA_TYPE: "UNSUPPORTED_DATA_TYPE"; INVALID_OR_EMPTY_ARRAY: "INVALID_OR_EMPTY_ARRAY"; INVALID_OR_EMPTY_OBJECT: "INVALID_OR_EMPTY_OBJECT"; UNABLE_TO_SERIALIZE_JSON: "UNABLE_TO_SERIALIZE_JSON"; UNABLE_TO_DESERIALIZE_JSON: "UNABLE_TO_DESERIALIZE_JSON"; INVALID_BATCH_SIZE: "INVALID_BATCH_SIZE"; UNABLE_TO_SLUGIFY_STRING: "UNABLE_TO_SLUGIFY_STRING"; INVALID_TIME_STRING: "INVALID_TIME_STRING"; INVALID_AUTHORIZATION_HEADER: "INVALID_AUTHORIZATION_HEADER"; INVALID_EMAIL_ADDRESS: "INVALID_EMAIL_ADDRESS"; INVALID_BASE64URL: "INVALID_BASE64URL"; WAIT_UNTIL_TIMED_OUT: "WAIT_UNTIL_TIMED_OUT"; INVALID_CURSOR: "INVALID_CURSOR"; INVALID_DATE: "INVALID_DATE"; INVALID_DAY_COUNT: "INVALID_DAY_COUNT"; INVALID_DATE_RANGE: "INVALID_DATE_RANGE"; INVALID_BYTE_LIMIT: "INVALID_BYTE_LIMIT"; INVALID_YOUTUBE_VIDEO_ID: "INVALID_YOUTUBE_VIDEO_ID"; INVALID_CONCURRENCY_LIMIT: "INVALID_CONCURRENCY_LIMIT"; INVALID_QUEUE_LIMIT: "INVALID_QUEUE_LIMIT"; REQUEST_QUEUE_FULL: "REQUEST_QUEUE_FULL"; REQUEST_COOLDOWN_ACTIVE: "REQUEST_COOLDOWN_ACTIVE"; REQUEST_COORDINATOR_CLOSED: "REQUEST_COORDINATOR_CLOSED"; CURSOR_TOO_LONG: "CURSOR_TOO_LONG" }
```

#### <a id="api-MAX_EMAIL_LENGTH"></a>`MAX_EMAIL_LENGTH`

*Constant*

the maximum length for an email address according to the standard (RFC 5321)

```ts
const MAX_EMAIL_LENGTH: 320
```

#### <a id="api-ONE_DAY_IN_MILLISECONDS"></a>`ONE_DAY_IN_MILLISECONDS`

*Constant*

one day in milliseconds

```ts
const ONE_DAY_IN_MILLISECONDS: number
```

#### <a id="api-ONE_HOUR_IN_MILLISECONDS"></a>`ONE_HOUR_IN_MILLISECONDS`

*Constant*

one hour in milliseconds

```ts
const ONE_HOUR_IN_MILLISECONDS: number
```

#### <a id="api-ONE_MINUTE_IN_MILLISECONDS"></a>`ONE_MINUTE_IN_MILLISECONDS`

*Constant*

one minute in milliseconds

```ts
const ONE_MINUTE_IN_MILLISECONDS: number
```

#### <a id="api-ONE_MONTH_IN_MILLISECONDS"></a>`ONE_MONTH_IN_MILLISECONDS`

*Constant*

one average month, a twelfth of an average year, in milliseconds

```ts
const ONE_MONTH_IN_MILLISECONDS: number
```

#### <a id="api-ONE_SECOND_IN_MILLISECONDS"></a>`ONE_SECOND_IN_MILLISECONDS`

*Constant*

one second in milliseconds

```ts
const ONE_SECOND_IN_MILLISECONDS: 1000
```

#### <a id="api-ONE_WEEK_IN_MILLISECONDS"></a>`ONE_WEEK_IN_MILLISECONDS`

*Constant*

one week in milliseconds

```ts
const ONE_WEEK_IN_MILLISECONDS: number
```

#### <a id="api-ONE_YEAR_IN_MILLISECONDS"></a>`ONE_YEAR_IN_MILLISECONDS`

*Constant*

one average year, of 365.25 days, in milliseconds

```ts
const ONE_YEAR_IN_MILLISECONDS: number
```

#### <a id="api-SECRET_TOKEN_PATTERNS"></a>`SECRET_TOKEN_PATTERNS`

*Constant*

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.

```ts
const SECRET_TOKEN_PATTERNS: readonly RegExp[]
```

### <a id="api-types"></a>Types

#### <a id="api-ICursorCodec"></a>`ICursorCodec`

*Type*

encodes a list's continuation as a cursor, and decodes one only for the list it came from

```ts
type ICursorCodec<TPayload, TScope> = {
    encode: (payload: TPayload) => string;
    decode: (cursor: string | null | undefined, scope: TScope) => TPayload | undefined;
};
```

**Members**

- `encode: (payload: TPayload) => string` (property)
- `decode: (cursor: string | null | undefined, scope: TScope) => TPayload | undefined` (property)

#### <a id="api-ICursorCodecOptions"></a>`ICursorCodecOptions`

*Type*

the options of createCursorCodec

```ts
type ICursorCodecOptions<TPayload, TScope> = {
    schema: ISchema<TPayload>;
    maxLength?: number;
    matchesScope?: (payload: TPayload, scope: TScope) => boolean;
};
```

**Members**

- `schema: ISchema<TPayload>` (property): validates the payload when it's encoded and when a cursor is decoded
- `maxLength: number` (property): the longest cursor, in characters, that's encoded or decoded. Defaults to 2048
- `matchesScope: (payload: TPayload, scope: TScope) => boolean` (property): 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.

#### <a id="api-ICursorValue"></a>`ICursorValue`

*Type*

the JSON a cursor can hold: an object or an array, as pagination continuations are

```ts
type ICursorValue = {
    [key: string]: IJsonValue;
} | IJsonValue[];
```

#### <a id="api-IDateTemplate"></a>`IDateTemplate`

*Type*

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

```ts
type IDateTemplate = 'date-short' | 'date-medium' | 'date-long' | 'time-short' | 'time-medium' | 'datetime-short' | 'datetime-medium' | 'datetime-long';
```

#### <a id="api-IDateValue"></a>`IDateValue`

*Type*

the value that can be used to create a Date instance

```ts
type IDateValue = Date | number | string;
```

#### <a id="api-IDelayOptions"></a>`IDelayOptions`

*Type*

the options that can be passed to the delay function

```ts
type IDelayOptions = {
    signal?: AbortSignal | undefined;
};
```

**Members**

- `signal: AbortSignal` (property): aborts the wait and rejects with the signal's reason

#### <a id="api-IEventBus"></a>`IEventBus`

*Type*

an in-process event bus, whose events and their payloads are the keys and values of TEvents

```ts
type IEventBus<TEvents extends Record<string, unknown>> = {
    emit: <TName extends keyof TEvents>(name: TName, event: TEvents[TName]) => void;
    subscribe: <TName extends keyof TEvents>(name: TName, handler: (event: TEvents[TName]) => unknown) => () => void;
    close: () => void;
};
```

**Members**

- `emit: (name: TName, event: TEvents[TName]) => void` (property): delivers the event to its handlers later, and returns at once
- `subscribe: (name: TName, handler: (event: TEvents[TName]) => unknown) => () => void` (property): registers a handler for an event, and returns the function that unsubscribes it
- `close: () => void` (property): drops the pending deliveries and every handler, and ignores later emits and subscriptions

#### <a id="api-IEventBusOptions"></a>`IEventBusOptions`

*Type*

what an event bus calls with each error a handler throws or rejects with

```ts
type IEventBusOptions<TEvents extends Record<string, unknown>> = {
    onError: (error: unknown, context: {
        name: keyof TEvents;
        event: TEvents[keyof TEvents];
    }) => void;
};
```

**Members**

- `onError: (error: unknown, context: { name: keyof TEvents; event: TEvents[keyof TEvents] }) => void` (property): receives the error and the event the handler was handling; anything it throws is swallowed

#### <a id="api-IFilterByQueryOptions"></a>`IFilterByQueryOptions`

*Type*

the options that can be used to filter items by a query

```ts
type IFilterByQueryOptions<T> = {
    queryProp?: keyof T;
    limit?: number;
};
```

**Members**

- `queryProp: keyof T` (property): the property to filter by, if not provided, the whole item will be used
- `limit: number` (property): the maximum number of items to return

#### <a id="api-IJsonValue"></a>`IJsonValue`

*Type*

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`.

```ts
type IJsonValue = string | number | boolean | null | IJsonValue[] | {
    [key: string]: IJsonValue | undefined;
};
```

#### <a id="api-INumberFormatConfig"></a>`INumberFormatConfig`

*Type*

Number Format Configuration The configuration that will be used to prettify a number.

```ts
type INumberFormatConfig = {
    minimumFractionDigits: number;
    maximumFractionDigits: number;
    prefix: string;
    suffix: string;
};
```

**Members**

- `minimumFractionDigits: number` (property)
- `maximumFractionDigits: number` (property)
- `prefix: string` (property)
- `suffix: string` (property)

#### <a id="api-IRedactSecretsOptions"></a>`IRedactSecretsOptions`

*Type*

the options accepted by redactSecrets

```ts
type IRedactSecretsOptions = {
    patterns?: readonly RegExp[];
    replacement?: string;
    preserveLineBreaks?: boolean;
};
```

**Members**

- `patterns: readonly RegExp[]` (property): the secrets to find, after the private keys. Defaults to SECRET_TOKEN_PATTERNS
- `replacement: string` (property): what each secret becomes. Defaults to '[redacted]'
- `preserveLineBreaks: boolean` (property): whether a private key keeps its line breaks, so the text's line numbers stay the same

#### <a id="api-IRedactSecretsResult"></a>`IRedactSecretsResult`

*Type*

the text redactSecrets returns, and how many secrets it replaced

```ts
type IRedactSecretsResult = {
    text: string;
    count: number;
};
```

**Members**

- `text: string` (property)
- `count: number` (property)

#### <a id="api-IRequestCoordinator"></a>`IRequestCoordinator`

*Type*

runs tasks within a concurrency limit and a bounded queue, and pauses them during a cooldown

```ts
type IRequestCoordinator = {
    run: <T>(task: () => Promise<T>, options?: IRunRequestOptions) => Promise<T>;
    deferUntil: (deadline: Date | number) => void;
    isIdle: () => boolean;
    close: () => void;
};
```

**Members**

- `run: (task: () => Promise<T>, options?: IRunRequestOptions) => Promise<T>` (property): runs the task once a slot is free and no cooldown is active, and settles as it does
- `deferUntil: (deadline: Date | number) => void` (property): pauses queued tasks until the deadline, a Date or epoch milliseconds, keeping a later one
- `isIdle: () => boolean` (property): whether no task runs or waits and no cooldown is active, such as before discarding it
- `close: () => void` (property): rejects the waiting tasks and every later run, and lets the running ones finish

#### <a id="api-IRequestCoordinatorOptions"></a>`IRequestCoordinatorOptions`

*Type*

the limits of createRequestCoordinator

```ts
type IRequestCoordinatorOptions = {
    maxConcurrent: number;
    maxQueued: number;
};
```

**Members**

- `maxConcurrent: number` (property): how many tasks run at once, at least 1
- `maxQueued: number` (property): how many tasks can wait for a slot, across every caller, at least 0

#### <a id="api-IRetryAsyncFunctionOptions"></a>`IRetryAsyncFunctionOptions`

*Type*

the options that can be passed to the retryAsyncFunction function

```ts
type IRetryAsyncFunctionOptions = {
    signal?: AbortSignal;
    shouldRetry?: (error: unknown) => boolean;
};
```

**Members**

- `signal: AbortSignal` (property): stops retrying and rejects with the signal's reason, even while an attempt is pending
- `shouldRetry: (error: unknown) => boolean` (property): decides whether a failed attempt is retried; returning false rethrows the error immediately

#### <a id="api-IRunRequestOptions"></a>`IRunRequestOptions`

*Type*

the options of one task a request coordinator runs

```ts
type IRunRequestOptions = {
    signal?: AbortSignal;
    caller?: unknown;
    maxQueuedForCaller?: number;
    rejectDuringCooldown?: boolean;
};
```

**Members**

- `signal: AbortSignal` (property): removes the task from the queue and rejects with the signal's reason when it aborts first
- `caller: unknown` (property): who the task runs for, compared with ===, so maxQueuedForCaller can limit each caller
- `maxQueuedForCaller: number` (property): how many of this caller's tasks can wait at once, at least 0
- `rejectDuringCooldown: boolean` (property): rejects with REQUEST_COOLDOWN_ACTIVE during a cooldown, instead of waiting it out

#### <a id="api-ISchema"></a>`ISchema`

*Type*

a validator for data from outside the program, such as a Zod schema: parse returns the data, or throws when it doesn't fit

```ts
type ISchema<T> = {
    parse(value: unknown): T;
};
```

**Members**

- `parse(value: unknown): T` (method)

#### <a id="api-ISortDirection"></a>`ISortDirection`

*Type*

the sort direction that can be applied to a list

```ts
type ISortDirection = 'asc' | 'desc';
```

#### <a id="api-ISubstitutionOptions"></a>`ISubstitutionOptions`

*Type*

the options that can be passed to the applySubstitutions function

```ts
type ISubstitutionOptions = {
    jsonIndent: number;
};
```

**Members**

- `jsonIndent: number` (property)

#### <a id="api-ITimeString"></a>`ITimeString`

*Type*

a duration such as '2 days' or '30 minutes', which toMS converts to milliseconds

```ts
type ITimeString = `${number} ${IUnit}`;
```

#### <a id="api-IUtcDateRange"></a>`IUtcDateRange`

*Type*

an inclusive range of UTC calendar dates, each as `YYYY-MM-DD`

```ts
type IUtcDateRange = {
    fromDate: string;
    toDate: string;
};
```

**Members**

- `fromDate: string` (property)
- `toDate: string` (property)

#### <a id="api-IUuidVersion"></a>`IUuidVersion`

*Type*

UUID Version The UUID versions supported by this library.

```ts
type IUuidVersion = 4 | 7;
```

#### <a id="api-IWaitUntilOptions"></a>`IWaitUntilOptions`

*Type*

the options that can be passed to the waitUntil function

```ts
type IWaitUntilOptions = {
    timeoutSeconds: number;
    intervalSeconds?: number;
    signal?: AbortSignal;
};
```

**Members**

- `timeoutSeconds: number` (property): the maximum number of seconds the whole call can take
- `intervalSeconds: number` (property): the number of seconds to wait between probes. Defaults to 1
- `signal: AbortSignal` (property): stops waiting and rejects with the signal's reason, even while a probe is pending
