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

tuple-keyed-map

v0.1.0

Published

A Map keyed by tuples whose entries are garbage-collected when their object components die — WeakMap edges for objects, Map edges for primitives, and a single-walk getOrInsert.

Readme

tuple-keyed-map

A Map keyed by tuples whose entries are garbage-collected when their object components die. Object components travel over WeakMap edges, primitive components over Map edges; an entry keyed by [obj, "config"] lives exactly as long as obj. No eviction policy, no registry, no leak. (The GC story is about object components: a tuple of only primitives has nothing to die, so it is retained until delete() or clear() — exactly like a plain Map entry.)

import { TupleMap } from 'tuple-keyed-map'

const cache = new TupleMap<[object, string], Result>()

cache.set([user, 'profile'], result)
cache.get([user, 'profile']) // result — fresh array, same components
cache.getOrInsertComputed([user, 'avatar'], render) // one walk, TC39 upsert shape

// drop `user` and the entry, its value, and its subtree become garbage

Zero runtime dependencies. ESM and TypeScript declarations included.

Why this exists

The established tuple-key package, many-keys-map, is a trie of ordinary Maps: it holds strong references to every object component forever. Used as a cache keyed by request objects, DOM nodes, or model instances, that is a leak by construction. This package is the complement: weak edges, GC-transparent entries — and, it turns out, considerably faster.

The price is stated up front: WeakMaps cannot be enumerated, so neither can this structure — no size, no iteration. That is a theorem, not an implementation gap (see DESIGN.md): a structure that can list its entries is a structure that retains them. Pick per use:

| need | use | |---|---| | iteration, size, strong retention is fine | many-keys-map | | entries that die with their key objects | tuple-keyed-map |

Receipts

Measured on Node 24.13.1, Apple Silicon, 2026-07-11.

Retention (npm run probe — drop 20,000 key objects while their entries are in the map, then GC):

| | key objects collected | heap retained | |---|---:|---:| | tuple-keyed-map | 20,000 / 20,000 | 0.5 MiB | | many-keys-map | 0 / 20,000 | 19.0 MiB |

Speed (npm run bench — cyclebench interleaves the candidates and cross-checks that both maps returned identical lookup sums; 1000 operations per call):

| workload | tuple-keyed-map | many-keys-map | ratio | |---|---:|---:|---:| | get hit, [string, int, int] | 48.6µs | 201µs | 4.1× | | get hit, [object, string] | 25.0µs | 243µs | 9.7× | | get miss | 9.6µs | 175µs | 18.2× | | set 1000 fresh tuples | 87.2µs | 233µs | 2.7× |

Ratios are the signal; absolutes vary with machine load. Reproduce both tables from this repo.

API

new TupleMap<K extends readonly unknown[], V>()

map.get(key)                          // V | undefined
map.has(key)                          // boolean — distinguishes stored undefined
map.set(key, value)                   // this, chainable
map.delete(key)                       // boolean — prunes empty trie nodes
map.clear()                           // void
map.getOrInsert(key, defaultValue)    // V — one walk; TC39 upsert shape
map.getOrInsertComputed(key, compute) // V — compute runs only on a miss

Semantics worth knowing:

  • Component equality is exactly Map/WeakMap key equality: SameValueZero for primitives (NaN matches NaN, -0 matches 0), identity for objects and functions.
  • Arity discriminates: [1], [1, 2], and [1, undefined] are three different keys. The empty tuple [] is a valid key.
  • Symbols are kept on strong edges (registered symbols cannot be weak keys; treating all symbols alike keeps the contract predictable).
  • delete prunes empty interior nodes bottom-up, so churning through distinct primitive tuples does not accrete shells. One bounded exception: a node that ever grew a weak edge cannot be proven empty (WeakMaps hide their population), so it is kept until the objects above it die or clear() runs.

Verification

Nineteen tests: the full Map contract (SameValueZero components, arity discrimination, stored-undefined vs absent), a 20,000-step randomized model-equivalence run against a serialized-key reference Map, white-box pruning checks, and — the reason the package exists — live GC assertions under --expose-gc: key objects and values are finalized while their entry is still in the map, and a strong-Map control in the same harness is not.

Install

npm install tuple-keyed-map

MIT © Xyra Sinclair