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

@actualwave/weak-storage

v0.1.1

Published

WeakStorage and WeakRefMap are based on WeakRef objects allowing to store values as weak references.

Readme

@actualwave/weak-storage

A Map-like collection that holds its values as weak references, letting the garbage collector reclaim them when nothing else holds a reference. Built on the WeakRef and FinalizationRegistry APIs.

Two classes are provided:

| Class | Description | |---|---| | WeakValueMap<K, V> | Map with weak values. Any key type; iteration skips collected entries. | | WeakStorage<K, V> | Extends WeakValueMap with a reverse index — look up the key given a value. |

Requirements

| Environment | Minimum version | |---|---| | Node.js | 14.6+ | | Chrome / Edge | 84+ | | Firefox | 79+ | | Safari | 14.1+ |

Both WeakRef and FinalizationRegistry must be available. Check MDN compatibility for the latest data.

Installation

npm install @actualwave/weak-storage

Quick Start

import { WeakStorage } from '@actualwave/weak-storage';

const store = new WeakStorage();

const user = { id: 1, name: 'Alice' };
store.set('user:1', user);

store.get('user:1');    // { id: 1, name: 'Alice' }
store.getKey(user);     // 'user:1'
store.has('user:1');    // true

// After the variable goes out of scope and GC runs,
// get() returns undefined and iteration skips the entry.

WeakValueMap<K, V>

A drop-in replacement for Map where values are weakly referenced. Keys can be anything — strings, numbers, symbols, or objects.

Constructor

new WeakValueMap(autoCleanup?: boolean)

| Option | Default | Description | |---|---|---| | autoCleanup | true | Registers a FinalizationRegistry that removes stale map entries when a value is collected. Disable for environments without FinalizationRegistry support or for manual cleanup via verify(). |

import { WeakValueMap } from '@actualwave/weak-storage';

// With auto-cleanup (default) — stale keys are removed automatically
const map = new WeakValueMap();

// Without auto-cleanup — use verify() to prune manually
const manual = new WeakValueMap(false);

set(key, value): this

Stores a value under key as a weak reference. Returns this for chaining.

map.set('a', { x: 1 })
   .set('b', { x: 2 })
   .set('c', { x: 3 });

get(key): V | undefined

Returns the value, or undefined if the key is missing or the value was collected.

const value = map.get('a'); // { x: 1 } or undefined

has(key): boolean

Returns true only if the key exists and its value is still alive.

map.has('a'); // true
map.has('missing'); // false

delete(key): boolean

Removes the entry. Returns true if the key existed.

map.delete('a'); // true
map.delete('a'); // false — already gone

clear(): void

Removes all entries.

map.clear();
map.approximateSize; // 0

forEach(callback): void

Iterates over live entries only — collected values are silently skipped.

map.forEach((value, key, map) => {
  console.log(key, value);
});

keys(), values(), entries()

Return IterableIterator instances that skip any entries whose values have been collected.

for (const key of map.keys()) { /* ... */ }
for (const value of map.values()) { /* ... */ }
for (const [key, value] of map.entries()) { /* ... */ }

// Spread also works
const liveKeys = [...map.keys()];

approximateSize: number

Returns the number of internal map entries. This may include stale entries that have been collected but whose FinalizationRegistry callback has not yet fired.

map.set('x', { n: 1 });
// After GC but before the finalizer runs:
map.approximateSize; // still 1
map.has('x');        // false — correctly reports the value is gone

If you need an accurate count, call verify() first:

map.verify();
map.approximateSize; // reflects only live entries

verify(): void

Rebuilds the internal map, discarding any entries whose values have been collected. Useful when:

  • autoCleanup is disabled and you want to reclaim memory explicitly.
  • You need approximateSize to reflect the true live count.
const map = new WeakValueMap(false); // no auto-cleanup

// ... time passes, some values get collected ...

map.verify();                 // prune dead entries
console.log(map.approximateSize); // accurate live count

WeakStorage<K, V>

Extends WeakValueMap with a reverse index backed by a WeakMap, giving O(1) key lookup from a value. Values must be objects (required by WeakMap).

Inherits all WeakValueMap methods. Additionally:

getKey(value): K | undefined

Returns the key associated with a value, or undefined if the value was never stored or the entry was deleted.

import { WeakStorage } from '@actualwave/weak-storage';

const store = new WeakStorage();

const request = { url: '/api/users' };
store.set('req-1', request);

store.getKey(request); // 'req-1'

delete() and clear() both clean up the reverse index:

store.delete('req-1');
store.getKey(request); // undefined

store.clear();
store.getKey(request); // undefined

TypeScript

Both classes are fully generic. The value type V is constrained to object because WeakRef cannot hold primitives.

import { WeakValueMap, WeakStorage } from '@actualwave/weak-storage';

interface Session {
  userId: string;
  token: string;
}

// Explicit generics
const sessions = new WeakValueMap<string, Session>();
sessions.set('sess-abc', { userId: '1', token: 'xyz' });

const session = sessions.get('sess-abc'); // Session | undefined

// WeakStorage with bidirectional lookup
const store = new WeakStorage<string, Session>();
store.set('sess-abc', { userId: '1', token: 'xyz' });

store.getKey({ userId: '1', token: 'xyz' }); // string | undefined

The exported interfaces let you substitute custom WeakRef / FinalizationRegistry implementations — useful for testing or non-standard environments:

import type {
  IWeakRef,
  IWeakRefConstructor,
  IFinalizationRegistry,
  IFinalizationRegistryConstructor,
} from '@actualwave/weak-storage';

How weak references work

A WeakRef wraps an object without keeping it alive. When the JavaScript engine determines that no strong references remain, it may collect the object. Calling ref.deref() afterwards returns undefined.

set('k', obj)
  └─ map.set('k', new WeakRef(obj))
  └─ finalizer.register(obj, 'k')

get('k')
  └─ map.get('k').deref()   → obj if alive, undefined if collected

[GC runs, obj is collected]
  └─ FinalizationRegistry fires → map.delete('k')  (if autoCleanup=true)

Key reuse guard

If a key is reused before the finalizer fires for the old value, the cleanup callback checks that the current ref is also dead before deleting. A live replacement value is never evicted:

map.set('k', oldValue);
// oldValue gets collected, but before the finalizer fires:
map.set('k', freshValue);
// finalizer fires for 'k' — but freshValue is alive, so the entry is kept
map.get('k'); // freshValue ✓

Caveats

  • GC timing is non-deterministic. A collected value may not disappear from get() immediately after all strong references are dropped — the engine decides when to collect. Never rely on finalizers for correctness-critical logic.
  • approximateSize can lag. It reflects the raw internal map size, which can include stale entries between GC and finalizer execution. Use verify() + approximateSize when an exact count matters.
  • WeakStorage.getKey() does not survive GC. The reverse index is a WeakMap, so when the value is collected, its reverse entry is also automatically removed.
  • Primitives as values are not supported. WeakRef can only wrap objects. Passing a primitive to set() will throw at runtime.