{"id":20513701,"url":"https://github.com/webreflection/gc-hook","last_synced_at":"2026-02-26T16:05:53.425Z","repository":{"id":201173859,"uuid":"707129220","full_name":"WebReflection/gc-hook","owner":"WebReflection","description":"A simplified FinalizationRegistry utility that works.","archived":false,"fork":false,"pushed_at":"2024-08-01T12:10:36.000Z","size":186,"stargazers_count":18,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-12-31T15:49:52.161Z","etag":null,"topics":["finalizationregistry","gc","utility"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/WebReflection.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-10-19T09:27:56.000Z","updated_at":"2024-08-05T09:11:11.000Z","dependencies_parsed_at":"2024-11-13T04:00:32.133Z","dependency_job_id":"bbb2d300-2d0f-466e-8965-7a5905f700ed","html_url":"https://github.com/WebReflection/gc-hook","commit_stats":{"total_commits":33,"total_committers":1,"mean_commits":33.0,"dds":0.0,"last_synced_commit":"a34201ea36db34a968430a3d72eeeb7bada98151"},"previous_names":["webreflection/gc-hook"],"tags_count":13,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WebReflection%2Fgc-hook","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WebReflection%2Fgc-hook/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WebReflection%2Fgc-hook/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WebReflection%2Fgc-hook/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/WebReflection","download_url":"https://codeload.github.com/WebReflection/gc-hook/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":232798397,"owners_count":18578091,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["finalizationregistry","gc","utility"],"created_at":"2024-11-15T21:12:42.778Z","updated_at":"2026-02-26T16:05:53.379Z","avatar_url":"https://github.com/WebReflection.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# gc-hook\n\n[![build status](https://github.com/WebReflection/gc-hook/actions/workflows/node.js.yml/badge.svg)](https://github.com/WebReflection/gc-hook/actions) [![Coverage Status](https://coveralls.io/repos/github/WebReflection/gc-hook/badge.svg?branch=main)](https://coveralls.io/github/WebReflection/gc-hook?branch=main)\n\n\u003csup\u003e**Social Media Photo by [Steve Johnson](https://unsplash.com/@steve_j) on [Unsplash](https://unsplash.com/)**\u003c/sup\u003e\n\nA simplified [FinalizationRegistry](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/FinalizationRegistry) utility that works:\n\n  * it does the right thing by never leaking the reference meant to be notified\n  * it allows overriding the returned proxy with any other more complex wrapper or indirection\n  * it allows references owners to *drop* from the registry explicitly, either via the *held* reference or an explicit token, if passed as extra option\n  * 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\n\n### Example\n\n```js\n// available as commonjs too\nimport { create, drop } from 'gc-hook';\n\n// keep a count of all passed references created here\nlet references = 0;\n\n// notify how many references are still around\nconst onGarbageCollected = myUtility =\u003e {\n  console.log(--references, 'references still used');\n};\n\nexport default options =\u003e {\n  const myUtility = { ...options, do: 'something' };\n  console.log(++references, 'references provided');\n  // return a proxy to avoid holding directly myUtility\n  // while keeping the utility in memory until such proxy\n  // is not needed, used, or referenced anymore\n  return create(myUtility, onGarbageCollected);\n};\n\n// as module consumer\nimport createUtility from './module.js';\n\nlet util = createUtility({some: 'thing'});\n// do something  amazing with the util ... then\nsetTimeout(() =\u003e {\n  // clear the utility or don't reference it anymore anywhere\n  util = null;\n  // once the GC kicks in, the module.js will log how many\n  // utilities are still around and never collected\n});\n```\n\n## Use Cases\n\n\u003cdetails\u003e\n  \u003csummary\u003e\u003cstrong\u003eInternal Objects\u003c/strong\u003e\u003c/summary\u003e\n  \u003cdiv markdown=1\u003e\n\nIn case you'd like to be notified when an object not meant to leak has been collected,\nyou can use the `create` function in its most simple way:\n\n```js\nimport { create } from 'gc-hook';\n\nconst privateObject = {};\nconst onGC = privateObject =\u003e {\n  console.log(privateObject, 'not used anymore');\n};\n\nexport create(privateObject, onGC);\n```\n\n  \u003c/div\u003e\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003e\u003cstrong\u003eFFI Objects\u003c/strong\u003e\u003c/summary\u003e\n  \u003cdiv markdown=1\u003e\n\nIf you are handling *FFI* related references, you can hold on internal values and yet return whatever artifact you like in the wild.\n\n```js\nimport { create } from 'gc-hook';\n\nexport const createWrap = reference =\u003e {\n\n  const onGC = reference =\u003e {\n    ffi.gc.decreaseRefCounting(reference);\n  };\n\n  const wrap = function (...args) {\n    return ffi.apply(reference, args);\n  };\n\n  wrap.destroy = onGC;\n\n  // will return the wrap as it is without holding\n  // the reference in the wild\n  return create(reference, onGC, { return: wrap });\n};\n```\n\nThis use case was designed after *pyodide* Proxy and GC dance around passed references to the *JS* world.\n\n  \u003c/div\u003e\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003e\u003cstrong\u003ePrimitives\u003c/strong\u003e\u003c/summary\u003e\n  \u003cdiv markdown=1\u003e\n\nIn case you need to relate a specific object to a unique id (*[coincident](https://github.com/WebReflection/coincident)* use case) and you don't need to ever unregister the held reference / id internally:\n\n```js\nimport { create } from 'gc-hook';\n\nconst onGC = id =\u003e {\n  console.log(id.valueOf(), 'not needed anymore');\n};\n\n// id can be any primitive in here and ref must be used as return\nexport const relate = (id, ref) =\u003e {\n  return create(\n    typeof id === 'string' ? new String(id) : new Number(id),\n    onGC,\n    { token: false, return: ref }\n  );\n};\n```\n\n  \u003c/div\u003e\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003e\u003cstrong\u003ePrimitives + Drop\u003c/strong\u003e\u003c/summary\u003e\n  \u003cdiv markdown=1\u003e\n\nIn 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:\n\n```js\nimport { create, drop } from 'gc-hook';\n\nconst onGC = ({ id, time }) =\u003e {\n  console.log(id, 'created at', time, 'not needed anymore');\n};\n\n// id can be any primitive in here\nexport const relate = (id, wrap) =\u003e {\n  const token = { id, time: Date.now() };\n  const hold = typeof id === 'string' ? new String(id) : new Number(id);\n  return {\n    value: create(hold, onGC, { token, return: wrap }),\n    drop: () =\u003e drop(token)\n  };\n};\n```\n\n  \u003c/div\u003e\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003e\u003cstrong\u003eComplex held values\u003c/strong\u003e\u003c/summary\u003e\n  \u003cdiv markdown=1\u003e\n\nOne 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:\n\n```js\nimport { create, drop } from 'gc-hook';\n\nexport const createComplexHeld = ref =\u003e {\n  const onGC = ({ ref, destroy, time }) =\u003e {\n    destroy();\n    console.log(ref, 'created at', time, 'not needed');\n  };\n\n  const wrap = function (...args) {\n    return ffi.apply(ref, args);\n  };\n\n  wrap.destroy = () =\u003e {\n    drop(held);\n    ffi.gc.decreaseRefCounting(ref);\n  };\n\n  const held = {\n    ref,\n    destroy: wrap.destroy,\n    time: Date.now(),\n  };\n\n  return create(held, onGC, { return: wrap });\n}:\n```\n\nThe 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.\n\n  \u003c/div\u003e\n\u003c/details\u003e\n\n### gc-hook/track\n\nIf you'd like to track one or more reference you can use `gc-hook/track` helper.\n\nAll it does is to notify in console, via `console.debug`, that such reference has eventually be collected.\n\n```js\n// or use https://esm.run/gc-hook/track live\nimport BUG_GC from 'gc-hook/track';\n\n// HINT: use a constant so that rollup or bundlers\n// can eventually remove all the dead code in production\nconst D = true;\n\n// create any reference\nlet test = { any: 'value' };\n\n// when debugging, pass an object literal to simplify\n// naming -\u003e references convention\nD\u0026\u0026BUG_GC({ test });\n\nsetTimeout(() =\u003e { test = null; });\n// now press the Collect Garbage button in devtools\n// and see the lovely message: **test** collected\n```\n\n## API\n\n```js\n// returns a ProxyHandler\u003chold\u003e or whatever\n// the `return` option wants to return.\n// The returned reference is the one that\n// notifies the GC handler once destroyed\n// or not referenced anymore in the consumer code.\ncreate(\n  // the reference or primitive to keep in memory\n  // until the returned value is used. It can be\n  // a primitive, but it requires `token = false`,\n  // or any reference to hold in memory.\n  hold,\n  // a callback that will receive the held value\n  // whenever its Proxy or wrapper is not referenced\n  // anymore in the program using it.\n  onGarbageCollected,\n  // optional properties:\n  {\n    // if passed along, it will be used automatically\n    // to create the ProxyHandler\u003chold\u003e.\n    handler = Object.create(null),\n    // override the otherwise automatically created Proxy\n    // for the `held` reference.\n    return = new Proxy(hold, handler),\n    // allow dropping from the registry via something\n    // different from the returned value itself.\n    // If this is explicitly `false`, no token is used\n    // to register the retained value.\n    token = hold,\n    // if explicitly set as `true` it will `console.debug`\n    // the fact the held value is not retained anymore out there.\n    debug = false,\n  } = {}\n);\n\n// Returns `true` if the `token` successfully\n// unregistered the proxy reference from the registry.\ndrop(\n  // it's either the held value waiting to be passed\n  // to the GC callback, or the explicit `token` passed\n  // while creating the reference around it.\n  token\n);\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwebreflection%2Fgc-hook","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwebreflection%2Fgc-hook","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwebreflection%2Fgc-hook/lists"}