configate 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "configate",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "description": "Configuration helper for TypeScript applications",
6
6
  "main": "dist/index.js",
package/dist/common.d.ts DELETED
@@ -1,7 +0,0 @@
1
- export type DefaultConfig = Record<string, any>;
2
- export type Environment = 'development' | 'staging' | 'production' | 'test' | string;
3
- export type FileExtension = 'ts' | 'mts' | 'js' | 'mjs' | 'cjs' | 'json';
4
- export type DeepPartial<T> = T extends object ? {
5
- [P in keyof T]?: DeepPartial<T[P]>;
6
- } : T | undefined;
7
- export declare function isObject(item: unknown): item is object;
package/dist/common.js DELETED
@@ -1,3 +0,0 @@
1
- export function isObject(item) {
2
- return item !== null && typeof item === 'object' && !Array.isArray(item);
3
- }
@@ -1,5 +0,0 @@
1
- import { type DefaultConfig } from './common.ts';
2
- /**
3
- * Use Object.freeze to make the config object immutable
4
- */
5
- export declare function deepFreezeConfig<T extends DefaultConfig>(config: T): T;
@@ -1,12 +0,0 @@
1
- import { isObject } from "./common.js";
2
- /**
3
- * Use Object.freeze to make the config object immutable
4
- */
5
- export function deepFreezeConfig(config) {
6
- for (const key in config) {
7
- if (isObject(config[key])) {
8
- config[key] = deepFreezeConfig(config[key]);
9
- }
10
- }
11
- return Object.freeze(config);
12
- }
@@ -1,9 +0,0 @@
1
- import { type DefaultConfig } from './common.ts';
2
- /**
3
- * Custom implementation of deepMerge to avoid using an external dependency
4
- *
5
- * - it merges objects deeply
6
- * - it overrides array, doesn't merge array elements
7
- * - it ignores properties with `undefined` values
8
- */
9
- export declare function deepMerge<T extends DefaultConfig>(target: T, ...sources: Array<Partial<T>>): T;
package/dist/deepMerge.js DELETED
@@ -1,31 +0,0 @@
1
- import { isObject } from "./common.js";
2
- /**
3
- * Custom implementation of deepMerge to avoid using an external dependency
4
- *
5
- * - it merges objects deeply
6
- * - it overrides array, doesn't merge array elements
7
- * - it ignores properties with `undefined` values
8
- */
9
- export function deepMerge(target, ...sources) {
10
- if (!sources.length) {
11
- return target;
12
- }
13
- const source = sources.shift();
14
- if (isObject(target) && isObject(source)) {
15
- for (const key in source) {
16
- if (Object.prototype.hasOwnProperty.call(source, key)) {
17
- if (key === '__proto__' || key === 'constructor') {
18
- continue;
19
- }
20
- const sourceValue = source[key];
21
- if (isObject(sourceValue) && isObject(target[key])) {
22
- target[key] = deepMerge(target[key], sourceValue);
23
- }
24
- else if (sourceValue !== undefined) {
25
- target[key] = sourceValue;
26
- }
27
- }
28
- }
29
- }
30
- return deepMerge(target, ...sources);
31
- }
@@ -1,39 +0,0 @@
1
- import type { DefaultConfig, Environment, FileExtension } from './common.ts';
2
- /**
3
- * Config library to load configuration files based on the environment
4
- *
5
- * Features:
6
- * - Load config files in order:
7
- * 1. default.ext
8
- * 2. {environment}.ext
9
- * 4. local.ext
10
- * 3. local-{environment}.ext
11
- * 5. custom-environment-variables.ext (only for .ts and .js files)
12
- * - Throw an error if a property is accessed that is not defined in the config
13
- * - Freeze the config object to prevent modifications
14
- * - Supported config file formats: TypeScript (.ts, .mts), JavaScript (.js, .mjs), JSON (.json)
15
- * - Config file can `export default {}` or `export const config: Config = { ... }`.
16
- * - named export is recommended in TS for type safety of the config object
17
- *
18
- * @param configDirs - paths to directories where the config files are located. Relative or absolute. Directories are merged in the array order. Default: ['${current-working-directory}/config']
19
- * @param environment - the environment to load the config for. Default: process.env.NODE_ENV
20
- * @param fileExtensions - an array of file extensions to use when looking for config files. Configs are loaded in this order. Default: ['ts', 'js']
21
- * @param throwOnUndefinedProp - throw an error if a property is accessed that is not defined in the config. Default: true
22
- * @param freezeConfig - freeze the config object to prevent modifications. Default: true
23
- *
24
- * @returns a promise that resolves to an object with 2 properties:
25
- * - `config` - a secure, frozen object with the loaded config. Throws an error if an undefined property is accessed
26
- * - `unsecureConfig` - an unsecure config. May be needed if config is passed directly to some module that wants to read undefined props or modify it
27
- */
28
- export declare function defineConfig<Config extends DefaultConfig>({ configDirs, environment, fileExtensions, throwOnUndefinedProp, freezeConfig, }: DefineConfigOptions): Promise<{
29
- config: Config;
30
- unsecureConfig: Config;
31
- }>;
32
- type DefineConfigOptions = {
33
- configDirs?: string[];
34
- environment?: Environment;
35
- fileExtensions?: FileExtension[];
36
- throwOnUndefinedProp?: boolean;
37
- freezeConfig?: boolean;
38
- };
39
- export {};
@@ -1,52 +0,0 @@
1
- import { deepFreezeConfig } from "./deepFreezeConfig.js";
2
- import { deepMerge } from "./deepMerge.js";
3
- import { importConfigFiles } from "./importConfigFiles.js";
4
- import { makeSecureDeepProxy } from "./makeSecureDeepProxy.js";
5
- /**
6
- * Config library to load configuration files based on the environment
7
- *
8
- * Features:
9
- * - Load config files in order:
10
- * 1. default.ext
11
- * 2. {environment}.ext
12
- * 4. local.ext
13
- * 3. local-{environment}.ext
14
- * 5. custom-environment-variables.ext (only for .ts and .js files)
15
- * - Throw an error if a property is accessed that is not defined in the config
16
- * - Freeze the config object to prevent modifications
17
- * - Supported config file formats: TypeScript (.ts, .mts), JavaScript (.js, .mjs), JSON (.json)
18
- * - Config file can `export default {}` or `export const config: Config = { ... }`.
19
- * - named export is recommended in TS for type safety of the config object
20
- *
21
- * @param configDirs - paths to directories where the config files are located. Relative or absolute. Directories are merged in the array order. Default: ['${current-working-directory}/config']
22
- * @param environment - the environment to load the config for. Default: process.env.NODE_ENV
23
- * @param fileExtensions - an array of file extensions to use when looking for config files. Configs are loaded in this order. Default: ['ts', 'js']
24
- * @param throwOnUndefinedProp - throw an error if a property is accessed that is not defined in the config. Default: true
25
- * @param freezeConfig - freeze the config object to prevent modifications. Default: true
26
- *
27
- * @returns a promise that resolves to an object with 2 properties:
28
- * - `config` - a secure, frozen object with the loaded config. Throws an error if an undefined property is accessed
29
- * - `unsecureConfig` - an unsecure config. May be needed if config is passed directly to some module that wants to read undefined props or modify it
30
- */
31
- export async function defineConfig({ configDirs = [`${process.cwd()}/config`], environment = process.env.NODE_ENV, fileExtensions = ['ts', 'js'], throwOnUndefinedProp = true, freezeConfig = true, }) {
32
- let mergedConfig = {};
33
- for (const configDir of configDirs) {
34
- const config = await importConfigFiles({
35
- configDir,
36
- environment,
37
- fileExtensions,
38
- });
39
- mergedConfig = deepMerge(mergedConfig, config);
40
- }
41
- const unsecureConfig = structuredClone(mergedConfig);
42
- if (throwOnUndefinedProp) {
43
- mergedConfig = makeSecureDeepProxy(mergedConfig);
44
- }
45
- if (freezeConfig) {
46
- mergedConfig = deepFreezeConfig(mergedConfig);
47
- }
48
- return {
49
- config: mergedConfig,
50
- unsecureConfig: unsecureConfig,
51
- };
52
- }
@@ -1,6 +0,0 @@
1
- import type { DefaultConfig, FileExtension } from './common.ts';
2
- export declare function importConfigFile<Config extends DefaultConfig>({ filePath, fileExtensions, shouldThrowError, }: {
3
- filePath: string;
4
- fileExtensions: FileExtension[];
5
- shouldThrowError?: boolean;
6
- }): Promise<Config>;
@@ -1,45 +0,0 @@
1
- var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
2
- if (typeof path === "string" && /^\.\.?\//.test(path)) {
3
- return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
4
- return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
5
- });
6
- }
7
- return path;
8
- };
9
- import fs from 'node:fs/promises';
10
- export async function importConfigFile({ filePath, fileExtensions, shouldThrowError = false, }) {
11
- let selectedExtension;
12
- for (const ext of fileExtensions) {
13
- try {
14
- // Quickly check if the file exists to avoid unnecessary parsing in import()
15
- await fs.access(`${filePath}.${ext}`, fs.constants.R_OK);
16
- selectedExtension = ext;
17
- break;
18
- }
19
- catch (_error) { }
20
- }
21
- if (!selectedExtension) {
22
- if (shouldThrowError) {
23
- throw new Error(`Config file "${filePath}" not found with any extension: ${fileExtensions.join(', ')}`);
24
- }
25
- return {};
26
- }
27
- const importOptions = selectedExtension === 'json' ? { with: { type: 'json' } } : undefined;
28
- try {
29
- const configFile = await import(__rewriteRelativeImportExtension(`${filePath}.${selectedExtension}`), importOptions);
30
- // Named export of `config` variable is preferred over default export
31
- if (configFile.config) {
32
- return configFile.config;
33
- }
34
- if (configFile.default) {
35
- return configFile.default;
36
- }
37
- throw new Error(`Config file "${filePath}" has no default export and does not have export "config"`);
38
- }
39
- catch (error) {
40
- if (shouldThrowError) {
41
- throw error;
42
- }
43
- return {};
44
- }
45
- }
@@ -1,8 +0,0 @@
1
- import type { DefaultConfig, Environment, FileExtension } from './common.ts';
2
- export declare function importConfigFiles<Config extends DefaultConfig>({ configDir, environment, fileExtensions, }: ImportConfigFilesOptions): Promise<DefaultConfig>;
3
- type ImportConfigFilesOptions = {
4
- configDir: string;
5
- environment?: Environment;
6
- fileExtensions: FileExtension[];
7
- };
8
- export {};
@@ -1,37 +0,0 @@
1
- import { deepMerge } from "./deepMerge.js";
2
- import { importConfigFile } from "./importConfigFile.js";
3
- export async function importConfigFiles({ configDir, environment, fileExtensions, }) {
4
- const defaultConfig = await importConfigFile({
5
- filePath: `${configDir}/default`,
6
- fileExtensions,
7
- shouldThrowError: true,
8
- });
9
- let envConfig;
10
- if (environment) {
11
- try {
12
- envConfig = await importConfigFile({
13
- filePath: `${configDir}/${environment}`,
14
- fileExtensions,
15
- shouldThrowError: true,
16
- });
17
- }
18
- catch (_error) {
19
- console.warn(`Environment ${environment} defined but no config file found`);
20
- }
21
- }
22
- const localConfig = await importConfigFile({
23
- filePath: `${configDir}/local`,
24
- fileExtensions,
25
- });
26
- const localEnvConfig = environment
27
- ? await importConfigFile({
28
- filePath: `${configDir}/local-${environment}`,
29
- fileExtensions,
30
- })
31
- : {};
32
- const customEnvVarsConfig = await importConfigFile({
33
- filePath: `${configDir}/custom-environment-variables`,
34
- fileExtensions: fileExtensions.filter((ext) => ext !== 'json'), // JSON not supported because it cannot use `process.env`
35
- });
36
- return deepMerge(structuredClone(defaultConfig), structuredClone(envConfig ?? {}), structuredClone(localConfig), structuredClone(localEnvConfig), structuredClone(customEnvVarsConfig));
37
- }
package/dist/index.d.ts DELETED
@@ -1,2 +0,0 @@
1
- export { defineConfig } from './defineConfig.ts';
2
- export type { DeepPartial, Environment, FileExtension } from './common.ts';
package/dist/index.js DELETED
@@ -1 +0,0 @@
1
- export { defineConfig } from "./defineConfig.js";
@@ -1,6 +0,0 @@
1
- import { type DefaultConfig } from './common.ts';
2
- /**
3
- * Use Proxy to add custom behavior when accessing properties of the config object
4
- * Throw an error if the property is not defined in the config
5
- */
6
- export declare function makeSecureDeepProxy<T extends DefaultConfig>(config: T): T;
@@ -1,35 +0,0 @@
1
- import { isObject } from "./common.js";
2
- /**
3
- * Use Proxy to add custom behavior when accessing properties of the config object
4
- * Throw an error if the property is not defined in the config
5
- */
6
- export function makeSecureDeepProxy(config) {
7
- for (const prop in config) {
8
- if (isObject(config[prop])) {
9
- // If it's an object, recursively wrap it
10
- config[prop] = makeSecureDeepProxy(config[prop]);
11
- }
12
- else if (Array.isArray(config[prop])) {
13
- // If it's an array, wrap each element and the array itself
14
- config[prop] = makeSecureProxy(config[prop].map((item) => isObject(item) || Array.isArray(item)
15
- ? makeSecureDeepProxy(item)
16
- : item));
17
- }
18
- }
19
- return makeSecureProxy(config);
20
- }
21
- function makeSecureProxy(targetObject) {
22
- return new Proxy(targetObject, {
23
- get(target, prop) {
24
- // Handle Symbol.toStringTag and Array properties
25
- if (prop === Symbol.toStringTag ||
26
- (Array.isArray(target) && Number.isNaN(Number(prop)))) {
27
- return Reflect.get(target, prop);
28
- }
29
- if (!(prop in target)) {
30
- throw new Error(`Property ${String(prop)} is not defined in the config`);
31
- }
32
- return Reflect.get(target, prop);
33
- },
34
- });
35
- }