npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@jossmac/lil-libs

v1.2.4

Published

A collection of little library helpers.

Readme

lil-libs

A small collection of TypeScript-first utilities for everyday application code. Each module is focused, composable, and ships with zero runtime dependencies.

Array

import {
  chunk,
  isIterable,
  isLength,
  isPopulatedArray,
  partition,
  createDeterministicKeySelector,
  toArray,
} from "@jossmac/lil-libs/array";

isIterable

Type guard that checks whether a value implements the iterable protocol.

isIterable(new Map()); // true
isIterable(new Set()); // true
isIterable([]); // true
isIterable({}); // false

isLength

Type guard for narrowing an array to a tuple of a specific length.

const values: number[] = [1, 2, 3];

if (isLength(values, 3)) {
  // values: [number, number, number]
}

isPopulatedArray

Type guard for narrowing an array to a non-empty tuple-like type.

const values: number[] = [1, 2, 3];

if (isPopulatedArray(values)) {
  // values: [number, ...number[]]
}

toArray

Returns an array for nullish, scalar, iterable, or array input.

toArray(null); // []
toArray(1); // [1]
toArray(new Set([1, 2])); // [1, 2]

chunk

Splits an array into fixed-size chunks.

The last chunk may be smaller than the given size if the array does not divide evenly.

chunk([1, 2, 3, 4], 2); // [[1, 2], [3, 4]]
chunk([1, 2, 3, 4, 5], 2); // [[1, 2], [3, 4], [5]]
chunk([], 2); // []

partition

Splits an array into two arrays using a predicate.

partition([1, 2, 3, 4], (n) => n % 2 === 0);
// [[2, 4], [1, 3]]

partition(["a", "bb", "ccc"], (s) => s.length > 1);
// [["bb", "ccc"], ["a"]]

Behaviour:

  • Returns a 2-item tuple: [matched, unmatched].
  • Preserves the original item order within both output arrays.
  • Passes (item, index, array) to the predicate.

createDeterministicKeySelector

Creates a function that deterministically maps a string to one of the provided keys using a stable hash.

const colors = ["red", "green", "blue"] as const;
const getColor = createDeterministicKeySelector(colors);

getColor("Albert"); // 'blue'
getColor("Barbara"); // 'green'
getColor("Charlie"); // 'red'

const color = getColor("David");
//    ^? 'red' | 'green' | 'blue' (inferred return type)

Behaviour:

  • Returns the same key for the same input every time.
  • Preserves literal key types (for as const arrays).
  • Supports empty input strings.
  • Throws if called with an empty keys array.

Assert

import { assert, assertNever, ensure } from "@jossmac/lil-libs/assert";

assert

Asserts that a value is present (or that a boolean is true).

function getName(id: number): string | undefined;

const maybeName = getName(123);
//    ^? string | undefined

assert(maybeName, "Name is required");
const name = maybeName;
//    ^? string

Behaviour:

  • Throws for false, null, and undefined.
  • Does not throw for other falsy values like 0 and "".
  • Narrows types after the assertion.

assertNever

Throws for unreachable branches in discriminated unions.

switch (status.kind) {
  case "idle":
  case "loading":
  case "done":
    break;
  default:
    assertNever(status.kind);
}

ensure

A convenience wrapper around assert that returns the value if the assertion passes.

function findUser(id: number): User | null;

const user = ensure(findUser(123), "User is required");
//    ^? User

Console

import { errorOnce, warnOnce } from "@jossmac/lil-libs/console";

errorOnce

Logs each unique error message only once per runtime instance.

errorOnce("API request failed");
errorOnce("API request failed"); // ignored

warnOnce

Logs each unique warning message only once per runtime instance.

warnOnce("Using fallback value");
warnOnce("Using fallback value"); // ignored

Constants

import { UNICODE_CHARS } from "@jossmac/lil-libs/constants";

UNICODE_CHARS

Common unicode characters used to compose UI text content. Use when you need explicit control over spacing and line-break behavior in UI copy.

`${10}${UNICODE_CHARS.narrowNoBreakSpace}kg`; // "10 kg"
`Page${UNICODE_CHARS.noBreakSpace}1`; // stays on one line
`USD${UNICODE_CHARS.wordJoiner}/${UNICODE_CHARS.wordJoiner}EUR`; // keeps the token together
`super${UNICODE_CHARS.zeroWidthSpace}long`; // invisible optional wrap point

narrowNoBreakSpace

A narrow form of a no-break space, typically the width of a thin or mid space. Use when two tokens should stay together with tighter spacing than a normal space (for example number-unit pairs or compact punctuation spacing).

noBreakSpace

A space character that prevents an automatic line break at its position. Use when two adjacent words or symbols must remain on the same line while keeping normal space width.

wordJoiner

A non-breaking form of the zero-width space. Use when you must prevent a line break between characters without introducing any visible spacing.

zeroWidthSpace

Intended for invisible word separation and for line-break control; it has no width. Use when you want to add optional wrap points in long unbroken text without adding visible spaces.

Error

import {
  ensureError,
  isError,
  isErrorLike,
  parseError,
} from "@jossmac/lil-libs/error";

isError

Guard for native Error instances.

isError(new Error("boom")); // true
isError("boom"); // false

isErrorLike

Guard for error-like objects exposing a string message property.

isErrorLike({ message: "boom" }); // true
isErrorLike({ message: 123 }); // false

parseError

Returns a human-readable error message from unknown input.

parseError(new Error("Boom")); // "Boom"
parseError("Something went wrong"); // "Something went wrong"
parseError({ message: "from object" }); // "from object"

parseError(null); // "An unknown error occurred."
parseError({ message: 123 }); // "An unknown error occurred."
parseError(null, "Custom fallback"); // "Custom fallback"

Behaviour:

  • Returns error.message for native Error values.
  • Returns value.message for error-like objects where message is a string.
  • Returns string inputs as-is.
  • Returns a fallback message for all other values.

ensureError

Returns an Error instance from unknown thrown input.

ensureError(new Error("boom")); // same Error instance
ensureError("boom"); // Error("boom")
ensureError({ message: "boom", name: "CustomError" }); // Error with copied metadata

Function

import {
  isDefined,
  lazy,
  noop,
  not,
  resolveMaybeFn,
} from "@jossmac/lil-libs/function";

noop

Does nothing and returns undefined.

button.addEventListener("click", noop);

isDefined

Type guard for filtering out null and undefined without losing type precision.

const bad = [1, null, 2, undefined, 3].filter(Boolean);
//    ^? (number | null | undefined)[]

const good = [1, null, 2, undefined, 3].filter(isDefined);
//    ^? number[]

not

Inverts a predicate.

const isEven = (n: number) => n % 2 === 0;
const isOdd = not(isEven);

isOdd(3); // true
isOdd(4); // false

resolveMaybeFn

Returns a value directly or by invoking a unary function.

resolveMaybeFn(42); // 42
resolveMaybeFn((x: number) => x * 2, 21); // 42

lazy

Returns a lazily computed value that is cached after first access. Access the result via .value.

const settings = lazy(() => loadSettings());

settings.value; // computes once
settings.value; // cached

Datetime

import { relativeTime } from "@jossmac/lil-libs/datetime";

relativeTime

Formats a date as relative time for nearby past or future values and falls back to a date string once the value is 24 hours away or more.

relativeTime(new Date(Date.now() - 1_000 * 60)); // "1 minute ago"
relativeTime(new Date(Date.now() + 1_000 * 60 * 5)); // "in 5 minutes"
relativeTime(new Date(Date.now() - 1_000), { numeric: "auto" }); // "Just now"
relativeTime(new Date(Date.now() - 1_000 * 60), { style: "short" }); // "1 min. ago"

relativeTime(
  new Date(Date.now() - 1_000 * 60 * 60 * 24),
  {},
  { dateStyle: "medium" },
); // "Jan 6, 2026" (locale-dependent)

Signature:

function relativeTime(
  value: Date | string,
  relativeOptions?: {
    numeric?: Intl.RelativeTimeFormatNumeric;
    style?: Intl.RelativeTimeFormatStyle;
  },
  dateOptions?: Intl.DateTimeFormatOptions,
): string;

Behaviour:

  • Accepts a Date or ISO 8601 string.
  • Returns relative output (e.g. "1 minute ago" or "in 5 minutes") for past and future values within 24 hours.
  • Returns a localized date string once the value is 24 hours old or more.
  • Uses the same 24-hour cutoff for future dates; after that it falls back to a date string.
  • With numeric: "auto", values within 10 seconds return "Just now".
  • Throws a TypeError for invalid date input.
  • Supports relative formatting via numeric and style options.
  • Supports custom date formatting via Intl.DateTimeFormat options.

DOM

import {
  ariaCurrent,
  atScrollBottom,
  atScrollLeft,
  atScrollRight,
  atScrollTop,
  getAbsoluteClientRect,
  getComputedStyle,
  getDisplayMode,
  hasScrollX,
  hasScrollY,
  isHtmlElement,
  isKeyboardInput,
  isTouchCapable,
  isTouchDevice,
  joinIds,
  nearestComputedStyle,
  querySelector,
  querySelectorAll,
  toDataAttributes,
} from "@jossmac/lil-libs/dom";

isHtmlElement

Type guard for narrowing unknown values to HTMLElement.

const el = document.querySelector("#app");
//    ^? Element | null

if (isHtmlElement(el)) {
  el.tabIndex = -1; // safe to operate on `el` as an `HTMLElement` type
}

isKeyboardInput

Checks whether an event target is an element that can trigger the software keyboard, on mobile devices.

const textInput = document.createElement("input");
textInput.type = "text";

const checkbox = document.createElement("input");
checkbox.type = "checkbox";

isKeyboardInput(textInput); // true
isKeyboardInput(checkbox); // false
isKeyboardInput(document.createElement("textarea")); // true

isTouchCapable

Returns true when the device can receive touch input.

Some devices support both touch and mouse input, such as laptop computers with a touchscreen.

isTouchCapable(); // true on touch-capable devices, otherwise false

isTouchDevice

Returns true when the primary pointer is "coarse" (for example, touch input).

isTouchDevice(); // true on coarse-pointer devices, otherwise false

querySelector

Thin wrapper around the Element.querySelector() method, which qualifies the returned value as an HTMLElement.

const container = document.createElement("div");
container.innerHTML = "<button>Save</button><svg><circle /></svg>";

querySelector(container, "button"); // HTMLButtonElement
querySelector(container, "circle"); // null (non-HTMLElement)
querySelector(null, "button"); // null

querySelectorAll

Thin wrapper around the Element.querySelectorAll() method, which returns HTMLElement[] instead of NodeListOf<Element>.

const container = document.createElement("div");
container.innerHTML = "<span>One</span><span>Two</span><svg><circle /></svg>";

querySelectorAll(container, "span"); // [HTMLSpanElement, HTMLSpanElement]
querySelectorAll(container, "span, circle"); // spans only
querySelectorAll(undefined, "span"); // []

getComputedStyle

Returns a computed style value for regular CSS properties and CSS variables.

const el = document.createElement("div");

getComputedStyle(el, "color"); // e.g. "rgb(0, 0, 0)"
getComputedStyle(el, "line-height"); // e.g. "normal"
getComputedStyle(el, "--space-10"); // custom property value

nearestComputedStyle

Walks up the parent chain until it finds a non-empty computed style value.

const parent = document.createElement("div");
const child = document.createElement("span");
parent.appendChild(child);

nearestComputedStyle(child, "color");
// value from child if present, otherwise nearest parent value

getDisplayMode

Returns the current display mode for browser and PWA contexts.

Possible values:

  • "fullscreen"
  • "minimal-ui"
  • "picture-in-picture"
  • "standalone"
  • "window-controls-overlay"
  • "browser"
getDisplayMode();
// e.g. "browser" in a tab, "standalone" in an installed PWA

ariaCurrent

Returns the appropriate aria-current value for a nav item based on the current path.

ariaCurrent("/about", "/about"); // "page"
ariaCurrent("/about/team", "/about"); // "true"
ariaCurrent("/contact", "/about"); // "false"
ariaCurrent("/about-us", "/about"); // "false" (not a child match)

Behaviour:

  • Returns "page" for exact path matches.
  • Returns "true" for child routes (for example /about/team under /about).
  • Returns "false" for non-matches and null pathnames.
  • Normalizes trailing slashes (except root) before matching.

joinIds

Joins IDs for ARIA attributes like aria-labelledby and aria-describedby.

joinIds("title-id", "description-id"); // "title-id description-id"
joinIds("title-id", null, undefined, "", false, "description-id"); // "title-id description-id"
joinIds(null, undefined, "", false); // undefined

Behaviour:

  • Filters out falsy values (null, undefined, false, "").
  • Returns IDs joined by a single space.
  • Returns undefined when no valid IDs remain.

toDataAttributes

Converts object keys to HTML data-* attributes.

toDataAttributes({
  isSelected: true,
  panelIndex: 2,
  count: 0,
  empty: "",
});
// {
//   "data-selected": true,
//   "data-panel-index": 2,
//   "data-count": 0,
// }

toDataAttributes(
  { isSelected: true, isolated: true, empty: "" },
  { omitFalsyValues: false, trimBooleanKeys: false },
);
// {
//   "data-is-selected": true,
//   "data-isolated": true,
//   "data-empty": "",
// }

Behaviour:

  • Converts camelCase keys to kebab-case.
  • Omits null and undefined values.
  • Omits false and "" by default.
  • Preserves 0 values, even when omitFalsyValues is true.
  • Trims leading is from boolean-style keys by default.
  • Only trims leading is when it is followed by an uppercase letter (isSelected -> data-selected, isolated -> data-isolated).

getAbsoluteClientRect

Returns getBoundingClientRect() values offset by document scroll, giving absolute page coordinates.

const rect = getAbsoluteClientRect(document.body);

rect.top;
rect.left;
rect.width;
rect.height;

hasScrollX

Checks whether an element has horizontal overflow.

hasScrollX(document.body); // true or false

hasScrollY

Checks whether an element has vertical overflow.

hasScrollY(document.body); // true or false

atScrollTop

Checks whether an element is at the top of its scroll range.

Uses <= 0 to handle elastic scrolling behavior.

atScrollTop(document.documentElement); // true or false

atScrollBottom

Checks whether an element is at the bottom of its scroll range.

Uses >= to handle elastic scrolling behavior.

atScrollBottom(document.documentElement); // true or false

atScrollLeft

Checks whether an element is at the left edge of its scroll range.

Uses <= 0 to handle elastic scrolling behavior.

atScrollLeft(document.documentElement); // true or false

atScrollRight

Checks whether an element is at the right edge of its scroll range.

Uses >= to handle elastic scrolling behavior.

atScrollRight(document.documentElement); // true or false

JSON

import {
  stringifyWithBigIntAsString,
  stringifyWithSortedKeys,
} from "@jossmac/lil-libs/json";

stringifyWithBigIntAsString

Serialises JSON while converting BigInt values to strings.

JSON.stringify({ id: 123n });
// ⚠ Uncaught TypeError: Do not know how to serialize a BigInt

stringifyWithBigIntAsString({ id: 123n });
// '{"id":"123"}'

stringifyWithSortedKeys

Serialises deterministic JSON by sorting object keys at every nesting level.

stringifyWithSortedKeys({ b: 2, a: 1 });
// '{"a":1,"b":2}'

stringifyWithSortedKeys([{ z: 1, a: 2 }]);
// '[{"a":2,"z":1}]'

Behaviour:

  • Object keys are sorted alphabetically.
  • Array order is preserved.
  • undefined object properties are omitted.

Number

import {
  clamp,
  findNearest,
  isAscending,
  isDescending,
  isFiniteNumber,
  isNumber,
  lerp,
  remap,
  roundToPrecision,
  roundToStep,
  sequence,
  unlerp,
} from "@jossmac/lil-libs/number";

isNumber

Runtime guard for JavaScript numbers, excluding NaN.

isNumber(42); // true
isNumber(Infinity); // true
isNumber(NaN); // false
isNumber("foo"); // false
isNumber({}); // false

isFiniteNumber

A convenience wrapper around isNumber, that also checks whether the value is finite.

isFiniteNumber(42); // true
isFiniteNumber(Infinity); // false

isAscending

Checks whether an array is in ascending order (allowing equal neighbouring values).

isAscending([1, 1, 2, 3]); // true
isAscending([3, 2, 1]); // false

isDescending

Checks whether an array is in descending order (allowing equal neighbouring values).

isDescending([3, 3, 2, 1]); // true
isDescending([1, 2, 3]); // false

clamp

Constrains a number to an inclusive range.

clamp(5, 0, 10); // 5
clamp(-5, 0, 10); // 0
clamp(15, 0, 10); // 10

roundToPrecision

Rounds a number to a specified number of fractional digits.

roundToPrecision(3.14159, 2); // 3.14
roundToPrecision(3.005, 2); // 3.01

Signature:

function roundToPrecision(value: number, digits: number, base?: number): number;

Behaviour:

  • digits <= 0 behaves like Math.round().
  • base defaults to 10; most callers should leave it unchanged.

roundToStep

Rounds a number to the nearest step interval.

roundToStep(5.26, 0.25); // 5.25
roundToStep(-5.26, 0.25); // -5.25

Behaviour:

  • Throws for step = 0.
  • Throws for non-finite step values like Infinity and NaN.

findNearest

Returns the closest value from a list, with configurable tie-breaking.

const items = [1, 3, 5, 7, 9];

findNearest(4, items); // 3 (default bias: "first")
findNearest(4, items, "last"); // 5
findNearest(4, items, "smaller"); // 3
findNearest(4, items, "larger"); // 5

Bias options:

  • "first" / "last" — prefer the item that appears earlier or later in the array.
  • "smaller" / "larger" — prefer the numerically smaller or larger tied value.
  • Throws if items is empty.

sequence

Generates inclusive numeric sequences in ascending or descending order.

sequence(1, 5); // [1, 2, 3, 4, 5]
sequence(5, 1); // [5, 4, 3, 2, 1]
sequence(0, 1, 0.33); // [0, 0.33, 0.66, 0.99]

Behaviour:

  • Includes both start and end when reachable by step increments.
  • Supports negative step input (uses absolute step size).
  • Derives decimal precision from the provided step.
  • Throws for step = 0 or non-finite step values.

lerp

Linear interpolation between two values.

lerp(0, 100, 0.25); // 25

unlerp

Inverse interpolation that returns a clamped factor in the 0..1 range.

unlerp(0, 100, 25); // 0.25

// unlerp clamps outside-range values to 0..1
unlerp(0, 10, -5); // 0
unlerp(0, 10, 15); // 1

remap

Maps a value from one numeric range to another using linear interpolation.

remap(5, [0, 10], [0, 100]); // 50
remap(-5, [-10, 0], [0, 100]); // 50
remap(7.5, [0, 10], [-20, -10]); // -12.5

remap(-5, [0, 10], [0, 100]); // 0 (clamped)
remap(15, [0, 10], [0, 100]); // 100 (clamped)

remap(15, [0, 10], [0, 100], { clamp: false }); // 150

Behaviour:

  • Clamps to the output range by default.
  • Supports negative and floating-point ranges.
  • Allows extrapolation with { clamp: false }.
  • Handles degenerate input ranges (from === to) predictably.

Object

import {
  TObject,
  isPlainObject,
  typedEntries,
  typedFromEntries,
  typedKeys,
} from "@jossmac/lil-libs/object";

isPlainObject

Checks whether a value is a plain object (including Object.create(null)).

isPlainObject({}); // true
isPlainObject(Object.create(null)); // true
isPlainObject([]); // false
isPlainObject(new Date()); // false

typedKeys

Typed alternative to Object.keys() that preserves key inference.

const obj = { foo: 1, bar: "hello" };

const keys = typedKeys(obj);
//    ^? ("foo" | "bar")[]

typedEntries

Typed alternative to Object.entries() that preserves key/value tuples.

const obj = { foo: 1, bar: "hello" };

const entries = typedEntries(obj);
//    ^? (["foo", number] | ["bar", string])[]

typedFromEntries

Typed alternative to Object.fromEntries() that preserves output shape.

const entries = [
  ["foo", 1],
  ["bar", "hello"],
] as const;

const rebuilt = typedFromEntries(entries);
//    ^? { foo: number; bar: string }

TObject

Provides a namespace-like wrapper around the typed object helpers.

const keys = TObject.keys({ foo: 1, bar: "hello" });
//    ^? ("foo" | "bar")[]

Random

Exports a single random object containing all methods to avoid naming collisions.

import { random } from "@jossmac/lil-libs/random";

random.bool

Returns random boolean values.

random.bool(); // true or false

random.int

Generates random integers in an inclusive range.

random.int(1, 3); // 1, 2, or 3
random.int(10, 1); // still valid

Behaviour:

  • random.int(min, max) is inclusive of both bounds.
  • Reversed bounds are automatically normalised.

random.float

Generates random floating-point numbers in a half-open range.

random.float(10, 20); // 10 <= n < 20
random.float(20, 10); // still valid

Behaviour:

  • random.float(min, max) is inclusive of min and exclusive of max.
  • Reversed bounds are automatically normalised.

random.choice

Returns one random item from an array.

random.choice(["a", "b", "c"]); // one item

random.sample

Returns a randomly sampled subset without mutating the original array.

const items = ["a", "b", "c", "d"];

random.sample(items, 2); // e.g. ["d", "a"]
random.sample(items); // single-item sample
random.sample(items, 0); // []

items; // still ["a", "b", "c", "d"]

Behaviour:

  • Defaults to count = 1.
  • Returns an empty array when count is 0.
  • Returns all items when count equals the array length.
  • Never mutates the input array.

random.shuffle

Returns a shuffled copy without mutating the original array.

const items = [1, 2, 3, 4];

random.shuffle(items); // shuffled copy
items; // unchanged

random.shuffler

Creates a function that returns a newly shuffled copy on each call.

const items = [1, 2, 3, 4];

const shuffleNow = random.shuffler(items);
shuffleNow(); // shuffled copy each call

random.sampler

Creates a function that returns a random sample on each call.

const items = [1, 2, 3, 4];

const sampleTwo = random.sampler(items, 2);
sampleTwo(); // random 2-item sample each call

String

import {
  base64Encode,
  contains,
  formatInitials,
  isString,
  pluralize,
} from "@jossmac/lil-libs/string";

isString

Type guard for string values.

isString("hello"); // true
isString(123); // false

base64Encode

Encodes UTF-8 strings to base64, or to a base64 data URI when a MIME type is provided.

base64Encode("hello");
// "aGVsbG8="

base64Encode("hello", "text/plain");
// "data:text/plain;base64,aGVsbG8="

Behaviour:

  • Supports Unicode input.
  • Preserves MIME type in data URI output.
  • Optimises SVG payload whitespace when mimeType is "image/svg+xml".

contains

Case-insensitive and diacritic-insensitive substring matching.

contains("café", "cafe"); // true
contains("Hello World", "world"); // true
contains("hello", ""); // true
contains("hello", "bye"); // false

Signature:

function contains(string: string, substring: string, locale?: string): boolean;

Behaviour:

  • Uses Intl.Collator with accent-insensitive and case-insensitive matching.
  • Accepts an optional locale, which defaults to "en".
  • Returns true for an empty substring.

pluralize

Returns singular/plural forms with optional count prefix.

pluralize(1, "wallet"); // "1 wallet"
pluralize(2, "wallet"); // "2 wallets"
pluralize(2, ["person", "people"]); // "2 people"
pluralize(2, ["person", "people"], false); // "people"

formatInitials

Returns initials for names with Unicode-aware grapheme support.

formatInitials("John Doe"); // "JD"
formatInitials("John Henry Doe"); // "JD"
formatInitials("John Henry Doe", { maxLetters: 3 }); // "JHD"
formatInitials("John Ronald Reuel Tolkien"); // "JT"
formatInitials("John Ronald Reuel Tolkien", { maxLetters: 3 }); // "JRR"
formatInitials("Élodie Durand"); // "ÉD"
formatInitials("ilker", { locale: "tr" }); // "İL"
formatInitials("李小龍"); // "李小"

Signature:

function formatInitials(
  name: string,
  options?: {
    maxLetters?: number;
    locale?: string;
  },
): string;

Behaviour:

  • Defaults to maxLetters = 2.
  • Uses the first letter from the first word and the first letter from the last word when maxLetters === 2.
  • Uses up to one letter from each word, left to right, when maxLetters >= 3.
  • For single-word names, uses the first maxLetters letters.
  • Returns "?" for empty or whitespace-only input.
  • Throws when maxLetters is not finite or is less than 1.

Types

import type {
  Maybe,
  NonNullableValues,
  Prettify,
  Satisfies,
  SomeOptional,
  SomeRequired,
  TupleOf,
  UnknownRecord,
  Widen,
} from "@jossmac/lil-libs/types";

Maybe<T>

Represents a maybe-present value for app-level checks.

type MaybeName = Maybe<string>;
//   ^? string | null | undefined

Prettify<T>

Flattens intersections and mapped types into a cleaner displayed shape.

type Raw = { id: string } & { name: string };
type User = Prettify<Raw>;
//   ^? { id: string; name: string }

Satisfies<T, Base>

Constrains T to be assignable to Base while preserving T's full detail.

type Endpoint = Satisfies<
  { method: "GET"; path: "/users" },
  { method: "GET" | "POST"; path: string }
>;
//   ^? { method: "GET"; path: "/users" }

NonNullableValues<T>

Removes null and undefined from each property value type.

type Input = { id: string | null; age?: number | undefined };
type Output = NonNullableValues<Input>;
//   ^? { id: string; age?: number }

SomeRequired<T, K>

Makes a subset of keys required while leaving all other keys unchanged.

type Input = { id?: string; name?: string; active?: boolean };
type Output = SomeRequired<Input, "id">;
//   ^? { id: string; name?: string; active?: boolean }

SomeOptional<T, K>

Makes a subset of keys optional while leaving all other keys unchanged.

type Input = { id: string; name: string; active: boolean };
type Output = SomeOptional<Input, "active">;
//   ^? { id: string; name: string; active?: boolean }

TupleOf<T, N>

Builds a fixed-length tuple of N elements of type T.

type Triple = TupleOf<number, 3>;
//   ^? [number, number, number]

Widen<T>

Widens literals to their broader primitive types.

type A = Widen<"hello">;
//   ^? string
type B = Widen<42>;
//   ^? number

UnknownRecord

Alias for a generic object map with unknown values.

type Payload = UnknownRecord;
//   ^? Record<string, unknown>

Development

Setup

Prerequisites:

  • Node.js 24 (active LTS)
  • pnpm 10.33.0 (managed via Corepack or installed globally)
nvm use
pnpm install

Scripts

pnpm check          # run all static checks
pnpm check:types    # TypeScript
pnpm check:lint     # ESLint
pnpm check:format   # Prettier

pnpm test           # run tests once
pnpm test:watch     # watch mode
pnpm test:coverage  # run tests with v8 coverage

pnpm release patch  # bump patch, create tag, push commit + tag
pnpm release minor  # bump minor, create tag, push commit + tag
pnpm release major  # bump major, create tag, push commit + tag

pnpm release patch -- --no-push  # keep release commit + tag local

When ready:

pnpm publish --access public