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

@guebbit/js-toolkit

v2.2.0

Published

Small, dependency-free TypeScript helpers for the things that keep coming up: array and object reshaping, string matching, ranges and distances, time conversion, cookies, query strings, and the DOM chores that predate a framework.

Downloads

1,220

Readme

@guebbit/js-toolkit

Small, dependency-free TypeScript helpers for the things that keep coming up: array and object reshaping, string matching, ranges and distances, time conversion, cookies, query strings, and the DOM chores that predate a framework.

Every function is a default export of its own module and is re-exported by name from the package root. Nothing here wraps a library — if lodash already does it well, it is not in this toolkit.

  • No runtime dependencies
  • TypeScript throughout, with declarations published for both module formats
  • Real ESM and CommonJS builds, so import and require both work natively
  • Every helper is importable on its own, and sideEffects: false lets a bundler drop whatever you do not use
  • Node 20+

Install

npm install @guebbit/js-toolkit

Usage

import { arrayChunks, getMapDistance, match, setUrlQueries } from '@guebbit/js-toolkit'

arrayChunks(['a', 'b', 'c', 'd', 'e'], 2) // [['a', 'b', 'c'], ['d', 'e']]
getMapDistance(0, 3, 0, 4) // 5
match('Ipsum', 'lorem ipsum sit') // true  — case-insensitive, first inside second
setUrlQueries({ tags: ['a', 'b'], page: 2 }) // 'tags=a%2Cb&page=2'

Import a single helper instead, when you would rather not go through the barrel:

import getMapDistance from '@guebbit/js-toolkit/getMapDistance'

CommonJS works the same way:

const { arrayChunks } = require('@guebbit/js-toolkit')
const getDelta = require('@guebbit/js-toolkit/getDelta').default

There is no default export on the barrel — it exports names. Every one of these forms, both module systems and both subpath styles, is exercised against a real packed tarball on each build.

Browser and Node

Most helpers are environment-agnostic. The DOM helpers need a document (a browser or jsdom), and deleteFile is Node-only — it is the single module that imports node:fs/promises. Because each helper is its own module and the package is side-effect free, importing the pure ones from a server bundle does not drag the DOM ones in.

API

Arrays and objects

| Signature | What it does | | ------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | | arrayChunks<T>(array: T[], n: number): T[][] | Split into n sub-arrays whose lengths differ by at most one. | | arrayColumns(haystack: Record<string, unknown>[], columns: string \| string[]): unknown[] | PHP's array_column, extended to several columns. One entry per row, always. | | arrayDepth<T>(check: T \| T[]): number | Nesting depth. 0 for a non-array. | | associativeSlice(object, start: number, end: number): Record<string, unknown> | Array.prototype.slice for an object's own keys, in insertion order. | | coerceStringArray(value?: unknown): string[] | Any value to a trimmed string[]. Splits comma-separated strings, drops blanks. | | canonicalize(value: unknown, throwOnCircular?: boolean): unknown | Recursively sorts object keys so JSON.stringify of the result is a stable cache key. Arrays keep their order, undefined is dropped, Date becomes ISO. |

Strings

| Signature | What it does | | ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | levenshteinDistance(a?, b?): number | Edit distance. 999 when both sides are absent — "nothing to compare", not "identical". | | match(check?, match?, sensitive?, distance?): boolean | Fuzzy compare. distance selects the mode: -2 two-way substring, -1 one-way substring (default), 0 exact, 1+ maximum edit distance. | | getUuid(): string | crypto.randomUUID() where available, timestamp + random otherwise. Not for cryptographic use. |

match takes { sensitive?, mode?, maxDistance? }. The mode says what a match means:

| Mode | True when | | ----------- | ---------------------------------------------------------- | | exact | the two are equal | | contains | check contains against | | contained | check is inside against — the default | | either | one is inside the other, whichever way round | | fuzzy | their edit distance is at most maxDistance (default 0) |

Both sides are trimmed, and lowercased unless sensitive is set. Equality satisfies every mode.

match('Ipsum', 'lorem ipsum') // true   — default 'contained'
match('lorem ipsum', 'Ipsum', { mode: 'contains' }) // true
match('lorem ipsum', 'lorem ispum', { mode: 'fuzzy', maxDistance: 2 }) // true

Errors

| Signature | What it does | | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | | extractErrorMessage(error: unknown, fallback?): string | The readable message out of any caught or rejected value, fallback (empty by default) when there is none. |

instanceof Error is not enough for an HTTP client: an interceptor that normalises failures rejects with a plain object, and every instanceof test then shows its fallback instead of the message the server sent. Consulted in order — a bare string, an Error, a message on the value itself, then .data.message and .response.data.message. The nested lookup runs only when the levels above it came up empty, so it can add a message but never replace one.

extractErrorMessage(new Error('Boom')) // 'Boom'
extractErrorMessage({ status: 400, message: 'Email taken' }) // 'Email taken'
extractErrorMessage({ response: { data: { message: 'Nope' } } }) // 'Nope'
extractErrorMessage(undefined, 'Something went wrong') // 'Something went wrong'

Numbers and ranges

| Signature | What it does | | --------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | getDelta(a: number, b: number, size?: number): number | Distance between two numbers. With a positive size they sit on a wrapping space and the shorter way around wins, never more than size / 2. | | getMapDistance(Xa, Xb, Ya, Yb, size?): number | Euclidean distance between two points; with a positive size both axes wrap. | | rangeOverlaps(firstStart, firstEnd, secondStart, secondEnd, sameUnitOverlap?): number | Number of overlapping units, 0 when disjoint. | | getOverlapRange(firstStart, firstEnd, secondStart, secondEnd): [number, number] | The intersection itself. [0, 0] when there is none; touching ranges do not count. |

Time

| Signature | What it does | | --------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | | secondsToTime(time?: number): ISecondsToTimeMap | Milliseconds to every unit at once, both depleting (hours) and total (hoursOnly). | | formatDuration(seconds: number, options?): string | A duration rendered compactly: 2h 15m, 3d 4h 5m. Largest requested unit absorbs everything above it. | | timeToSeconds(date?: string, delimiter?: string): number | 'HH:MM:SS:ms' to milliseconds. Components may be omitted from the right. | | getExecTime<T>(fn: () => T \| Promise<T>): Promise<{ result: T; time: number }> | Time a sync or async function, resolving with its result and elapsed milliseconds. |

JSON

| Signature | What it does | | ------------------------------------------------------------ | ---------------------------------------------------------------------- | | getJson(json?: string): unknown | Parse any JSON value, undefined if empty or invalid. | | isJson<T>(test: string): Record<string, T> \| T[] \| false | Parse a JSON structure, false for a bare value or invalid input. |

Neither writes to the console — the return value is the report.

DOM

| Signature | What it does | | ---------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | | appendChildren(element, ...children): HTMLElement \| Element | appendChild for arrays, through one document fragment. | | eventDelegate(eventName, childSelector, callback, parent?): void | Delegated listener; this inside the callback is the matched child. | | formatNodeList(elementsArray?): HTMLElement[] | Any element, array, NodeList or HTMLCollection to a plain static array. | | getElementCenter(element: Element): number[] | [x, y] centre from the bounding rect. | | getForm(form: HTMLElement \| null, selectors?): Record<string, unknown> | Every named field's value, keyed by name. | | getIframe(iframe?): HTMLElement \| HTMLBodyElement \| undefined | The iframe document's body, undefined if unreachable. | | getIndex(element: HTMLElement \| null): number | jQuery's .index(). -1 when there is no parent. | | getSiblings(element): Element[] | jQuery's .siblings(). | | getValue(element: HTMLElement \| null, attribute?): string \| number \| boolean \| undefined | Value of an input, textarea, select, checkbox, radio group, attribute or text node. | | isInViewport(element: Element, fully?: boolean): boolean | Partially, or entirely, inside the viewport. |

Browser platform

| Signature | What it does | | --------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | | copyToClipboard(text: string): Promise<boolean> | Clipboard API where available, hidden-textarea fallback otherwise. | | downloadBlob(data: Blob \| BlobPart, filename: string, type?): void | Trigger a client-side download. | | getCookie(name) / setCookie(name, value, options?) / deleteCookie(name, path?, domain?) | Read, write and remove cookies. Options: days, path, domain, secure, sameSite. | | getUrlQueries(search?, arraySeparator?): Record<string, string \| string[]> | Parse a query string. Repeated or separator-joined keys become arrays. Defaults to location.search, and to '' where there is no location. | | setUrlQueries(query, merge?, arraySeparator?): string | Build a query string. Empty values are dropped, or removed from merge. | | toFormData(object, form?, namespace?): FormData | Object to FormData, nesting as a[b][c]. Blob and File are appended whole. |

Both query helpers are framework-agnostic: hand them any router's query string, or apply the result with history.replaceState.

Node

| Signature | What it does | | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------- | | deleteFile(filePath: string, onError?): Promise<boolean> | Delete a file. Resolves false if it did not exist; onError fires for any other failure. |

Exported types

ISecondsToTimeMap (the shape secondsToTime returns, every field required), ISetCookieOptions (the third argument to setCookie), and IMatchOptions / TMatchMode (the second argument to match) are all importable from the package root.

Contributing

npm run complete:check   # lint, format, typecheck, build, test
npm test                 # unit, property and type tests
npm run test:pack        # packaging smoke test
npm run test:mutation    # mutation testing

FAST_CHECK_SEED=$RANDOM FAST_CHECK_RUNS=1000 npm test   # explore past the fixed seed

The suite is layered — unit, property-based, type-level, packaging and mutation — and each layer catches something the others cannot. TESTING.md explains what each one is for, how the per-file mutation baseline gate works, and why some surviving mutants are left alone on purpose.

License

AGPL-3.0. See LICENSE.