{"id":20526315,"url":"https://github.com/lostpebble/dynamic-config-store","last_synced_at":"2025-06-26T06:33:12.866Z","repository":{"id":33275065,"uuid":"157386568","full_name":"lostpebble/dynamic-config-store","owner":"lostpebble","description":"Simple configuration utility for deployments and libraries - written in Typescript","archived":false,"fork":false,"pushed_at":"2023-01-08T19:11:50.000Z","size":312,"stargazers_count":5,"open_issues_count":2,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-06-18T23:37:51.386Z","etag":null,"topics":["config","configuration","configuration-management","env","environment-variables","node"],"latest_commit_sha":null,"homepage":"","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/lostpebble.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}},"created_at":"2018-11-13T13:41:01.000Z","updated_at":"2025-05-12T18:40:44.000Z","dependencies_parsed_at":"2023-01-15T00:17:53.093Z","dependency_job_id":null,"html_url":"https://github.com/lostpebble/dynamic-config-store","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/lostpebble/dynamic-config-store","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lostpebble%2Fdynamic-config-store","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lostpebble%2Fdynamic-config-store/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lostpebble%2Fdynamic-config-store/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lostpebble%2Fdynamic-config-store/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lostpebble","download_url":"https://codeload.github.com/lostpebble/dynamic-config-store/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lostpebble%2Fdynamic-config-store/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":262014762,"owners_count":23245197,"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":["config","configuration","configuration-management","env","environment-variables","node"],"created_at":"2024-11-15T23:13:38.376Z","updated_at":"2025-06-26T06:33:12.814Z","avatar_url":"https://github.com/lostpebble.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"## 🔱 Dynamic Config Store\n\nA utility for configurations which can be dynamically changed from various sources.\n\n```\nyarn add dynamic-config-store\n```\n\nThe library has been built from the ground up in Typescript, so its recommended (though not required)\nto use it to get some great auto-completion suggestions for your configs.\n\n## Quick Start\n\nLet's create a basic server configuration:\n\n```typescript\nimport { ConfigStore } from \"dynamic-config-store\";\n\n// Can pass a TypeScript interface to the constructor\n// for all the goodness that comes with that\n\nconst config = new ConfigStore\u003cISimpleServerConfig\u003e({\n  isProductionEnv: false,\n  serverSecret: \"123abc\",\n  instance: {\n    port: 8000,\n    host: {\n      protocol: \"http\",\n      hostname: \"localhost\",\n    },\n  },\n});\n```\n\nThis creates our initial configuration, with **well-defined defaults**.\n\nGetting config values at a later stage is easy:\n\n```\nconst { isProductionEnv } = config.getConfig();\n```\n\nAs far as usefulness goes, this isn't giving us much at the moment. Let's look at some ways `dynamic-config-store` expands on the\ncustomization of your configs:\n\n### 🔌 Environment Links\n\n#### Pre-defined environment overrides for your config\n\nLet's try one now:\n\n```typescript\nconfig.setEnvLinks({\n  isProductionEnv: {\n    env: \"NODE_ENV\",\n    type: ETypeOfEnvLink.FUNCTION,\n    func: v =\u003e v === \"production\",\n  },\n});\n```\n\nWe now have a nice global, _boolean_, configuration value for `isProductionEnv` which we can call upon.\n   No more ugly checks for `process.env.NODE_ENV === \"production\"` all over the place.\n\nLook at the `type` here. We passed a `FUNCTION` type, which means that whatever we read from the environment\n   variables will be passed through a function, which we have defined here in `func`, and the returned value\n   will become `isProductionEnv`.\n\nBy default, this link is `required` - as in, if we don't find `process.env.NODE_ENV`, the config will throw an error.\n\nLet's look at some more:\n\n```typescript\nconfig.setEnvLinks({\n  instance: {\n    port: {\n      env: \"SERVER_PORT\",\n      type: ETypeOfEnvLink.NUMBER,\n      required: false\n    },\n    host: {\n      hostname: {\n        env: \"SERVER_HOST_NAME\",\n        type: ETypeOfEnvLink.STRING,\n        defaultValue: \"example.com\",\n        required: false\n      }\n    }\n  },\n});\n```\n\nHere we define two more environment links. You can define as many and as deeply in the config\n\"tree\" as you would like. For the sake of example we have run `setEnvLinks()` twice now, but\nyou can combine all your links in one go.\n\nYou can see on the second link here, we set a `defaultValue` - this will take precedence over\nthe initial default value we set at the beginning, on defining the config. (This is mostly useful\nwhen overriding external library configs)\n\nLastly, look at the `type` that has been set for both. `NUMBER` and `STRING` respectively. Let's go\ninto more detail on those:\n\n#### Environment Link Types\n\nEnvironment variables are always of `string` type, but sometimes we don't always want strings.\n`dynamice-config-store` provides you with alternative ways to interpret values from the environment:\n\n- `STRING` - The value will be returned as is from the environment variable\n- `NUMBER` - The value will be converted and set as a number on your config property\n- `JSON_STRING` - The value will be put through `JSON.parse()`, this assumes that the\n  environment value is a serialized JSON string. The config takes care of converting serialized JavaScript dates\n  back to proper `Date` objects too.\n- `FUNCTION` - Allows you to define a function under the `func` property, which will simply be passed\n  the value from the environment - and must return the final value you'd like in your config.\n\n### ⚠ Environment Overrides\n\nEvery config value has the ability to be overridden through environment variables, **_even if no link exists_**\n\nMostly used for quick fixes or debugging scenarios, when a full re-build and deployment is not desired.\n\nIn our above config, we kept it simple for the sake of example. But we really should have added a little more\ninfo to make it more deliberate in its identity.\n\nWe should have done something like this:\n\n```typescript\nconst config = new ConfigStore\u003cISimpleServerConfig\u003e({\n   isProductionEnv: false,\n   serverSecret: \"123abc\",\n   instance: {\n     port: 8080,\n     host: {\n       protocol: \"http\",\n       hostname: \"localhost\",\n     },\n   },\n }, \"CONFIG_SERVER_OVERRIDE_\", \"Server Config\");\n```\n\nThose two second parameters identify this configuration much better. And especially for the sake of\nEnvironment Overrides, the first one helps us target this specific config - like so:\n\n```typescript\n{\n isProductionEnv: false,    // CONFIG_SERVER_OVERRIDE_IS_PRODUCTION_ENV\n serverSecret: \"123abc\",    // CONFIG_SERVER_OVERRIDE_SERVER_SECRET\n instance: {\n   port: 8080,              // CONFIG_SERVER_OVERRIDE_INSTANCE__PORT\n   host: {\n     protocol: \"http\",      // CONFIG_SERVER_OVERRIDE_INSTANCE__PORT__PROTOCOL\n     hostname: \"localhost\", // CONFIG_SERVER_OVERRIDE_INSTANCE__PORT__HOSTNAME\n   },\n },\n}\n```\n\nIf any of those override environment variables have been set, they will take precedence over any other value\nset for the config.\n\nAny value set in these overrides shall be put through `JSON.parse()`, allowing for the reviving of proper\nJavaScript types - including `Date` objects. So keep that in mind, because of the following cases:\n\n```\nCONFIG_SERVER_OVERRIDE_IS_PRODUCTION_ENV:   true          // fine - can parse directly to boolean\nCONFIG_SERVER_OVERRIDE_SERVER_SECRET:       123abc        // ERROR - not JSON parsable\nCONFIG_SERVER_OVERRIDE_SERVER_SECRET:       \"123abc\"      // 50 / 50 - will fail if the quotations aren't escaped\nCONFIG_SERVER_OVERRIDE_SERVER_SECRET:       \\\"123abc\\\"    // probably safest for string types\nCONFIG_SERVER_OVERRIDE_INSTANCE__PORT:      4000          // fine - can parse directly to number\n```\n\nBasically this allows you to create great global configs for your deployments without thinking straight away\nabout what you'd like to expose as an environment variable. If you find that later you are using an override\nconstantly to set a certain config value - then its probably time to create a dedicated and well-named link.\n\n### Ignoring Environment Overrides\n\nIf for whatever reason (possibly security concerns) you don't want to allow an override for a certain value - all\nyou have to do to ignore the override is get your config like so:\n\n```typescript\nconst { serverSecret } = ServerConfig.getConfig({ ignoreOverrides: true });\n```\n\n## ✨ Config Reactions\n\nSometimes you need values in your config which are based off of other values. For this purpose you\ncan create reactions in the config which will play out after all other values have been set by whatever\nmeans:\n\n```typescript\nconfig.addConfigChangeReaction((config) =\u003e {\n  if (!config.isProductionEnv) {\n    config.serverSecret = \"dev_secret\";\n  }\n});\n```\n\nYou don't need to return a value - simply change the current config directly. The great library [Immer](https://github.com/mweststrate/immer) is used under the covers to\nallow this!\n\n## Deep Merging and Extending\n\nAll of the methods in `dynamic-config-store` use **deep merging** when changing the config. This\nensures that your objects deeper in the config tree are not wholly over-written or cleared when you\nsimply want to change a single value within one.\n\nThis is useful for when you are extending an external configuration, from perhaps a library or one of your own\ninternal packages that you use in different projects for code re-use. For example, if you were extending this\nServer Config from the example in a project you might do something like:\n\n```typescript\nimport { ServerConfig } from \"server-utility/ServerConfig\";\n\nServerConfig.setConfig({\n   serverSecret: \"my-new-secret\",\n   instance: {\n     port: 3000,\n   },\n});\n```\n\nThis would set a new `serverSecret` default, and would only replace the `port` value inside of `instance`, and keep all the original defaults set by the\noriginal config during creation, here in a library apparently called `server-utility`. You could even now define\nyour own **Environment Links** into this external configuration, specific to this deployment:\n\n```typescript\nServerConfig.setEnvLinks({\n  serverSecret: {\n    env: \"MY_SERVER_SECRET\",\n    type: ETypeOfEnvLink.STRING\n  },\n}, true);   // Setting true here will wipe out all the Links set previously!\n            // In this case, SERVER_PORT, SERVER_HOST_NAME and NODE_ENV\n            // default is false - as this is most often not desired\n```\n\nModularity and extensibility are first class citizens in `dynamic-config-store`!\n\n## Best practice\n\nYou should always define your configuration before any other code runs. Let's look at a simple example of\na config for a server deployment:\n\nCreate an entry file and import the config file first, this initializes it before any other code:\n\n```typescript\n// project/src/entry.ts\nimport \"./ServerConfig\";\nimport \"./Server\";\n```\n\n```typescript\n// project/src/ServerConfig.ts\nimport { ConfigStore } from \"dynamic-config-store\";\n\nexport const ServerConfig = new ConfigStore\u003cISimpleServerConfig\u003e({\n   isProductionEnv: false,\n   serverSecret: \"123abc\",\n   instance: {\n     port: 8080,\n     host: {\n       protocol: \"http\",\n       hostname: \"localhost\",\n     },\n   },\n }, \"CONFIG_SERVER_OVERRIDE_\", \"Server Config\");\n```\n\nThat potential Server file:\n\n```typescript\n// project/src/Server.ts\nimport { ServerConfig } from \"./ServerConfig\";\nimport Koa from \"koa\";\n\nconst { serverSecret, isProductionEnv, instance: { port, host: { protocol, hostname }} } = ServerConfig.getConfig();\n\nconst app = new Koa();\n\napp.keys([serverSecret]);\n\n// ... app configuration ...\n\napp.listen(port, () =\u003e {\n  if (!isProductionEnv) {\n    console.info(`Server is not running as production!`);\n  }\n  \n  console.info(`listening on ${protocol}://${hostname}:${port}`);\n});\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flostpebble%2Fdynamic-config-store","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flostpebble%2Fdynamic-config-store","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flostpebble%2Fdynamic-config-store/lists"}