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 🙏

© 2024 – Pkg Stats / Ryan Hefner

gc-hook

v0.4.1

Published

A simplified FinalizationRegistry utility that works

Downloads

41,603

Readme

gc-hook

build status Coverage Status

Social Media Photo by Steve Johnson on Unsplash

A simplified FinalizationRegistry utility that works:

  • it does the right thing by never leaking the reference meant to be notified
  • it allows overriding the returned proxy with any other more complex wrapper or indirection
  • it allows references owners to drop from the registry explicitly, either via the held reference or an explicit token, if passed as extra option
  • it avoids understanding how the FinalizationRegistry works, helping you to focus on more complex issues instead of re-implementing the same dance over and over

Example

// available as commonjs too
import { create, drop } from 'gc-hook';

// keep a count of all passed references created here
let references = 0;

// notify how many references are still around
const onGarbageCollected = myUtility => {
  console.log(--references, 'references still used');
};

export default options => {
  const myUtility = { ...options, do: 'something' };
  console.log(++references, 'references provided');
  // return a proxy to avoid holding directly myUtility
  // while keeping the utility in memory until such proxy
  // is not needed, used, or referenced anymore
  return create(myUtility, onGarbageCollected);
};

// as module consumer
import createUtility from './module.js';

let util = createUtility({some: 'thing'});
// do something  amazing with the util ... then
setTimeout(() => {
  // clear the utility or don't reference it anymore anywhere
  util = null;
  // once the GC kicks in, the module.js will log how many
  // utilities are still around and never collected
});

Use Cases

In case you'd like to be notified when an object not meant to leak has been collected, you can use the create function in its most simple way:

import { create } from 'gc-hook';

const privateObject = {};
const onGC = privateObject => {
  console.log(privateObject, 'not used anymore');
};

export create(privateObject, onGC);

If you are handling FFI related references, you can hold on internal values and yet return whatever artifact you like in the wild.

import { create } from 'gc-hook';

export const createWrap = reference => {

  const onGC = reference => {
    ffi.gc.decreaseRefCounting(reference);
  };

  const wrap = function (...args) {
    return ffi.apply(reference, args);
  };

  wrap.destroy = onGC;

  // will return the wrap as it is without holding
  // the reference in the wild
  return create(reference, onGC, { return: wrap });
};

This use case was designed after pyodide Proxy and GC dance around passed references to the JS world.

In case you need to relate a specific object to a unique id (coincident use case) and you don't need to ever unregister the held reference / id internally:

import { create } from 'gc-hook';

const onGC = id => {
  console.log(id.valueOf(), 'not needed anymore');
};

// id can be any primitive in here and ref must be used as return
export const relate = (id, ref) => {
  return create(
    typeof id === 'string' ? new String(id) : new Number(id),
    onGC,
    { token: false, return: ref }
  );
};

In case you need to relate a specific object to a unique id but you still would like to drop the reference from the FinalizationRegistry later on:

import { create, drop } from 'gc-hook';

const onGC = ({ id, time }) => {
  console.log(id, 'created at', time, 'not needed anymore');
};

// id can be any primitive in here
export const relate = (id, wrap) => {
  const token = { id, time: Date.now() };
  const hold = typeof id === 'string' ? new String(id) : new Number(id);
  return {
    value: create(hold, onGC, { token, return: wrap }),
    drop: () => drop(token)
  };
};

One does not need to pass to the GC callback just a specific kind of value so that it's possible to combine various operations at once:

import { create, drop } from 'gc-hook';

export const createComplexHeld = ref => {
  const onGC = ({ ref, destroy, time }) => {
    destroy();
    console.log(ref, 'created at', time, 'not needed');
  };

  const wrap = function (...args) {
    return ffi.apply(ref, args);
  };

  wrap.destroy = () => {
    drop(held);
    ffi.gc.decreaseRefCounting(ref);
  };

  const held = {
    ref,
    destroy: wrap.destroy,
    time: Date.now(),
  };

  return create(held, onGC, { return: wrap });
}:

The only and most important thing is to never return something part of the held logic otherwise that returned value cannot possibly ever be Garbage Collected.

gc-hook/track

If you'd like to track one or more reference you can use gc-hook/track helper.

All it does is to notify in console, via console.debug, that such reference has eventually be collected.

// or use https://esm.run/gc-hook/track live
import BUG_GC from 'gc-hook/track';

// HINT: use a constant so that rollup or bundlers
// can eventually remove all the dead code in production
const D = true;

// create any reference
let test = { any: 'value' };

// when debugging, pass an object literal to simplify
// naming -> references convention
D&&BUG_GC({ test });

setTimeout(() => { test = null; });
// now press the Collect Garbage button in devtools
// and see the lovely message: **test** collected

API

// returns a ProxyHandler<hold> or whatever
// the `return` option wants to return.
// The returned reference is the one that
// notifies the GC handler once destroyed
// or not referenced anymore in the consumer code.
create(
  // the reference or primitive to keep in memory
  // until the returned value is used. It can be
  // a primitive, but it requires `token = false`,
  // or any reference to hold in memory.
  hold,
  // a callback that will receive the held value
  // whenever its Proxy or wrapper is not referenced
  // anymore in the program using it.
  onGarbageCollected,
  // optional properties:
  {
    // if passed along, it will be used automatically
    // to create the ProxyHandler<hold>.
    handler = Object.create(null),
    // override the otherwise automatically created Proxy
    // for the `held` reference.
    return = new Proxy(hold, handler),
    // allow dropping from the registry via something
    // different from the returned value itself.
    // If this is explicitly `false`, no token is used
    // to register the retained value.
    token = hold,
    // if explicitly set as `true` it will `console.debug`
    // the fact the held value is not retained anymore out there.
    debug = false,
  } = {}
);

// Returns `true` if the `token` successfully
// unregistered the proxy reference from the registry.
drop(
  // it's either the held value waiting to be passed
  // to the GC callback, or the explicit `token` passed
  // while creating the reference around it.
  token
);