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

@e280/stz

v0.2.21

Published

everyday ts fns for everything

Readme

🏂 @e280/stz

stz is e280's standard library of environment-agnostic typescript tools. zero dependencies.

🥨 stz primitives

🍏 pub and sub

ergonomic event emitters

import {pub, sub} from "@e280/stz"

pub

  • make a publisher fn
    // create a pub fn
    const sendMessage = pub<[string]>()
    
    // subscribe to it
    sendMessage.subscribe(m => console.log(m))
    
    // publish to it
    sendMessage("hello")

sub

  • make a subscriber fn — it's just like pub, except it's flipsy-reversey!
    // create a sub fn
    const onMessage = sub<[string]>()
    
    // subscribe to it
    onMessage(m => console.log(m))
    
    // publish to it
    onMessage.publish("hello")

pub vs sub

  • pub and sub both have the same facilities
    • .publish
    • .subscribe
    • .on
    • .next
    • .clear
  • i seem to use sub more often

the more you know, about pubsub

  • publish actually returns a promise, to wait for all async subscribers
    await onMessage.publish("hello")
  • subscribe returns a fn to unsubscribe
    const unsubscribe = onMessage(() => {})
    unsubscribe()
  • .clear() to wipe all subscribed listeners
    onMessage.clear()
  • .next(fn?) is a better way to do .once..
    • you can use it like a .once:
      onMessage.next(message => {})
    • but it also gives you a promise like this:
      const [message] = await onMessage.next()
    • of course the promise can be used like this:
      onMessage.next().then(([message]) => {})

🍏 defer

defer the resolve/reject of a promise to the outside

import {defer} from "@e280/stz"

const deferred = defer()
  • resolve the deferred promise
    deferred.resolve()
  • reject the deferred promise
    deferred.reject(new Error("fail"))
  • await the promise
    await deferred.promise

🍏 nap

sleep for some milliseconds

import {nap} from "@e280/stz"

await nap(900)
  // wait for 900 milliseconds

🍏 all

it's just sugar for Promise.all

import {all} from "@e280/stz"

await all(
  nap(500),
  Promise.resolve("hello"),
  fetch("whatever.json"),
)

🍏 concurrent

sugar for Promise.all, but returns named things as an object

import {concurrent} from "@e280/stz"

cons {slept, hello, whatever} = await concurrent({
  slept: nap(500),
  hello: Promise.resolve("hello"),
  whatever: fetch("whatever.json"),
})

🍏 disposer

easy trash management

import {disposer} from "@e280/stz"
  • create a disposer
    const dispose = disposer()
  • schedule something for cleanup
    dispose.schedule(() => console.log("disposed!"))
  • schedule multiple things at once
    dispose.schedule(
      () => console.log("disposed thing 1"),
      () => console.log("disposed thing 2"),
      () => ev(window, {keydown: () => console.log("keydown")}),
    )
  • schedule is chainable if you prefer that vibe
    dispose
      .schedule(() => console.log("disposed thing 1"))
      .schedule(() => console.log("disposed thing 2"))
      .schedule(() => ev(window, {keydown: () => console.log("keydown")}))
  • dispose of all that garbage
    dispose()

🍏 G Crew

extended js data types

GMap

extended js Map

  • many are saying it's "The Deluxe Mapping Experience"
    import {GMap} from "@e280/stz"
    
    const map = new GMap<number, string>([
      [1, "hello"],
      [2, "world"],
    ])
  • map.require(key) — returns the value for key.. if missing, throw an error
    const value = map.require(1)
      // "hello"
  • map.guarantee(key, makeFn) — returns the value for key.. if missing, run makeFn to set and return the value
    const value = map.guarantee(3, () => "rofl")
      // "rofl"

GSet

extended js Set

  • new GSet<T>()
  • set.adds(item1, item2, item3) — add multiple items without a for-loop
  • set.deletes(item1, item2, item3) — add multiple items without a for-loop

GWeakMap

extended js WeakMap

  • new GWeakMap<K, V>()
  • weakMap.require(key) — returns value for key.. if missing, throw an error
  • weakMap.guarantee(key, makeFn) — returns the value for key.. if missing, run makeFn to set and return the value

🥨 stz fn tools

🍏 queue(fn)

execute calls in sequence (not concurrent)

import {queue, nap} from "@e280/stz"

const fn = queue(async() => nap(100))

fn()
fn()
await fn() // waits for the previous calls (sequentially)

🍏 once(fn)

ensure a fn is only executed one time

import {once} from "@e280/stz"

let count = 0
const fn = once(() => count++)
console.log(count) // 0

fn()
console.log(count) // 1

fn()
console.log(count) // 1

🍏 deadline(100, fn)

throws an error if the async function takes too long

import {deadline} from "@e280/stz"

const fn = deadline(100, async() => {

  // example deliberately takes too long
  await nap(200)
})

await fn()
  // DeadlineError: deadline exceeded (0.1 seconds)

🍏 debounce(100, fn)

wait some time before actually executing the fn (absorbing redundant calls)

we use debounce a lot in ui code, like on a user's keyboard input in a form field, but rendering the form input can actually be slow enough that it causes problems when they type fast — to eliminate the jank, we debounce with like 400 ms, so we wait for the user to finish typing for a moment before actually running the validation.

import {debounce} from "@e280/stz"

const fn = debounce(100, async() => {
  await coolAction()
})

// each fn() call resets the timer
fn()
fn()
fn()

// coolAction is only called once here, other calls are redundant

🍏 microbounce(fn)

collapse multiple calls into a single call (uses queueMicrotask under the hood)

it's like debounce(0, fn) but more efficient by using queueMicrotask instead of setTimeout

import {microbounce} from "@e280/stz"

const fn = microbounce(async() => coolAction())
fn()
fn()
fn() // previous calls are redundant

🍏 cycle(fn)

execute a function over and over again, back to back

import {cycle} from "@e280/stz"

let ticks = 0

const stop = cycle(async() => {

  // use a nap to add a delay between each execution
  await nap(200)

  ticks++
})

// stop repeating whenever you want
stop()

🥨 stz data utilities

🍏 txt

convert to/from utf8 string format

  • txt.fromBytes(bytes) — bytes to string
  • txt.toBytes(string) — string to bytes

🍏 bytes

utilities for dealing with Uint8Array

  • bytes.eq(bytesA, bytesB) — check if two byte arrays are equal
  • bytes.random(32) — generate crypto-random bytes

🍏 BaseX utilities

convert binary data to/from various encodings

import {hex, base58, base64} from "@e280/stz"

hex

all BaseX utilities have these methods

  • hex.fromBytes(u8array) — encode bytes to string
  • hex.toBytes(str) — decode string to bytes
  • hex.toInteger(string) — decode string as js integer
  • hex.fromInteger(n) — encode js integer as a string
  • hex.random(32) — generate random encoded string (32 bytes)

all BaseX utilities

  • hex
  • base2
  • base36
  • base58
  • base62
  • base64
  • base64url

make a custom BaseX utility

  • you can provide a lexicon to produce your own BaseX codec
    const myHex = new BaseX({characters: "0123456789abcdef"})

tiny timestamps

  • fun fact: you can make insanely compact timestamp strings like this:
    base62.fromInteger(Date.now() / 1000)
      // "1uK3au"
    • 1748388028 base10 epoch seconds (10 chars)
    • 1uK3au base62 epoch seconds (6 chars)
    • nice

🍏 bytename

friendly string encoding for binary data

a bytename looks like "midsen.picmyn.widrep.baclut dotreg.filtyp.nosnus.siptev". that's 16 bytes. each byte maps to a three-letter triplet

the bytename parser (bytename.toBytes) ignores all non-alphabetic characters. thus midsen.picmyn, midsenpicmyn, and mid@sen$pic@myn are all equal.

import {bytename} from "@e280/stz"
  • bytename.fromBytes(new Uint8Array([0xDE, 0xAD, 0xBE, 0xEF]))
      // "ribmug.hilmun"
  • bytename.toBytes("ribmug.hilmun")
      // Uint8Array, 4 bytes
  • const data = new Uint8Array([
      0xDE, 0xAD, 0xBE, 0xEF,
      0xDE, 0xAD, 0xBE, 0xEF,
    ])
    
    bytename.fromBytes(data, {
      groupSize: 2, // default is 4
      groupSeparator: " ",
      wordSeparator: ".",
    })
      // "ribmug.hilmun ribmug.hilmun"

🍏 thumbprint

hybrid of bytename and base58 to make binary data more human-friendly

  • looks like nodlyn.fasrep.habbud.ralwel.Avo7gFmdWMRHkwsD149mcaBoZdS69iXuJ
  • the idea is that the first parts are in bytename format, so it's easy for humans to recognize
  • and the remaining data is shown in base58
  • thumbprint.fromBytes(u8array) — encode bytes to thumbprint string
  • thumbprint.toBytes(thumbstring) — decode thumbprint string to bytes
  • thumbprint.fromHex(hexstring) — convert a hex string into a thumbprint
  • thumbprint.toHex(thumbstring) — convert a thumbprint into a hex string

🍏 toq

tar-like binary file format for efficiently packing multiple files together

import {toq, txt} from "@e280/stz"

data layout

  • 4 magic bytes "TOQ\x01"
  • for each file (little endian)
    • name length 1 byte (u8)
    • name x bytes (max 255 B)
    • data length 4 bytes (u32)
    • data x bytes (max 4 GB)

toq pack/unpack

  • toq.pack — accepts any iterable of file entries
    const pack: Uint8Array = toq.pack([
      ["hello.txt", txt.toBytes("hello world")],
      ["deadbeef.data", new Uint8Array([0xDE, 0xAD, 0xBE, 0xEF])],
    ])
  • toq.is — check if a file is a toq pack or not
    toq.is(pack) // true
  • toq.unpack — generator fn yields file entries
    for (const [name, data] of toq.unpack(pack))
      console.log(name, data.length)

toq works nice with maps

  • pack a map of files
    const files = new Map<string, Uint8Array>()
    files.set("hello.txt", txt.toBytes("hello world"))
    files.set("deadbeef.data", new Uint8Array([0xDE, 0xAD, 0xBE, 0xEF]))
    
    const pack = toq.pack(files)
  • unpack into a new map
    const files = new Map(toq.unpack(pack))

💖 stz is by e280

reward us with github stars
build with us at https://e280.org/ but only if you're cool