{"id":27996338,"url":"https://github.com/groupon/shared-store","last_synced_at":"2025-07-08T01:05:09.964Z","repository":{"id":28239835,"uuid":"31744923","full_name":"groupon/shared-store","owner":"groupon","description":"Keeping config data in sync","archived":true,"fork":false,"pushed_at":"2023-03-04T10:51:10.000Z","size":421,"stargazers_count":9,"open_issues_count":5,"forks_count":7,"subscribers_count":9,"default_branch":"main","last_synced_at":"2025-06-09T13:54:39.453Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/groupon.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","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":"2015-03-06T00:46:47.000Z","updated_at":"2025-04-28T08:30:49.000Z","dependencies_parsed_at":"2024-06-20T22:08:56.410Z","dependency_job_id":null,"html_url":"https://github.com/groupon/shared-store","commit_stats":null,"previous_names":[],"tags_count":38,"template":false,"template_full_name":null,"purl":"pkg:github/groupon/shared-store","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/groupon%2Fshared-store","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/groupon%2Fshared-store/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/groupon%2Fshared-store/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/groupon%2Fshared-store/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/groupon","download_url":"https://codeload.github.com/groupon/shared-store/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/groupon%2Fshared-store/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":264171574,"owners_count":23567708,"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":"2025-05-08T21:38:13.925Z","updated_at":"2025-07-08T01:05:09.910Z","avatar_url":"https://github.com/groupon.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![nlm-github](https://img.shields.io/badge/github-groupon%2Fshared--store%2Fissues-F4D03F?logo=github\u0026logoColor=white)](https://github.com/groupon/shared-store/issues)\n![nlm-node](https://img.shields.io/badge/node-%3E%3D10.13-blue?logo=node.js\u0026logoColor=white)\n![nlm-version](https://img.shields.io/badge/version-4.1.2-blue?logo=version\u0026logoColor=white)\n# shared-store\n\nThis module allows you load data from a `.json`, `.cson` or `.yml` file, URL or custom loader\nfunction and share that data easily amongst multiple node.js worker processes.\nWhen the data changes, the master process will load the latest changes, which\nare immediately made available for all workers to consume.\n\nAt Groupon, this allows us to dynamically update app config in a central\nsystem, and see those changes propagate automatically to our node.js processes\nwithout having to restart our apps.\n\nFor development, engineers can change their local file-based config, and see\nthose changes reflected immediately.\n\nFiles are observed via `fs.watch` and URLs via a polling mechanism.  HTTP cache\nheaders are supported for optimal fetching behavior.\n\n## Install\n\n```bash\nnpm install --save shared-store\n```\n\n## Usage\n\nThe code below can be used in both master \u0026 child processes.  The master process\n(i.e. `cluster.isMaster`) will run the loader; whereas the child processes will\nfetch changes from the `temp` directory.\n\n```js\nconst SharedStore = require('shared-store');\nconst fileContent = require('shared-store/file');\n\nconst store = new SharedStore({\n  temp: 'tmp/config',\n\n  loader(options) {\n    return fileContent(options.filename, {\n      watch: true,\n    });\n  }\n});\n\n// Load the initial configuration\nstore.init({ filename: 'conf/application.json' })\n  .then(config =\u003e {\n\n    // The `config` variable was passed into the callback for convenience\n    // purposes only.  If you want the latest data at any point, you can call:\n    const latestConfig = store.getCurrent();\n\n    const server = require('http').createServer((req, res) =\u003e {\n      res.end('ok');\n    }).listen(latestConfig.port);\n  }).catch(err =\u003e { throw err });\n```\n\nWhen loading fails, the store will fall back to the `temp` directory.\n\n## API\n\n### `new SharedStore({ temp, loader, active })`\n\nCreates a new store, based on the given `loader`.\n\n* `temp`:\n  Directory to store cached versions of the data.\n  This is used for handling load errors\n  and for sharing data across processes.\n  The directory should not contain any other files.\n* `loader`:\n  A function that takes `options` and returns\n  an observable with the data.\n* `active`:\n  If this instance should actively load the data\n  or just get the latest from the `temp` directory.\n  Defaults to `cluster.isMaster`.\n\n\n#### `store.init([ options, [ callback ] ])`\n\nSets the options that will be passed into the loader.\nThe promise will be resolved with the initial data.\n\nNote that cached data could be returned in the callback/promise if it already\nexists.\n\n`SharedStore` does not interpret `options` in any way,\nit is just forwarded to the loader.\n\n\n#### `store.getCurrent()`\n\nGet the last known version of the data.\nThis function is safe to call once `init` finished.\nIt will just return `undefined` otherwise.\n\n#### `store.setActive(active=true)`\n\nEnables/disables active mode (see constructor).\n\n### `SharedStore.safeMerge`\n\nRandom utility function to deep-merge multiple objects\nwithout mutating any of the input arguments.\nVery useful in loader implementations\nwhen combined with `Observable.combineLatest`.\n\n\n### `fileContent(filename, options)`\n\n```js\nconst fileContent = require('shared-store/file');\n```\n\nAn observable representing the content of a file.\nBy default, parses the content based on the extension.\n\nSupport for the following extensions is built in:\n\n* `.json`:\n  Parsed using `JSON.parse`\n* `.cson`:\n  Parsed using [`CSON.parse`](https://github.com/groupon/cson-parser)\n* `.yml`/`.yaml`:\n  Parsed using [`yaml.load`](https://github.com/nodeca/js-yaml)\n\n##### Options\n\n* `defaultValue`:\n  If the file is not found, return this value instead.\n  Parse errors will still cause failures.\n* `root`:\n  If provided, the filename will be resolved relative to it.\n* `parse`:\n  Function that takes a string and parses it into data.\n  Defaults to auto-detection from the file's extension.\n* `watch`: Watch file for changes, defaults to `false`.\n* `interval`:\n  Interval for checking for file changes in ms.\n  If provided, has to be at least 1000 (1 second).\n  Any value `\u003c= 0` disables the behavior.\n  If `watch` is enabled, `interval` is automatically disabled.\n  Defaults to `0` (disabled).\n\n### `fileAlternativesContent([filename, ...], options)`\n\n```js\nconst fileAlternativesContent = require('shared-store/file-alternatives');\n```\n\nAn observable representing the content of a single file, chosen from among\nmultiple alternatives.  All of the alternatives in the array argument are\nchecked for existence.  Iff exactly one exists, it is passed along with\n`options` to `fileContent()`.  This is useful for multiple extensions:\n\n```js\nfileAlternativesContent(['some-file.cson', 'some-file.json'], options)\n```\n\nor for a legacy path location and a new path location:\n\n```js\nfileAlternativesContent([oldestPath, oldPath, currentPath], options)\n```\n\nIf more than one or none of the paths exist, it is an error.\n\n### `httpResource({fetch, interval})`\n\n```js\nconst httpResource = require('shared-store/http');\n```\n\nAn observable representing a cacheable HTTP resource.\nMakes only minimal assumptions about how the data is fetched\nand focuses on handling caching.\n\nFor `interval`, see the `fileContent` options above.\n\n##### `fetch(headers) -\u003e Promise({ body, response })`\n\nThe http resource will pass in cache headers\nthat should be forwarded by the fetch implementation.\n`httpResource` will store `ETag` and `Last-Modified` headers\nin the response and pass them into subsequent requests.\nIf `response.statusCode` is 304,\nthe last known body will be returned.\n`httpResource` does not interpret the body in any way.\n\nExample `fetch` implementation using `request`:\n\n```js\nfunction fetch(headers) {\n  return new Promise((resolve, reject) =\u003e {\n    request('http://my-service/config', {\n      headers, json: true\n    }, (error, response) =\u003e {\n      if (response \u0026\u0026 response.statusCode === 304) {\n        // Ignore parse errors etc. when a 304 is received\n        return resolve({ response });\n      } else if (error) {\n        // Forward errors\n        return reject(error);\n      }\n      // Default: resolve with response and body\n      resolve({ response, body: response.body });\n    });\n  });\n}\n```\n\nIf you don't set an interval,\nhandling the `headers` argument isn't necessary.\n\n## Events\n\nEvents are emitted on instances of the `SharedStore` class.  Most notably,\nerror events are emitted as `'err'` and not `'error'`.  This is because\nunhandled `'error'` events throw, which isn't necessary when fetching fails\ndue to HTTP connection timeouts, etc.\n\nIn this scenario, the cached copy will continue to be used until the data\nsource is available again.\n\n## Tests\nOnce you've cloned the repository, you can run:\n\n```bash\nnpm run setup\nnpm test\n```\n\nNote: `npm run setup` is the same as `npm install` except that it ensures the\npublic npm registry is always used.\n\n## License\n```\nCopyright (c) 2015, Groupon, Inc.\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions\nare met:\n\nRedistributions of source code must retain the above copyright notice,\nthis list of conditions and the following disclaimer.\n\nRedistributions in binary form must reproduce the above copyright\nnotice, this list of conditions and the following disclaimer in the\ndocumentation and/or other materials provided with the distribution.\n\nNeither the name of GROUPON nor the names of its contributors may be\nused to endorse or promote products derived from this software without\nspecific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\nIS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED\nTO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A\nPARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nHOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED\nTO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\nPROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\nLIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\nNEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgroupon%2Fshared-store","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgroupon%2Fshared-store","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgroupon%2Fshared-store/lists"}