{"id":19974555,"url":"https://github.com/twooster/weaklings","last_synced_at":"2026-05-15T05:35:15.153Z","repository":{"id":142825128,"uuid":"360521126","full_name":"twooster/weaklings","owner":"twooster","description":"Strength through weakness","archived":false,"fork":false,"pushed_at":"2021-04-22T15:59:52.000Z","size":6,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-02-13T00:34:47.689Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","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/twooster.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":"2021-04-22T13:03:03.000Z","updated_at":"2021-04-23T09:46:55.000Z","dependencies_parsed_at":"2023-06-14T19:46:04.446Z","dependency_job_id":null,"html_url":"https://github.com/twooster/weaklings","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twooster%2Fweaklings","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twooster%2Fweaklings/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twooster%2Fweaklings/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twooster%2Fweaklings/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/twooster","download_url":"https://codeload.github.com/twooster/weaklings/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241411434,"owners_count":19958746,"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":[],"created_at":"2024-11-13T03:15:23.974Z","updated_at":"2026-05-15T05:35:15.119Z","avatar_url":"https://github.com/twooster.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# weaklings\n\nA library to make your code stronger by making it a little weaker.\n\n## Motivation\n\nWe recently discovered a memory leak due to the use of a of short-lived\n(per-HTTP request) cache that had periodic item expiry. The item expiry\nwas controlled by a setTimeout. We forgot to close out the cache, assuming\ngarbage collection would do all of the work we needed. Of course, it didn't,\nand `setTimeout` maintained a reference to the object so it was never GC'd.\n\nThis library is a small attempt to create some weak primitives that can be\nused to avoid this problem in the future.\n\nLike all libraries around GC, it's probably better if you don't use these\nat all, but there could be use-cases where such tooling comes in handy.\n\n## API\n\nNote that you although you can import straight from the `weaklings` package,\nthe `weaklings/timeout` and `weaklings/interval` have some memory overhead\nin bookkeeping. You should import from those directly to only incur the\ncosts that you need.\n\n### weakCallback\n\nBehavior:\n\nAccepting an object and a function (typically a callback) such that the\nfunction will only be called if the object still exists.\n\nImport:\n\n```javascript\nimport { weakCallback } from 'weaklings/callback'\n```\n\nDeclaration:\n\n```typescript\nexport function weakCallback\u003cT, U, V\u003e(obj: T, fn: (obj: T, ...args: U) =\u003e V): (...args: U) =\u003e V\n```\n\nExample, when run with `node --expose-gc`:\n\n```javascript\nlet someObj = { val: 0 }\n\nconst cb = weakCallback(someObj, (someObj, inc) =\u003e {\n  someObj.val += inc\n  console.log('val incremented', someObj.val)\n})\n\n// Note that the first argument to the wrapped function will always be the\n// object, and the remaining arguments will be passed afterwards so that you\n// don't hold a closure to it in your function.\ncb(10)\n// val incremented 10\ncb(2)\n// val incremented 12\n\nsomeObj = null\ngc() // force gc collection\n\ncb(3)\n// wrapped callback never called\n```\n\nNote: If you're using this from within a class, be sure that you do not\nform an arrow-closure over the class instance. For example:\n\n```javascript\nclass SomeTransientThing {\n  doThing() { /* ... */ }\n\n  makeTransientCallback() {\n    // First parameter to the wrapped function cannot be called `this` -- it's\n    // a syntax error. Thus `self`.\n    return weakCallback(this, self =\u003e {\n      self.doThing()\n    })\n  }\n}\n```\n\nIf the above used:\n\n```javascript\nmakeTransientCallback() {\n  return weakCallback(this, () =\u003e {\n    this.doThing()\n  })\n}\n```\n\nThen the lambda function itself would contain a closure over `this`, thus\nholding a reference to the object.\n\n### setWeakTimeout, clearWeakTimeout\n\nBehavior:\n\nCreates a timeout that will only trigger if the referenced object hasn't been\nGC'd.\n\nImport:\n\n```javascript\nimport { setWeakTimeout, clearWeakTimeout } from 'weaklings/timeout'\n```\n\nDeclaration:\n\n```typescript\nexport function setWeakTimeout\u003cT extends object, U\u003e(obj: T, fn: (obj: T, ...args: U) =\u003e unknown, timeout: number, ...args: U): WeakTimeout\nexport function clearWeakTimeout(timeout: WeakTimeout)\n```\n\nUse:\n\n```javascript\nclass EasilyGarbageCollectedCache {\n  constructor(lifetime, fetch) {\n    this.lifetime = lifetime\n    this.fetch = fetch\n    this.cache = new Map()\n  }\n\n  get(key) {\n    let cached = this.cache.get(key)\n    if (cached) {\n      clearWeakTimeout(cached.timeout)\n    }\n    if (!cached) {\n      cached = {\n        value: this.fetch(key),\n        timeout: undefined\n      }\n    }\n    cached.timeout = setWeakTimeout(this, self =\u003e {\n      self.clear(key)\n    }, this.lifetime)\n    return cached.value\n  }\n\n  clear(key) {\n    const cached = this.cache.get(key)\n    if (cached) {\n      clearWeakTimeout(cached.timeout)\n      this.cache.delete(key)\n    }\n  }\n}\n```\n\nThe above will create a cache that auto-purges entries at a given interval to\nreduce memory usage, and itself should be garbage-collectable without needing\nto remember to clear all of the associated timeouts.\n\n### setWeakInterval, clearWeakInterval\n\nBehavior:\n\nSimilar to setWeakTimeout and clearWeakTimeout, but operates with intervals.\n\nImport:\n\n```javascript\nimport { setWeakInterval, clearWeakInterval } from 'weaklings/interval'\n```\n\nDeclaration:\n\n```typescript\nexport function setWeakInterval\u003cT extends object, U\u003e(obj: T, fn: (obj: T, ...args: U) =\u003e unknown, timeout: number, ...args: U): WeakTimeout\nexport function clearWeakInterval(timeout: WeakTimeout)\n```\n\nNo example, use the the same as you would with `setInterval`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftwooster%2Fweaklings","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftwooster%2Fweaklings","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftwooster%2Fweaklings/lists"}