{"id":19711588,"url":"https://github.com/wjsoftware/wj-config","last_synced_at":"2025-04-29T17:31:42.036Z","repository":{"id":45972821,"uuid":"512814235","full_name":"WJSoftware/wj-config","owner":"WJSoftware","description":"JavaScript configuration module for browser and server projects that works like .Net configuration where any number of data sources are merged and environment variables can contribute/overwrite values by following a naming convention.","archived":false,"fork":false,"pushed_at":"2025-04-25T23:00:17.000Z","size":1031,"stargazers_count":18,"open_issues_count":0,"forks_count":2,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-04-25T23:11:17.588Z","etag":null,"topics":["angular","api-client","configuration","electron","javascript","nodejs","preact","react","solidjs","svelte","typescript","vue"],"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/WJSoftware.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,"zenodo":null}},"created_at":"2022-07-11T15:36:29.000Z","updated_at":"2025-04-25T23:00:13.000Z","dependencies_parsed_at":"2023-02-18T21:15:59.534Z","dependency_job_id":"b8c7ad0a-22c8-4979-aebf-e78fccf39378","html_url":"https://github.com/WJSoftware/wj-config","commit_stats":{"total_commits":124,"total_committers":2,"mean_commits":62.0,"dds":0.008064516129032251,"last_synced_commit":"269dc70af153b7e6b924ad8e19aa12753a75cce4"},"previous_names":[],"tags_count":16,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WJSoftware%2Fwj-config","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WJSoftware%2Fwj-config/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WJSoftware%2Fwj-config/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/WJSoftware%2Fwj-config/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/WJSoftware","download_url":"https://codeload.github.com/WJSoftware/wj-config/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251549275,"owners_count":21607379,"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":["angular","api-client","configuration","electron","javascript","nodejs","preact","react","solidjs","svelte","typescript","vue"],"created_at":"2024-11-11T22:12:35.233Z","updated_at":"2025-04-29T17:31:42.006Z","avatar_url":"https://github.com/WJSoftware.png","language":"TypeScript","readme":"# wj-config\n\n[![NPM](https://img.shields.io/npm/v/wj-config?style=plastic)](https://www.npmjs.com/package/wj-config)\n![Latest Release](https://img.shields.io/github/v/release/WJSoftware/wj-config?include_prereleases\u0026sort=semver\u0026style=plastic)\n![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/WJSoftware/wj-config?style=plastic\u0026color=violet)\n![npm bundle size](https://img.shields.io/bundlephobia/min/wj-config?color=red\u0026label=minified\u0026style=plastic)\n\n\u003e JavaScript configuration module for **NodeJS** and **browser frameworks** that works like .Net configuration where \n\u003e any number of data sources are merged, and environment variables can contribute/overwrite values by following a \n\u003e naming convention.\n\n\u003e [!IMPORTANT]\n\u003e ## v3.0.0\n\u003e \n\u003e Version 3.0.0 is a full re-write on the TypeScript side of the package.  Its Intellisense is now fully accurate for \n\u003e almost everything and anything.  Read all about it in [the TypeScript Wiki page](https://github.com/WJSoftware/wj-config/wiki/English__Theory__TypeScript-and-wj-config).\n\nThis JavaScript configuration library works everywhere and has zero dependencies.  Use it in Node, Deno, Bun or the \nbrowser equally.  One configuration package for all your JavaScript/TypeScript needs.\n\nFeel free to fork and pull request to include sample applications for your favorite library/framework.\n\n## Important Notes\n\n1. This package has been written in TypeScript and transpiled to ES modules.\n2. Building the configuration object is an asynchronous operation, so [top level await](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await#top_level_await) is highly recommended.\n3. The minimum supported **NodeJS** version is: ![NodeJS Minimum Version](https://img.shields.io/node/v/wj-config?style=social)\n\n## Features\n\nIn a nutshell, this configuration package provides:\n\n+ The ability to merge any number of data sources as one, just like .Net configuration loads files and memory \ndictionaries.\n+ 6 pre-defined data sources:  JSON string, POJO object, single value, fetched data, dictionary, and environment.\n+ Condition the inclusion of a data source based on a predicate function's return value.\n+ Assign traits to the current environment and condition the inclusion of data sources based on the current \nenvironment's assigned traits.\n+ The ability to create special functions from data that is meant to be used to construct URL's.  The created \nfunctions provide route replacement values, query string generation and URL encoding of replacement values.\n+ An environment object with the current environment definition and helpful `isXXX()` functions to quickly create \nconditionals based on the current environment, just like .Net's `IHostEnvironment` interface.\n+ Configuration value tracing:  If needed for troubleshooting or debugging, the configuration builder will also create \na full trace of all configuration values in the `_trace` property of the resulting configuration object.\n+ **Full IntelliSense**:  Version 3 of this package has had its TypeScript re-written and is now fully accurate.  Get \nIntellisense on all your configuration data effortlessly.\n\n## Examples\n\nThere are working examples of use [here](https://github.com/WJSoftware/wj-config/tree/main/examples).  Feel free to \nexplore them and to contribute.\n\n| Technology | wj-config Version | Technology Version | Development Port |\n| - | - | - | - |\n| ReactJS | v1.0.2 | ReactJS v18.2.0 | 3001 |\n| NodeJS Express | v1.0.2 | Express v4.16.1 | 3002 |\n| ReactJS | v1.1.0 | ReactJS v18.2.0 | 3003 |\n| NodeJS Console (CommonJS) | v2.0.0 | v18.1.0 |\n| NodeJS Console (ES Modules) | v2.0.0 | v18.1.0 |\n| NodeJS Express (CommonJS) | v2.0.0 | Express v4.16.1 | 3004 |\n| NodeJS Express (ES Modules) | v2.0.0 | Express v4.18.1 | 3005 |\n| ReactJS | v2.0.0 | v18.2.0 | 3006 |\n| VueJS | v2.0.0 | v3.2.45 | 3007 |\n| Deno | v2.0.0 | v1.29.1 |\n| Svelte | v2.0.0 | v3.54.0 | 3008 |\n\nThe repository contains the necessary `launch.json` file to run each of the examples in *Visual Studio Code*.\n\n## Quickstart\n\n1. Install the NPM package.\n2. Create your JSON configuration files (quickstart will assume you want per-environment configuration).\n3. Build your configuration object.\n\n### 1. Install the NPM Package\n\n```bash\nnpm install wj-config\n```\n\n### 2. Create your JSON Configuration Files\n\nCreate a JSON file to be your main configuration file and name it, say `config.json`.  For **web projects** you have \ntwo choices:\n\n1. Include this file under the `/src` folder, in which case you later `import` it.\n2. Include it in the `/public` or `/static` folder, in which case you later `fetch` it.\n\nFor **NodeJS** you import or load using the `fs` module.\n\nExample configuration JSON:\n\n```json\n{\n    \"app\": {\n        \"title\": \"My Awesome App\",\n        \"system\": \"awe-app\",\n        \"id\": \"awe-app-appshell\"\n    },\n    \"logging\": {\n        \"minLevel\": \"information\"\n    },\n    \"ws\": {\n        \"defaultTimeout\": 30,\n        \"gateway\": {\n            \"rootPath\": \"/api/v1\",\n            \"login\": \"/login\",\n            \"catalogue\": {\n                \"rootPath\": \"/cat\",\n                \"getAll\": \"\",\n                \"single\": \"/{catId}\"\n            }\n        }\n    }\n}\n```\n\n\u003e [!NOTE]\n\u003e The `ws` section is special.  See [URL-Building Functions](https://github.com/WJSoftware/wj-config/wiki/English__Theory__URL-Building-Functions)\n\u003e in the **Wiki** for the details.\n\nNow write per-environment JSON files.  Example for development (would be named `config.Development.json`):\n\n```json\n{\n    \"logging\": {\n        \"minLevel\": \"debug\"\n    }\n}\n```\n\nYes, you only write the overrides, the values that change for the environment.  All other configuration values will also \nbe available, but is not necessary to repeat them:  DRY configuration.\n\n### 3. Build Your Configuration Object\n\nCreate a module of yours called `config.js` or whatever pleases you.  Obtain the environment name, load the 2 JSON \nfiles and build the configuration object.  This is generally speaking.\n\nThere are two styles available:  The *classic* style leaves to you, the programmer, the responsibility of figuring out \na way to select the correct per-environment data source.  The *conditional* style leaves the decision to the \nconfiguration builder.  Pick whichever pleases you, but know that the latter is safer.\n\nFrom now on, any code samples that call the `loadJsonFile()` function are referring to this function:\n\n```js\nfunction loadJsonFile(fileName, isRequired) {\n    const fileExists = fs.existsSync(fileName);\n    if (fileExists) {\n        const data = fs.readFileSync(fileName);\n        return JSON.parse(data);\n    }\n    else if (isRequired) {\n        throw new Error(`Configuration file ${fileName} is required but was not found.`);\n    }\n    // Return an empty object.\n    return {};\n};\n```\n\nIf you don't like it, feel free to write your own.  I wrote this before I knew of the existence of the `fs/promises` \nmodule.  If you write one yourself using async `fs`, please pull request and share the love. 😁😎\n\n#### Classic Style\n\n##### NodeJS ES Modules (Recommended)\n\n```ts\nimport wjConfig, { buildEnvironment, type EnvironmentName } from 'wj-config';\nimport mainConfig from \"./config.json\" assert {type: 'json'}; // Importing data is a thing in NodeJS.\n\n// Obtain an environment object ahead of time to help setting configuration up.\nconst environments = ['Test', 'Staging', 'Production'] as const;\nconst env = buildEnvironment(environments, process.env.NODE_ENV as EnvironmentName\u003ctypeof environments\u003e);\n\nconst configPromise = wjConfig()\n    .addObject(mainConfig) // Main configuration JSON file.\n    .name('Main') // Give data sources a meaningful name for value tracing purposes.\n    .addObject(loadJsonFile(`./config.${env.current.name}.json`)) // The developer is deciding by using a file name tactic.\n    .name(env.current.name)\n    .addEnvironment(process.env) // Adds a data source that reads the environment variables in process.env.\n    .includeEnvironment(env) // So the final configuration object has the environment property.\n    .createUrlFunctions('ws') // So the final configuration object will contain URL builder functions.\n    .build(env.isDevelopment()); // Only trace configuration values in the Development environment.\n\n// This is a top-level await:\nexport default await configPromise; // The build() function is asynchronous, so await its promise and export the result.\n```\n\nThe calls to `addEnvironment()`, `includeEnvironment()` and `createUrlFunctions()` are not mandatory, they are just \ncustomary.  Typically, you also want to include the environment variables, have the `environment` object and also make \nuse of [URL-Building Functions](https://github.com/WJSoftware/wj-config/wiki/English__Theory__URL-Building-Functions).\n\n##### NodeJS CommonJS Modules (If You Must)\n\n```js\n// Export the result of an IIFE, which will be a promise to return the configuration object.  This means that code in \n// need for the configuration object will have to execute inside async functions to be able to await, or wrap the \n// whole thing within a call to .then(), like in one of the examples provided in this project's repository.\n// This is why CommonJS is discouraged.  It makes things more complex.\nmodule.exports = (async function () {\n    const { default: wjConfig, buildEnvironment } = await import('wj-config');\n    const environments = ['Test', 'Staging', 'Production'];\n    const env = buildEnvironment(environments, process.env.NODE_ENV);\n    return wjConfig()\n        .addObject(loadJsonFile('./config.json', true))\n        .name('Main')\n        .addObject(loadJsonFile(`./config.${env.current.name}.json`))\n        .name(env.current.name)\n        .addEnvironment(process.env)\n        .includeEnvironment(env)\n        .createUrlFunctions('ws')\n        .build(env.isDevelopment());\n})();\n```\n\n##### Web Projects\n\n\u003e [!IMPORTANT]\n\u003e If your project is a React project created with *Create React App*, the recommendation is to eject or use the \n\u003e `@craco/craco` package (or similar one) in order to configure webpack to allow top-level awaits.  You can read the \n\u003e details in the [Top Level Await](https://github.com/WJSoftware/wj-config/wiki/English__JavaScript-Concepts__Top-Level-Await) \n\u003e section in the **Wiki**.  It can also work without top-level awaits, but in all honesty, I don't like it.  The \n\u003e **Wiki** also explains how to achieve this for Vite projects (Vue, Svelte, React, etc.).\n\n```ts\nimport wjConfig, { buildEnvironment, type EnvironmentName } from 'wj-config';\nimport mainConfig from './config.json'; // One may import data like this, or fetch it.\n\nconst environments = ['Test', 'Staging', 'Production'] as const;\nconst env = buildEnvironment(environments, window.env.REACT_ENVIRONMENT as EnvironmentName\u003ctypeof environments\u003e);\nconst configPromise = wjConfig()\n    .addObject(mainConfig)\n    .name('Main') // Give data sources a meaningful name for value tracing purposes.\n    .addFetched(`/config.${env.current.name}.json`, false) // Fetch the JSON from the /public folder.\n    .name(env.current.name)\n    .addEnvironment(window.env, 'REACT_APP_') // Adds a data source that reads the environment variables in window.env.\n    .includeEnvironment(env) // So the final configuration object has the environment property.\n    .createUrlFunctions('ws') // So the final configuration object will contain URL builder functions.\n    .build(env.isDevelopment()); // Only trace configuration values in the Development environment.\n\nexport default await configPromise;\n```\n\n---\n\nOk, now go ahead and consume your configuration object anywhere you need it.\n\n### NodeJS ES Modules\n\n```javascript\nimport config from './config.js';\n\nconsole.log(config.app.title);\n```\n\n### NodeJS CommonJS Modules\n\n```javascript\nconst configPromise = require('./config.js');\n\nconfigPromise.then(config =\u003e {\n    console.log(config.app.title);\n});\n```\n\n### Web Projects\n\n```javascript\nimport config from './config';\n\nconsole.log(config.app.title);\n```\n\n### Conditional Style\n\n\u003e Since **v2.0.0**\n\nThere are two possible ways to do conditional style per-environment configuration.  The shortest first using the \n**Web Projects** sample:\n\n```ts\nimport wjConfig, { buildEnvironment, type EnvironmentName } from 'wj-config';\nimport mainConfig from './config.json';\n\nconst environments = ['Test', 'Staging', 'Production'] as const;\nconst env = buildEnvironment(environments, window.env.REACT_ENVIRONMENT as EnvironmentName\u003ctypeof environments\u003e);\nconst config = wjConfig()\n    .addObject(mainConfig)\n    .name('Main')\n    .includeEnvironment(env)\n    .addPerEnvironment((b, envName) =\u003e b.addFetched(`/config.${envName}.json`, false))\n    .addEnvironment(window.env, 'REACT_APP_')\n    .createUrlFunctions('ws')\n    .build(env.isDevelopment());\n\nexport default await config;\n```\n\nIt looks almost identical to the classic.  This one has a few advantages:\n\n1. Covers all possible environments.\n2. Helps you avoid typos.\n3. Makes sure there's at least one data source per defined environment.\n\nThis conditional style requires the call to `includeEnvironment()` and to be made *before* calling \n`addPerEnvironment()`.  Make sure you define your environment names when creating the environment object:\n\n```ts\nconst environments = ['myDev', 'myTest', 'myProd'] as const;\nconst env = buildEnvironment(environments, window.env.REACT_ENVIRONMENT as EnvironmentName\u003ctypeof environments\u003e);\n```\n\nThis way `addPerEnvironment()` knows your environment names.\n\nThe longer way of the conditional style looks like this:\n\n```ts\nimport wjConfig, { buildEnvironment, type EnvironmentName } from 'wj-config';\nimport mainConfig from './config.json';\n\nconst environments = [\n    'Development',\n    'PreProduction',\n    'Production'\n] as const;\nconst env = buildEnvironment(environments, window.env.REACT_ENVIRONMENT as EnvironmentName\u003ctypeof environments\u003e);\nconst config = wjConfig()\n    .addObject(mainConfig)\n    .name('Main')\n    .addFetched(`/config.Development.json`, false)\n    .forEnvironment('Development')\n    .addFetched(`/config.PreProduction.json`, false)\n    .forEnvironment('PreProduction')\n    .addFetched(`/config.Production.json`, false)\n    .forEnvironment('Production')\n    .addEnvironment(window.env, 'REACT_APP_')\n    .includeEnvironment(env)\n    .createUrlFunctions('ws')\n    .build(env.isDevelopment());\n\nexport default await config;\n```\n\nThis one has advantages 2 and 3 above, plus allows for the possiblity of having completely different data source types \nper environment.  Furthermore, this allows you to add more environment-specific data sources if, for example, a \nparticular environment requires 2 or more data sources.  95% of the time you'll need the short one only.\n\n\u003e [!NOTE]\n\u003e This \"long\" version can be mixed with the \"short\" version, if you so desire.\n\nThis works in **NodeJS** too.  There is a performance catch, though:  If in NodeJS you use `loadJsonFile()` with the \n`addObject()` data source function, you'll be reading all per-environment configuration files, even the unqualified \nones.  To avoid this performance hit, pass a function to `addObject()` that, in turn, calls `loadJsonFile()`:\n\n```ts\nimport wjConfig, { buildEnvironment, EnvironmentName } from 'wj-config';\nimport mainConfig from \"./config.json\" assert {type: 'json'};\n\nconst environments = ['Test', 'Staging', 'Production'] as const;\nconst env = buildEnvironment(environments, process.env.NODE_ENV as EnvironmentName\u003ctypeof environments\u003e);\n\nconst config = wjConfig()\n    .addObject(mainConfig)\n    .name('Main')\n    .includeEnvironment(env)\n    // Using a function that calls loadJsonFile() instead of calling loadJsonFile directly.\n    .addPerEnvironment((b, envName) =\u003e b.addObject(() =\u003e loadJsonFile(`./config.${envName}.json`)))\n    .addEnvironment(process.env)\n    .createUrlFunctions('ws')\n    .build(env.isDevelopment());\n\nexport default await config;\n```\n\nNow you know how to do per-environment configuration in the *classic* and *conditional* styles.  Pick your poison.\n\n## Documentation\n\nThis README was already too long, so all documentation has been re-written and placed in this repository's \n[wiki](https://github.com/WJSoftware/wj-config/wiki).  It is in English only for now.\n\nBe sure to stop by because this not-so-quick start tutorial only scratched the surface of what is possible with \n**wj-config**.\n\nEnjoy!\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwjsoftware%2Fwj-config","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwjsoftware%2Fwj-config","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwjsoftware%2Fwj-config/lists"}