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

q-js-utils

v1.3.6

Published

A collection of JavaScript utilities.

Readme

q-js-utils

A collection of JavaScript utilities.

Installation

npm install q-js-utils

Or

yarn add q-js-utils

Usage

For the entire library (convenience, but potentially larger bundle if not tree-shaken by consumer's bundler):

import { darkOrLight, str2Hex, getInitials } from 'q-js-utils';
// Or
// import * as utils from 'q-js-utils';

const isDark = darkOrLight('#000');
const nameToHex = str2Hex('Muhamad Husein');
const initialName = getInitials('Muhamad Husein');

console.log(isDark); // true
console.log(nameToHex); // f529de
console.log(initialName); // MH

For specific modules (recommended for tree-shaking):

import { darkOrLight } from 'q-js-utils/darkOrLight';
import { str2Hex } from 'q-js-utils/str2Hex';
import { getInitials } from 'q-js-utils/getInitials';

Utilities

For numeric data

import { isNumber } from 'q-js-utils/isNumber';
import { isNegative } from 'q-js-utils/isNegative';
import { padWithLeadingZeros } from 'q-js-utils/padWithLeadingZeros';

const one = 1;
const minus = -1;

console.log(isNumber(one)); // true

console.log(isNegative(one)); // false
console.log(isNegative(minus)); // true

console.log(padWithLeadingZeros(one)); // '01'

debounce

Debouncing an input for an autocomplete search

import { debounce } from 'q-js-utils/debounce';

const searchInput = document.getElementById('searchInput') as HTMLInputElement;

const performSearch = (query: string) => {
  console.log(`Performing search for: ${query}`);
  // In a real app, you would make an API call here
};

const debouncedSearch = debounce(performSearch, 300); // Wait 300ms after last keypress

if (searchInput) {
  searchInput.addEventListener('input', (event) => {
    const target = event.target as HTMLInputElement;
    debouncedSearch(target.value);
  });
}

Debouncing a window resize event

import { debounce } from 'q-js-utils/debounce';

const handleResize = () => {
  console.log(`Window resized to: ${window.innerWidth}x${window.innerHeight}`);
};
// Wait 250ms after resizing stops
const debouncedResize = debounce(handleResize, 250);
window.addEventListener('resize', debouncedResize);

debounceAdvanced

Resetting a Form Before Debounced Submission

import { debounceAdvanced } from 'q-js-utils/debounceAdvanced';

const submitForm = debounceAdvanced(
  (formData: { email: string }) => console.log("Submitting:", formData),
  1000
);

// User clicks submit but then clicks "Reset"
submitForm({ email: "[email protected]" });
submitForm.cancel(); // Stops the submission if the form was reset

Avoiding Unnecessary Resize/Scroll Handlers

import { debounceAdvanced } from 'q-js-utils/debounceAdvanced';

const logScrollPosition = debounceAdvanced(
  () => console.log("Current scroll Y:", window.scrollY),
  200
);

window.addEventListener("scroll", logScrollPosition);

// When navigating away, cancel pending calls
window.removeEventListener("scroll", logScrollPosition);
logScrollPosition.cancel(); // Cleanup

Game Input Handling (Cancel on Player Death)

import { debounceAdvanced } from 'q-js-utils/debounceAdvanced';

const shootBullet = debounceAdvanced(
  () => console.log("🔥 Pew!"),
  300,
  { leading: true } // Immediate first shot
);

shootBullet(); // Fires instantly
shootBullet(); // Queued for 300ms later...

// Player dies before the next shot
shootBullet.cancel(); // Prevents queued shots

throttle

Basic usage

import { throttle } from 'q-js-utils/throttle';

const throttledScroll = throttle((position: number) => {
  console.log('Current scroll position:', position);
}, 100);

window.addEventListener('scroll', () => throttledScroll(window.scrollY));

With default wait time (300ms)

import { throttle } from 'q-js-utils/throttle';

const throttledClick = throttle(() => console.log('Clicked!'));
button.addEventListener('click', throttledClick);

throttleAdvanced

Cancellation Support (cancel())

import { throttleAdvanced } from 'q-js-utils/throttleAdvanced';

const throttledScroll = throttleAdvanced(handleScroll, 100);
window.addEventListener('scroll', throttledScroll);

// Later...
throttledScroll.cancel(); // Stops any pending execution

Immediate Execution (flush())

import { throttleAdvanced } from 'q-js-utils/throttleAdvanced';

const throttledApiCall = throttleAdvanced(fetchData, 1000);
throttledApiCall("query");

// Force execute if waiting
throttledApiCall.flush();

Pending Check (pending())

if (throttledFn.pending()) {
  console.log("Waiting to execute...");
}

Example Use Cases:

Cancellable Scroll Handler

import { throttleAdvanced } from 'q-js-utils/throttleAdvanced';

const scrollHandler = throttleAdvanced((position: number) => {
  console.log("Current scroll:", position);
}, 200);

window.addEventListener('scroll', () => scrollHandler(window.scrollY));

// When leaving the page
window.addEventListener('beforeunload', () => {
  scrollHandler.cancel(); // Cleanup
});

Throttled API Calls with Manual Flush

import { throttleAdvanced } from 'q-js-utils/throttleAdvanced';

const searchAPI = throttleAdvanced(async (query: string) => {
  const results = await fetch(`/search?q=${query}`);
  displayResults(results);
}, 500);

searchInput.addEventListener('input', (e) => {
  searchAPI(e.target.value);
});

// "Search Now" button forces execution
searchButton.addEventListener('click', () => {
  searchAPI.flush();
});

Game Loop with Cancellation

import { throttleAdvanced } from 'q-js-utils/throttleAdvanced';

const gameUpdate = throttleAdvanced((deltaTime: number) => {
  updatePlayerPosition(deltaTime);
}, 16); // ~60fps

gameLoop.on('update', gameUpdate);

// When game pauses
pauseButton.addEventListener('click', () => {
  gameUpdate.cancel(); // Stop pending updates
});

request

import { request } from 'q-js-utils/request';

try {
  const todo = await request('https://jsonplaceholder.typicode.com/todos/1');
  console.log('request Todo:', todo);
} catch (error) {
  console.error('request Error:', error);
}

console.log('Download Progress');
try {
  const blob = await request('https://httpbin.org/bytes/1048576', {
    // @ts-ignore
    // responseType: "hell",
    responseType: "blob",
    onProgress: (progress) => {
      console.log('progress: ', progress);
      if (progress.total) {
        console.log(
          `Progress: ${progress.loaded} / ${progress.total} bytes (${(
            progress.progress! * 100
          ).toFixed(2)}%)`,
        );
      } else {
        console.log(`Progress: ${progress.loaded} bytes loaded (total unknown)`);
      }
    },
  });

  console.log('Download blob: ', blob);
  console.log('Download complete. Blob size: ', blob?.size, 'bytes');
} catch (error: any) {
  console.error('4. Download Progress Error: ', error);
  for(let err in error){
    console.error('err: ', err);
  }
}

nextId

Generates a unique, sequentially incremented string ID with an optional prefix. Each call increments an internal counter to ensure uniqueness.

import { nextId } from '../src/nextId';

console.log(`nextId to ${nextId()}`);
console.log(`nextId to ${nextId()}`);
console.log(`nextId to ${nextId('x')}`);

cached

import { cache } from 'q-js-utils/cache';

const sayHi = cache(name => 'Hi, ' + name);

darkOrLight

import { darkOrLight } from 'q-js-utils/darkOrLight';

const isDark = darkOrLight('#000');

str2Hex

import { str2Hex } from 'q-js-utils/str2Hex';

const nameToHex = str2Hex('Muhamad Husein');

getInitials

import { getInitials } from 'q-js-utils/getInitials';

const initialName = getInitials('Muhamad Husein');

obj2FormData

import { obj2FormData } from 'q-js-utils/obj2FormData';

const objData = {
  name: "Muhamad Husein",
  email: "[email protected]"
};
const dataForm = obj2FormData(objData);

isEqual

Deeply compares two values to determine if they are equal. Handles primitives, arrays, objects, Maps, Sets, Dates, RegExps, and circular references.

Can be significantly faster than Lodash's _.isEqual() in most cases, while still handling all the same edge cases.

import { isEqual } from 'q-js-utils/isEqual';

isEqual({ a: 1, b: [2, 3] }, { a: 1, b: [2, 3] }) // true
isEqual({ a: 1 }, { a: 1, b: undefined }) // false
isEqual({ a: 1, b: [2, 3] }, { b: [2, 3], a: 1 }) // true
isEqual(NaN, NaN) // true

cn

Joins class names together, filtering out falsy values. @param {...(string | boolean | null | undefined)} classes - Class names or conditional expressions. returns string Combined class names as a single string, or undefined (to prevent class="" not render in node).

import { cn } from 'q-js-utils/cn';

const isActive = true;
const hasError = false;
const emptyString = '';
const zero = 0;
const nullVar = null;
const undefinedConst = undefined;
let undefinedLet;

// Returns: "btn active" (when isActive is true and hasError is false)
cn('btn', isActive && 'active', hasError && 'error');

// undefined
cn(
  hasError && 'error',
  emptyString && 'emptyString',
  zero && 'zero',
  nullVar && 'nullVar',
  undefinedConst && 'undefinedConst',
  undefinedLet && 'undefinedLet',
);

uuidv7

Generate a UUIDv7 (time-ordered, monotonic). RFC 9562: https://www.rfc-editor.org/rfc/rfc9562.html

import { uuidv7 } from 'q-js-utils/uuidv7';

// Lexicographically sorted even if generated in the same millisecond.
console.log('1. uuidv7:', uuidv7());
console.log('2. uuidv7:', uuidv7());
console.log('3. uuidv7:', uuidv7());

// Example output:
// 018f3fcd-04d1-7a8f-9f4a-1b33a5a2f7c9
// 018f3fcd-04d1-7a90-8a77-3a13f20dcbae
// 018f3fcd-04d1-7a91-81df-1d92ff45a4ec

download

Triggers a file download from a given Blob or File object in modern browsers.

import { download } from 'q-js-utils/download';

// Basic
download(text);

// Custom filename
download(text, {
  name: "greeting.txt"
});

// Custom timeout to cleanup createObjectURL
download(text, {
  timeout: 1000,
});

// Whether to append anchor to DOM for Safari compatibility
download(text, {
  append: true,
});

// All
download(text, {
  name: "greeting.txt",
  timeout: 1000,
  append: true,
});

shape

Shapes an object by picking or omitting specified keys, with TypeScript inferring exact key types.

import { shape } from 'q-js-utils/shape';

const user = {
  id: 1,
  name: "Husein",
  email: "[email protected]",
  role: "admin",
};

const pick = shape(user, ["id", "name"] as const); // { id: 1; name: "Husein" }

const omit = shape(user, ["id", "name"] as const, true); // { email: "[email protected]"; role: "admin" }

capitalize

Capitalizes the first letter.

import { capitalize } from 'q-js-utils/capitalize';

console.log(capitalize("husein"));     // Husein
console.log(capitalize("élève"));      // "Élève"
console.log(capitalize("mañana"));     // "Mañana"