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

@just-io/utils

v2.0.1

Published

Utility functional

Downloads

172

Readme

@just-io/utils

Lightweight TypeScript utility functions.

Installation

npm install @just-io/utils

Utilities

debounce / debounceByKey

Debounce function calls with optional key-based grouping.

import { debounce, debounceByKey } from '@just-io/utils';

// Basic debounce - executes after delay with last arguments
function setFilterValue(values: string[]): void {
    console.log('Filter:', values);
}

const debouncedSetFilterValue = debounce(setFilterValue, 1000);

debouncedSetFilterValue(['first']);
// wait 300ms...
debouncedSetFilterValue(['second']); // Only ['second'] executes after 1000ms

// Cancel pending execution
debouncedSetFilterValue.teardown();

// Key-based debounce - separate timers per extracted key
function notify(event: { type: string; at: number }): void {
    console.log(event.type, event.at);
}

const debouncedNotify = debounceByKey(notify, (event) => event.type, 1000);

debouncedNotify({ type: 'change', at: 0 });
debouncedNotify({ type: 'update', at: 0 }); // Both execute - different keys
debouncedNotify({ type: 'change', at: 10 }); // Replaces first 'change', only at:10 executes

// Cancel by key or all
debouncedNotify.teardown({ type: 'change', at: 0 });
debouncedNotify.teardownAll();

memo / memoByKey

Memoize function calls - skip execution if arguments haven't changed.

import { memo, memoByKey } from '@just-io/utils';

// Basic memo - only calls when extracted values change
function notify(event: { type: string; at: number }): void {
    console.log(event.type, event.at);
}

const memoNotify = memo(notify, (event) => [event.type, event.at]);

memoNotify({ type: 'change', at: 0 }); // Executes
memoNotify({ type: 'change', at: 0 }); // Skipped - same values
memoNotify({ type: 'change', at: 1 }); // Executes - 'at' changed

// Only trigger on changes (skip first call)
function setFilterValue(values: string[]): void {
    console.log('Filters changed:', values);
}

const memoSetFilterValue = memo(setFilterValue, (values) => values, true);

memoSetFilterValue([]); // Skipped (initial call)
memoSetFilterValue(['first']); // Executes (values changed)

// Key-based memo - separate memo state per key
function notifyChannel(channel: string, event: { type: string; at: number }): void {
    console.log(channel, event);
}

const memoNotifyChannel = memoByKey(
    notifyChannel,
    (channel) => channel,
    (channel, event) => [event.type, event.at],
);

memoNotifyChannel('channel1', { type: 'change', at: 0 }); // Executes
memoNotifyChannel('channel2', { type: 'change', at: 0 }); // Executes (different key)
memoNotifyChannel('channel1', { type: 'change', at: 0 }); // Skipped
memoNotifyChannel('channel1', { type: 'change', at: 10 }); // Executes (values changed)

// Key-based with onlyChanges
function setFilter(filter: string, values: string[]): void {
    console.log(filter, values);
}

const memoSetFilter = memoByKey(
    setFilter,
    (filter) => filter,
    (filter, values) => values,
    true,
);

memoSetFilter('filter1', []); // Skipped (initial)
memoSetFilter('filter1', ['1']); // Executes (changed)

DeepMap

Map with array keys (nested key paths). Useful for hierarchical data.

import { DeepMap } from '@just-io/utils';

// Create empty or with initial entries
const deepMap = new DeepMap<string, string>();
const withEntries = new DeepMap<string, string>([
    [['one'], 'str'],
    [['one', 'two'], 'str'],
]);

// Copy from another DeepMap
const copy = new DeepMap(withEntries);

// Set and get with array keys
deepMap.set([], 'root'); // Empty key path
deepMap.set(['one'], 'first');
deepMap.set(['one', 'two'], 'nested');

deepMap.get(['one']); // 'first'
deepMap.get(['one', 'two']); // 'nested'
deepMap.get(['missing']); // undefined

// Check existence
deepMap.has(['one']); // true
deepMap.has(['missing']); // false

// Delete entries
deepMap.delete(['one', 'two']); // returns true
deepMap.delete(['missing']); // returns false

// Take: get and delete in one operation
const value = deepMap.take(['one']); // 'first', now deleted
deepMap.take(['missing']); // undefined

// Size and clear
deepMap.size; // number of entries
deepMap.clear(); // remove all

// Iteration
deepMap.forEach((value, key) => console.log(key, value));

for (const [key, value] of deepMap) {
    console.log(key, value);
}

Array.from(deepMap.entries()); // [[key, value], ...]
Array.from(deepMap.keys()); // [key, ...]
Array.from(deepMap.values()); // [value, ...]

// Clone subtree (non-destructive)
deepMap.set(['one'], 'str');
deepMap.set(['one', 'two'], 'str');
const cloned = deepMap.clone(['one']);
// cloned: [[], 'str'], [['two'], 'str']

// Extract subtree (removes from original)
const extracted = deepMap.extract(['one']);
// extracted has the subtree, deepMap no longer has it

// Append entries under a prefix
deepMap.append(['one'], [[['two'], 'str']]);
// Result: [['one', 'two'], 'str']

EventEmitter / Notifier

Type-safe event handling.

import { EventEmitter, EventTuple, Notifier } from '@just-io/utils';

// Notifier - single event type
type Event = [first: string, second: string];

const notifier = new Notifier<Event>();

const handler = (first: string, second: string) => console.log(first, second);
notifier.subscribe(handler);
notifier.notify('a', 'b'); // logs: a b

// One-time subscription
notifier.subscribe((a, b) => console.log('once:', a, b), { once: true });
notifier.notify('x', 'y'); // Both handlers called
notifier.notify('x', 'y'); // Only permanent handler

notifier.unsubscribe(handler); // Remove specific
notifier.unsubscribeAll(); // Remove all

// EventEmitter - multiple named events
type EventMap = {
    one: [first: number];
    two: [first: string, second: number];
};

const emitter = new EventEmitter<EventMap>();

emitter.on('one', (first) => console.log('one:', first));
emitter.on('two', (first, second) => console.log('two:', first, second));

emitter.emit('one', 1);
emitter.emit('two', '1', 2);

// One-time listener
emitter.once('one', (first) => console.log('once:', first));

// Emit from tuple (useful for batching)
type ETuple = EventTuple<EventMap>;
const events: ETuple[] = [
    ['one', 1],
    ['two', '2', 1],
];
for (const event of events) {
  emitter.emit(...event);
}

// Unsubscribe
emitter.off('one', handler);
emitter.unsubscribeAll('one'); // Remove all 'one' handlers
emitter.unsubscribeAll(); // Remove all handlers

// Event store - batch events and emit later
const store = emitter.makeStore();
store.add('one', 1);
store.add('one', 2);
store.emit(); // Emits both events
store.emit(); // Does nothing (already emitted)

License

MIT