{"id":24698588,"url":"https://github.com/faradayio/lock_and_cache_js","last_synced_at":"2026-01-23T09:20:41.767Z","repository":{"id":8817138,"uuid":"59857614","full_name":"faradayio/lock_and_cache_js","owner":"faradayio","description":"Most caching libraries don't do locking, meaning that \u003e1 process can be calculating a cached value at the same time. Since you presumably cache things because they cost CPU, database reads, or money, doesn't it make sense to lock while caching?","archived":false,"fork":false,"pushed_at":"2024-10-08T19:58:12.000Z","size":283,"stargazers_count":2,"open_issues_count":8,"forks_count":0,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-09-20T17:04:47.612Z","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":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/faradayio.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","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":"2016-05-27T19:11:56.000Z","updated_at":"2021-03-15T15:31:58.000Z","dependencies_parsed_at":"2024-08-06T16:59:38.138Z","dependency_job_id":"590830b9-1e08-4f31-b595-3523efea88f9","html_url":"https://github.com/faradayio/lock_and_cache_js","commit_stats":null,"previous_names":[],"tags_count":34,"template":false,"template_full_name":null,"purl":"pkg:github/faradayio/lock_and_cache_js","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/faradayio%2Flock_and_cache_js","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/faradayio%2Flock_and_cache_js/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/faradayio%2Flock_and_cache_js/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/faradayio%2Flock_and_cache_js/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/faradayio","download_url":"https://codeload.github.com/faradayio/lock_and_cache_js/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/faradayio%2Flock_and_cache_js/sbom","scorecard":{"id":392717,"data":{"date":"2025-08-11","repo":{"name":"github.com/faradayio/lock_and_cache_js","commit":"2c02454bca569d1485afd6c544eb8a07fc51a9a9"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.5,"checks":[{"name":"Code-Review","score":0,"reason":"Found 2/23 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci.yml:25: update your workflow using https://app.stepsecurity.io/secureworkflow/faradayio/lock_and_cache_js/ci.yml/master?enable=pin","Warn: npmCommand not pinned by hash: .github/workflows/ci.yml:28","Info:   0 out of   1 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   1 npmCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/ci.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 9 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":0,"reason":"15 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-93q8-gq69-wqmw","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-gxpj-cx7g-858c","Warn: Project is vulnerable to: GHSA-2j2x-2gpw-g8fm","Warn: Project is vulnerable to: GHSA-ww39-953v-wcq6","Warn: Project is vulnerable to: GHSA-29mw-wpgm-hmr9","Warn: Project is vulnerable to: GHSA-35jh-r3h4-6jhm","Warn: Project is vulnerable to: GHSA-f8q6-p94x-37v3","Warn: Project is vulnerable to: GHSA-xvch-5gv4-984h","Warn: Project is vulnerable to: GHSA-35q2-47q7-3pc3","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-j8xg-fqg3-53r7","Warn: Project is vulnerable to: GHSA-c4w7-xm78-47vh"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-18T18:12:52.277Z","repository_id":8817138,"created_at":"2025-08-18T18:12:52.278Z","updated_at":"2025-08-18T18:12:52.278Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279000825,"owners_count":26082950,"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","status":"online","status_checked_at":"2025-10-09T02:00:07.460Z","response_time":59,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":"2025-01-27T04:29:24.783Z","updated_at":"2025-10-09T06:30:54.870Z","avatar_url":"https://github.com/faradayio.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# lock_and_cache\n\nLock and cache using Redis!\n\nMost caching libraries don't do locking, meaning that \u003e1 process can be calculating a cached value at the same time. Since you presumably cache things because they cost CPU, database reads, or money, doesn't it make sense to lock while caching?\n\n## Quickstart\n\n```ts\nimport { LockAndCache } from \"lock_and_cache\";\n\nconst cache = new LockAndCache();\n\nasync function computeStockQuote(symbol: string) {\n  // Fetch stock price from remote source, cache for one minute.\n  //\n  // Calling this multiple times in parallel will only run it once the cache key\n  // is based on the function name and arguments.\n  return 100;\n}\nconst stockQuote = cache.wrap({ ttl: 60 }, computeStockQuote);\n\n// If you forget this, your process will never exit.\ncache.close();\n```\n\n## Install\n\n```sh\nnpm install --save lock_and_cache\n```\n\n## Theory\n\nlock_and_cache...\n\n1. returns cached value (if exists)\n2. acquires a lock\n3. returns cached value (just in case it was calculated while we were waiting for a lock)\n4. calculates and caches the value\n5. releases the lock\n6. returns the value\n\nAs you can see, most caching libraries only take care of (1) and (4) (well, and (5) of course).\n\n## Setup\n\nBy default, you probably want to put locks and caches in separate databases:\n\n```sh\nexport CACHE_URL=redis://redis:6379/2\nexport LOCK_URL=redis://redis:6379/3\n```\n\n## API\n\nWe have full JSDoc for this library, but these are the highlights at the time of writing.\n\n```ts\n/**\n * Valid cache keys.\n *\n * These may only contain values that we can serialize consistently.\n */\nexport type CacheKey = string | number | boolean | null | CacheKey[];\n\n/** Options that can be passed to `get` and `wrap`. */\nexport type GetOptions = {\n  /**\n   * \"Time to live.\" The time to cache a value, in seconds.\n   *\n   *  Defaults to `DEFAULT_TTL`.\n   */\n  ttl?: number;\n};\n\n/**\n * A user-supplied function that computes a value to cache.\n *\n * This function may have an optional `displayName` property, which may be used\n * as part of the cache key if this function is passed to `wrap`.\n *\n * By default, `WorkFn` functions do not accept arguments, but if you're using\n * the `wrap` feature, you may want to supply an optional argument list.\n */\ntype WorkFn\u003cT, Args extends CacheKey[] = []\u003e = ((\n  ...args: Args\n) =\u003e Promise\u003cT\u003e) \u0026 {\n  displayName?: string;\n};\n\nclass LockAndCache {\n  /**\n   * Create a new `LockAndCache`.\n   *\n   * @param caches Caches to use. Defaults to `tieredCache()`.\n   * @param lockClient Redis client to use for locking. Defaults to using\n   * `DEFAULT_REDIS_OPTIONS`.\n   */\n  constructor({\n    lockClient = defaultRedisLockClient(),\n    cacheClient = defaultRedisCacheClient(),\n  } = {});\n\n  /**\n   * Shut down this cache manager. No other functions may be called after this.\n   */\n  close(): void;\n\n  /**\n   * Either fetch a value from our cache, or compute it, cache it and return it.\n   *\n   * @param key The cache key to use.\n   * @param options Cache options. `ttl` is in seconds.\n   * @param work A function which performs an expensive caculation that we want\n   * to cache.\n   */\n  async get\u003cT\u003e(key: CacheKey, options: GetOptions, work: WorkFn\u003cT\u003e): Promise\u003cT\u003e;\n\n  /**\n   * Given a work function, wrap it in a `cache.get`, using the function's\n   * arguments as part of our cache key.\n   *\n   * @param options Cache options. `name` is the base name of our cache key.\n   * `ttl` is in seconds, and it defaults to `DEFAULT_TTL`.\n   * @param work The work function to wrap. If `options.name` is not specified,\n   * either `work.displayName` or `work.name` must be a non-empty string.\n   */\n  wrap\u003cT, Args extends CacheKey[] = []\u003e(\n    options: GetOptions \u0026 { name?: string },\n    work: WorkFn\u003cT, Args\u003e\n  ): WorkFn\u003cT, Args\u003e;\n}\n```\n\n## Contributing\n\nPlease send us your pull requests!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffaradayio%2Flock_and_cache_js","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffaradayio%2Flock_and_cache_js","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffaradayio%2Flock_and_cache_js/lists"}