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

@jvstack/helpers

v1.4.1

Published

Collection of helpers i use in all my projects

Readme

@jvstack/helpers

Collection of helpers I use in all my projects.

Installation

npm install @jvstack/helpers

Exports

| Export | Description | | ------------------- | ------------------------------------------------------------------------ | | ObjectExtensions | Typed wrapper for object entries, keys, values, omit, pick, and extend | | ArrayExtensions | Array subclass for zipping, grouping, deduplication, and set-like checks | | functionUtils | Small function helpers such as identity, noop, and throwError | | Matcher | Type-safe matcher for discriminated unions | | Policy | Composable allow/deny rules | | IndexedCollection | Key-indexed collection of items | | Counter | Mutable counts keyed by property keys |

ObjectExtensions

Wraps a plain object and returns typed helpers instead since the default ones are not properly typed.

import { ObjectExtensions } from "@jvstack/helpers";

const obj = new ObjectExtensions({ a: 1, b: 2, c: 3 });

obj.entries(); // [["a", 1], ["b", 2], ["c", 3]]
obj.keys(); // ["a", "b", "c"]
obj.values(); // [1, 2, 3]

obj.omit("b").unwrap(); // { a: 1, c: 3 }
obj.pick("a", "c").unwrap(); // { a: 1, c: 3 }
obj.extend({ d: 4 }).unwrap(); // { a: 1, b: 2, c: 3, d: 4 }
obj.with({ a: "some string" }) // { a: "some string", b: 2, d: 4 }

ObjectExtensions.fromEntries([
  ["a", 1],
  ["b", true],
] as const).unwrap(); // { a: 1, b: true }

ArrayExtensions

Array subclass with helpers for zipping, grouping, deduplication, chunking, and set-like checks. Methods that return arrays return new ArrayExtensions instances so they can be chained. Use zipStrict and zipByStrict when every pair must exist; they throw instead of returning undefined.

import { ArrayExtensions } from "@jvstack/helpers";

const items = new ArrayExtensions(1, 2, 3, 2);

items; // [1, 2, 3, 2]

items.hasSome([3, 4]); // true
items.hasEvery([2, 3]); // true

items.zipStrict(["a", "b", "c", "d"]); // [[1, "a"], [2, "b"], [3, "c"], [2, "d"]]
items.zip(["a", "b"]); // [[1, "a"], [2, "b"], [3, "c"], [2, undefined]]

const left = [{ id: 1 }, { id: 2 }];
const right = [
  { id: 2, name: "b" },
  { id: 1, name: "a" },
];

new ArrayExtensions(...left).zipByStrict(right, (a, b) => a.id === b.id);
// [[{ id: 1 }, { id: 1, name: "a" }], [{ id: 2 }, { id: 2, name: "b" }]]

new ArrayExtensions(...[{ id: 1 }]).zipBy([{ id: 2 }], (a, b) => a.id === b.id);
// [[{ id: 1 }, undefined]]

const users = [
  { id: "1", role: "admin", name: "Alice" },
  { id: "2", role: "user", name: "Bob" },
  { id: "3", role: "admin", name: "Carol" },
];

new ArrayExtensions(...users).indexBy((user) => user.id);
// { "1": ..., "2": ..., "3": ... }

items.enumerate(); // [[0, 1], [1, 2], [2, 3], [3, 2]]
items.unique(); // [1, 2, 3]
new ArrayExtensions(...users).uniqueBy((user) => user.id); // first item per id

const groups = new ArrayExtensions(...users).groupBy((user) => user.role);
groups.admin; // both admin users
groups.user; // the user

new ArrayExtensions(1, 2, 3, 4, 5).windows(2);
// [[1, 2], [2, 3], [3, 4], [4, 5]]

ArrayExtensions.rangeInclusive(1, 5, 2);
// [1, 3, 5]

ArrayExtensions.rangeExclusive(1, 5);
// [1, 2, 3, 4]

new ArrayExtensions(1, 2, 3, 4, 5).lastIndex;
// 4

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

functionUtils

Common function helpers for defaults, placeholders, and typed passthrough.

import { functionUtils } from "@jvstack/helpers";

functionUtils.identity(42); // 42

const items = [1, 2, 3].map(functionUtils.identity);

functionUtils.noop();
await functionUtils.asyncNoop();

functionUtils.throwError(new Error("failed")); // never returns

Matcher

Matches on a discriminant field and narrows the union inside each branch callback.

import { Matcher } from "@jvstack/helpers";

type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "square"; side: number };

const circle: Shape = { kind: "circle", radius: 5 };

const area = new Matcher(circle, "kind").match({
  circle: (value) => Math.PI * value.radius ** 2,
  square: (value) => value.side ** 2,
});

Event handling example:

type Event =
  | { type: "click"; x: number; y: number }
  | { type: "keydown"; key: string };

const label = new Matcher({ type: "click", x: 1, y: 2 }, "type").match({
  click: (event) => `${event.x},${event.y}`,
  keydown: (event) => event.key,
});

Policy

Model validation or authorization as composable rules. Each policy returns a PolicyResult that is either allowed or denied with failure values.

import { Policy, PolicyResult, PolicyResultType } from "@jvstack/helpers";

class AllowWhenPositive extends Policy<number, string> {
  execute(value: number) {
    return value > 0
      ? PolicyResult.allowed()
      : PolicyResult.denied(["not positive"]);
  }
}

class AllowWhenEven extends Policy<number, string> {
  execute(value: number) {
    return value % 2 === 0
      ? PolicyResult.allowed()
      : PolicyResult.denied(["not even"]);
  }
}

const policy = Policy.and(new AllowWhenPositive(), new AllowWhenEven());

const result = policy.execute(4);

if (result.type === PolicyResultType.denied) {
  result.throw(); // throws with { failures: [...] }
}

Combine policies with static helpers or instance methods:

Policy.and(new AllowWhenPositive(), new AllowWhenEven());
Policy.or(new AllowWhenPositive(), new AllowWhenEven());
Policy.not(new AllowWhenPositive(), "denied by not");
Policy.noop<number, string>();

new AllowWhenPositive().and(new AllowWhenEven());
new AllowWhenPositive().or(new AllowWhenEven());

Behavior:

  • and — all policies must allow; stops at the first denial
  • or — any policy may allow; collects failures when all deny
  • not — inverts a single policy
  • noop — always allows

IndexedCollection

Stores items in a collection keyed by a function. Duplicate keys overwrite earlier items.

import { IndexedCollection } from "@jvstack/helpers";

type User = { id: number; name: string };

const users = new IndexedCollection(
  [
    { id: 1, name: "Alice" },
    { id: 2, name: "Bob" },
  ],
  (user) => user.id,
);

users.get(1); // { id: 1, name: "Alice" }
users.add({ id: 3, name: "Carol" });
[...users]; // all items

const admins = users.set.leftDifference(otherUsers);
const shared = users.set.intersection(otherUsers);
const everyone = users.set.union(otherUsers);

Use .set to get an IndexedSet with leftDifference, rightDifference, symetricDifference, intersection, and union. Operations compare items by key, not reference.

Counter

Tracks numeric counts for arbitrary keys. Missing keys read as 0.

import { Counter } from "@jvstack/helpers";

const counter = new Counter<string>();

counter.add("click");
counter.add("click");
counter.add("view");

counter.get("click"); // 2
counter.get("view"); // 1
counter.get("missing"); // 0

counter.decrement("click");
counter.get("click"); // 1

License

MIT