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

@maxigarcia/js-utils

v1.22.1

Published

JavaScript Utility Functions

Readme

@maxigarcia/js-utils

A small, dependency-free JavaScript/TypeScript utility library for everyday frontend work: arrays, class names, debouncing, deep comparison, HTTP requests, storage, text encoding, URL handling, and object helpers. Published as an ES module with TypeScript types.

What it is

@maxigarcia/js-utils bundles helpers you often copy between projects—things like joining conditional CSS classes, debouncing handlers, typed fetch wrappers, namespaced localStorage, or reading query strings from the current page. Each function lives in its own module, is tree-shakeable via the package entry point, and ships with Vitest coverage.

The package targets browser and Node environments where the relevant APIs exist (fetch, window, URL, Storage, TextEncoder, btoa/atob, etc.). URL helpers that read or mutate the current page default to window.location.href when no URL is passed.

Install

npm install @maxigarcia/js-utils

Quick start

import {
  addParamsToUrl,
  cn,
  createStorage,
  debounce,
  decodeText,
  deepEqual,
  encodeText,
  getParamFromUrl,
  http,
  tryParseJson,
  uniqueBy,
} from '@maxigarcia/js-utils';

const className = cn('btn', isActive && 'btn-active', null, 'w-full');
const onResize = debounce(() => console.log('resized'), 300);
const same = deepEqual({ a: [1] }, { a: [1] });
const users = uniqueBy([{ id: 1 }, { id: 2 }, { id: 1 }], 'id');

const token = encodeText('hello'); // Base64 (UTF-8 safe)
const page = getParamFromUrl('page');
const nextUrl = addParamsToUrl('https://example.com', { page: 2, q: 'hello' });

const api = http('/api/users');
const created = await api.post<{ id: number }>({ body: { name: 'Ada' } });

const preferences = createStorage<{ theme: string }>('preferences');
preferences.setJson({ theme: 'dark' });

const data = tryParseJson(`{ name: "Ada", tags: ["js"], }`); // lenient parse

API

uniqueBy

Returns a new array with the first occurrence of each item kept, keyed by key. Values are compared with deepEqual, so object and array keys work.

uniqueBy([{ id: 1 }, { id: 2 }, { id: 1 }], 'id');
// → [{ id: 1 }, { id: 2 }]

cn

Joins truthy class fragments into one string. Falsy values (false, null, undefined, 0, '') are dropped; booleans used as flags are ignored.

cn('card', isOpen && 'card--open', disabled && 'card--disabled');
// → "card card--open"

debounce

Returns a debounced wrapper that delays calling fn until delay ms have passed without another invocation.

const save = debounce((value: string) => persist(value), 500);

deepEqual

Recursively compares two values. Arrays are compared by length and element; plain objects by own keys and values. Primitives use ===.

deepEqual({ x: [1, { y: 2 }] }, { x: [1, { y: 2 }] }); // true

createStorage

Creates a namespaced helper around localStorage (or any Storage). Keys are prefixed as app-storage-${name}.

const userStorage = createStorage<{ id: number; name: string }>('user');

userStorage.setJson({ id: 1, name: 'Ada' });
userStorage.getJson(); // → { id: 1, name: 'Ada' }

userStorage.setItem('raw-string');
userStorage.getItem(); // → "raw-string"

userStorage.removeItem();
userStorage.clear();

| Method | Description | | ---------------- | ---------------------------------------------- | | getItem() | Reads the namespaced string value, or null. | | setItem(value) | Writes a string value. | | removeItem() | Removes the namespaced key. | | clear() | Clears the underlying storage entirely. | | getJson() | Parses and returns the stored JSON, or null. | | setJson(value) | Stringifies and stores a typed value. |

encodeText / decodeText

Encodes and decodes strings with UTF-8 via TextEncoder / TextDecoder and Base64 (btoa / atob), so non-ASCII text round-trips correctly.

String helpers

| Function | Description | | ----------------------------- | --------------------------------------------------------------------------------------------- | | capitalize(value) | Uppercases the first character and lowercases the rest. Empty strings are returned unchanged. | | unwrapString(value) | Strips one or more layers of surrounding single or double quotes. | | removeTrailingCommas(value) | Removes trailing commas before } or ] in a string (useful before JSON parsing). |

Object helpers

| Function | Description | | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | | isRecord(value) | Type guard: plain object (not null, not an array). | | getNestedValue(obj, path) | Reads a dot-separated path (e.g. "user.address.city"). | | toFlatObject(obj, parentKey?) | Flattens nested objects into dot-notation keys. | | tryParseJson(value) | Parses JSON after normalizing unquoted keys, unwrapping quoted strings, and removing trailing commas. Returns undefined on failure. |

URL helpers

Helpers that read or mutate the current page accept an optional url string; if omitted, they use window.location.href.

| Function | Description | | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | getParamsFromUrl(url?) | Returns a URLSearchParams instance for the URL’s query string. | | getParamFromUrl(key, url?) | Gets one decoded query param, or null if missing. | | addParamsToUrl(baseUrl, params?) | Appends query params to baseUrl. Values may be string, number, boolean, null, or undefined; empty / null / undefined are skipped. | | pushParamsToUrl(url) | Calls history.pushState with the given URL (updates the address bar). | | removeParamFromUrl(key, url?) | Deletes one query param; returns the updated URL string. | | isValidHttpUrl(url) | true if the string is a valid http: or https: URL. | | getUrlDomain(url) | Returns the hostname, or null if the URL is invalid. |

Example:

import { addParamsToUrl, pushParamsToUrl } from '@maxigarcia/js-utils';

const url = addParamsToUrl('https://example.com/settings', {
  tab: 'profile',
  page: 1,
  draft: null, // omitted
});
// → "https://example.com/settings?tab=profile&page=1"

pushParamsToUrl(url);

http

Creates a small typed client around fetch for a base URL. JSON responses are parsed automatically; non-OK responses throw HttpError. Plain objects and arrays in body are JSON-stringified (with Content-Type: application/json); other body types are passed through. Query params use the same shape as addParamsToUrl.

import { http, isHttpError } from '@maxigarcia/js-utils';

const users = http('/api/users');

const list = await users.get<{ id: number; name: string }[]>({
  params: { page: 1, q: 'ada' },
});

const created = await users.post<{ id: number }>({
  body: { name: 'Ada' },
});

users.abort(); // cancels in-flight requests from this client

| Method | Description | | ------------------ | ------------------------------------------------------------------------- | | get(options?) | GET request; returns parsed JSON (or undefined for empty / non-JSON). | | post(options?) | POST with optional body. | | put(options?) | PUT with optional body. | | patch(options?) | PATCH with optional body. | | delete(options?) | DELETE request. | | abort() | Aborts all in-flight requests started by this client. |

Options accept standard RequestInit fields (except method, signal, and body shape noted above), plus params for query string values. Failed responses throw HttpError (status + message); network failures throw NetworkError; aborted requests throw AbortRequestError. Type guards: isHttpError, isNetworkError, isAbortRequestError.

Development

npm install
npm run lint
npm test
npm run build

Build output goes to dist/ and is what gets published (exports point at ./dist/index.js and ./dist/index.d.ts).

License

ISC — see package.json.