konfeeg 0.0.1 → 0.0.3

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/README.md CHANGED
@@ -102,7 +102,7 @@ config.mongo.poolSize // number
102
102
  | Field | Required | Description |
103
103
  | --------------------------------- | -------- | ------------------------------------------------------------------------------------- |
104
104
  | `doc` | required | Human-readable description |
105
- | `format` | required | Validation format — see below |
105
+ | `format` | optional | Validation format — see below. If omitted, no validation is applied; the resolved type is inferred from `value`/per-env fields, or `any` if none are declared |
106
106
  | `value` | optional | Constant shared across all environments (lowest priority) |
107
107
  | `processEnv` | optional | `process.env` key — runtime override (highest priority) |
108
108
  | `importMetaEnv` | optional | `import.meta.env` key — runtime override (highest priority) |
package/dist/index.cjs CHANGED
@@ -3,28 +3,28 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  function validateAndCoerce(value, format, fullKey, errors) {
4
4
  switch (format) {
5
5
  case String:
6
- if (typeof value !== "string") errors.push(`Config value for ${fullKey} must be a string`);
6
+ if (typeof value !== "string") errors.push(`${fullKey}: must be a string`);
7
7
  break;
8
8
  case Number:
9
9
  value = Number(value);
10
- if (isNaN(value)) errors.push(`Config value for ${fullKey} must be a number`);
10
+ if (isNaN(value)) errors.push(`${fullKey}: must be a number`);
11
11
  break;
12
12
  case Boolean:
13
- if (typeof value !== "boolean" && value !== "true" && value !== "false" && value !== 1 && value !== 0) errors.push(`Config value for ${fullKey} must be a boolean`);
13
+ if (typeof value !== "boolean" && value !== "true" && value !== "false" && value !== 1 && value !== 0) errors.push(`${fullKey}: must be a boolean`);
14
14
  value = value === "true" ? true : value === "false" ? false : Boolean(value);
15
15
  break;
16
16
  case Array:
17
- if (!Array.isArray(value)) errors.push(`Config value for ${fullKey} must be an array`);
17
+ if (!Array.isArray(value)) errors.push(`${fullKey}: must be an array`);
18
18
  break;
19
19
  case "url":
20
20
  try {
21
21
  new URL(value);
22
22
  } catch {
23
- errors.push(`Config value for ${fullKey} must be a valid URL; found "${value}"`);
23
+ errors.push(`${fullKey}: must be a valid URL; found "${value}"`);
24
24
  }
25
25
  break;
26
26
  default: if (format instanceof Array) {
27
- if (!format.includes(value)) errors.push(`Config value for ${fullKey} must be one of: [${format.join(", ")}]`);
27
+ if (!format.includes(value)) errors.push(`${fullKey}: must be one of: [${format.join(", ")}]`);
28
28
  }
29
29
  }
30
30
  return value;
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":[],"sources":["../src/format.ts","../src/create-config.ts","../src/define-config.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\n\nexport function validateAndCoerce(\n value: any,\n format: any,\n fullKey: string,\n errors: string[],\n): any {\n switch (format) {\n case String:\n if (typeof value !== \"string\") {\n errors.push(`Config value for ${fullKey} must be a string`)\n }\n break\n case Number:\n value = Number(value)\n if (isNaN(value))\n errors.push(`Config value for ${fullKey} must be a number`)\n break\n case Boolean:\n if (\n typeof value !== \"boolean\" &&\n value !== \"true\" &&\n value !== \"false\" &&\n value !== 1 &&\n value !== 0\n ) {\n errors.push(`Config value for ${fullKey} must be a boolean`)\n }\n value =\n value === \"true\" ? true : value === \"false\" ? false : Boolean(value)\n break\n case Array:\n if (!Array.isArray(value)) {\n errors.push(`Config value for ${fullKey} must be an array`)\n }\n break\n case \"url\":\n try {\n new URL(value)\n } catch {\n errors.push(\n `Config value for ${fullKey} must be a valid URL; found \"${value}\"`,\n )\n }\n break\n default:\n if (format instanceof Array) {\n if (!format.includes(value)) {\n errors.push(\n `Config value for ${fullKey} must be one of: [${format.join(\", \")}]`,\n )\n }\n }\n }\n\n return value\n}\n","/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport { validateAndCoerce } from \"./format.js\"\nimport type {\n ConfigGroup,\n ResolveConfigGroup,\n ValidateSchema,\n} from \"./types.js\"\nimport type {\n CreateConfigOptions,\n EnvName,\n EnvsShape,\n Fallbacks,\n} from \"./util-types.js\"\n\n/**\n * Create a resolved, validated config for the given environment.\n *\n * Curried so the envs declaration is bound on the first call and the\n * schema is inferred (giving you autocomplete) on the second call.\n *\n * @typeParam E - The envs shape describing required/optional environments.\n *\n * @example\n * ```ts\n * type MyEnvs = {\n * dev?: unknown\n * staging: unknown\n * production: unknown\n * }\n * const config = createEnvironmentConfig<MyEnvs>()('dev', {\n * port: { doc: 'Port', format: Number, value: 3000 },\n * })\n * config.port // number\n * ```\n *\n * @example Fallback environments\n * ```ts\n * // When running in `dev`, any entry that does not declare a `dev` value\n * // falls back to the entry's `integ` value.\n * const config = createEnvironmentConfig<MyEnvs>()(\n * 'dev',\n * {\n * apiUrl: {\n * doc: 'API URL',\n * format: 'url',\n * integ: 'https://integ.example.com',\n * staging: 'https://staging.example.com',\n * production: 'https://api.example.com',\n * },\n * },\n * { fallbacks: { dev: 'integ' } },\n * )\n * ```\n */\nexport function createEnvironmentConfig<E extends EnvsShape>() {\n return <const G extends ConfigGroup<E>>(\n env: EnvName<E>,\n inputConfig: ValidateSchema<G, E>,\n options?: CreateConfigOptions<E>,\n ): ResolveConfigGroup<G> & { env: EnvName<E> } =>\n buildConfig<E, G>(env, inputConfig as unknown as G, options)\n}\n\nfunction buildConfig<E extends EnvsShape, G extends ConfigGroup<E>>(\n env: EnvName<E>,\n inputConfig: G,\n options?: CreateConfigOptions<E>,\n): ResolveConfigGroup<G> & { env: EnvName<E> } {\n const errors: string[] = []\n\n // Resolve the per-environment lookup chain once for the active env.\n // Throws synchronously on a circular fallback chain.\n const envChain = resolveFallbackChain<E>(env, options?.fallbacks)\n\n function processConfig(\n config: ConfigGroup<E>,\n keyPrefix: string,\n ): Record<string, any> {\n const output: Record<string, any> = {}\n\n for (const [key, entry] of Object.entries(config)) {\n if (key === \"env\") {\n throw new Error(\n `Config key \"env\" is reserved and cannot be used. It will already be present by default.`,\n )\n }\n\n const fullKey = keyPrefix ? `${keyPrefix}.${key}` : key\n\n if (!(\"doc\" in entry)) {\n output[key] = processConfig(entry as ConfigGroup<E>, fullKey)\n continue\n }\n\n const configEntry = entry as any\n\n // Value resolution — sources are evaluated in ascending priority order.\n // The highest-priority source that resolves to a defined value wins.\n //\n // Priority │ Source\n // ─────────┼────────────────────────────────────────────────────────────────\n // 1 (low) │ Static `value` — same value across all environments\n // 2 │ Per-environment field, walking the fallback chain\n // │ — overrides the static value for that specific environment\n // 3 (high) │ Runtime env var via `processEnv` or `importMetaEnv`\n // │ — always wins; intended for secrets and local dev overrides\n\n // Priority 1: static value (lowest precedence)\n let value: any = \"value\" in configEntry ? configEntry.value : undefined\n\n // Priority 2: per-environment value, walking the fallback chain.\n // The first env in the chain with a defined value wins.\n for (const candidateEnv of envChain) {\n const envValue = configEntry[candidateEnv]\n if (envValue !== undefined) {\n value = envValue\n break\n }\n }\n\n // Priority 3: runtime env var (highest precedence — always wins when defined)\n if (\"processEnv\" in configEntry) {\n const runtimeOverride =\n // @ts-expect-error process may not be defined in browser builds\n typeof process !== \"undefined\" && process.env\n ? // @ts-expect-error process may not be defined in browser builds\n process.env[configEntry.processEnv as string]\n : undefined\n if (runtimeOverride !== undefined) value = runtimeOverride\n } else if (\"importMetaEnv\" in configEntry) {\n const runtimeOverride =\n // @ts-expect-error import.meta.env may not be defined in Node builds\n typeof import.meta !== \"undefined\" && import.meta.env\n ? // @ts-expect-error import.meta.env may not be defined in Node builds\n import.meta.env[configEntry.importMetaEnv as string]\n : undefined\n if (runtimeOverride !== undefined) value = runtimeOverride\n }\n\n const hasValueSource =\n value !== undefined ||\n \"processEnv\" in configEntry ||\n \"importMetaEnv\" in configEntry ||\n configEntry.optional\n\n if (value === undefined && !hasValueSource) {\n errors.push(\n `${fullKey}: No value source declared and \"optional\" is not set.`,\n )\n continue\n }\n\n if (value === undefined) {\n if (configEntry.optional) {\n value = configEntry.default\n if (value === undefined) {\n output[key] = undefined\n continue\n }\n } else {\n errors.push(\n `${fullKey}: Missing required config value in environment ${env}`,\n )\n continue\n }\n }\n\n //\n // Format validation and coercion\n //\n value = validateAndCoerce(value, configEntry.format, fullKey, errors)\n\n output[key] = value\n }\n\n return output\n }\n\n let outputConfig = processConfig(inputConfig, \"\")\n\n if (errors.length > 0) {\n console.error(\"Environment config validation failed\", errors)\n throw new Error(\n `Environment config validation failed:\\n${errors.join(\"\\n\")}`,\n )\n }\n\n outputConfig = {\n env,\n ...outputConfig,\n }\n return outputConfig as ResolveConfigGroup<G> & { env: EnvName<E> }\n}\n\n/**\n * Build the ordered list of environments to consult for per-environment\n * value resolution. The active env is always first; each subsequent entry\n * is the fallback target declared for the previous env. Throws if the\n * chain is cyclic.\n */\nfunction resolveFallbackChain<E extends EnvsShape>(\n env: EnvName<E>,\n fallbacks: Fallbacks<E> | undefined,\n): EnvName<E>[] {\n const chain: EnvName<E>[] = [env]\n if (!fallbacks) return chain\n\n const seen = new Set<string>([env])\n let current: EnvName<E> = env\n while (fallbacks[current] !== undefined) {\n const next = fallbacks[current] as EnvName<E>\n if (seen.has(next)) {\n throw new Error(\n `Circular fallback chain detected: ${[...chain, next].join(\" -> \")}`,\n )\n }\n seen.add(next)\n chain.push(next)\n current = next\n }\n return chain\n}\n","import { createEnvironmentConfig } from \"./create-config.js\"\nimport type {\n ConfigGroup,\n ResolveConfigGroup,\n ValidateSchema,\n} from \"./types.js\"\nimport type { CreateConfigOptions, EnvName, EnvsShape } from \"./util-types.js\"\n\n/**\n * Like {@link createEnvironmentConfig}, but binds the schema first and the\n * environment later. Useful when the active environment is not known at\n * schema-definition time.\n *\n * @typeParam E - The envs shape describing required/optional environments.\n *\n * @example\n * ```ts\n * type MyEnvs = {\n * dev?: unknown\n * staging: unknown\n * production: unknown\n * }\n * const buildConfig = defineEnvironmentConfig<MyEnvs>()({\n * port: { doc: 'Port', format: Number, value: 3000 },\n * })\n * const config = buildConfig('dev')\n * ```\n *\n * @example Fallback environments\n * ```ts\n * const buildConfig = defineEnvironmentConfig<MyEnvs>()(\n * { apiUrl: { doc: 'API URL', format: 'url', staging: 'https://staging' } },\n * { fallbacks: { dev: 'staging' } },\n * )\n * const config = buildConfig('dev') // apiUrl resolved from `staging`\n * ```\n */\nexport function defineEnvironmentConfig<E extends EnvsShape>() {\n const create = createEnvironmentConfig<E>()\n return <const G extends ConfigGroup<E>>(\n inputConfig: ValidateSchema<G, E>,\n options?: CreateConfigOptions<E>,\n ): ((env: EnvName<E>) => ResolveConfigGroup<G> & { env: EnvName<E> }) =>\n (env: EnvName<E>) =>\n create(env, inputConfig as any, options)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../src/format.ts","../src/create-config.ts","../src/define-config.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\n\nexport function validateAndCoerce(\n value: any,\n format: any,\n fullKey: string,\n errors: string[],\n): any {\n switch (format) {\n case String:\n if (typeof value !== \"string\") {\n errors.push(`${fullKey}: must be a string`)\n }\n break\n case Number:\n value = Number(value)\n if (isNaN(value)) errors.push(`${fullKey}: must be a number`)\n break\n case Boolean:\n if (\n typeof value !== \"boolean\" &&\n value !== \"true\" &&\n value !== \"false\" &&\n value !== 1 &&\n value !== 0\n ) {\n errors.push(`${fullKey}: must be a boolean`)\n }\n value =\n value === \"true\" ? true : value === \"false\" ? false : Boolean(value)\n break\n case Array:\n if (!Array.isArray(value)) {\n errors.push(`${fullKey}: must be an array`)\n }\n break\n case \"url\":\n try {\n new URL(value)\n } catch {\n errors.push(`${fullKey}: must be a valid URL; found \"${value}\"`)\n }\n break\n default:\n if (format instanceof Array) {\n if (!format.includes(value)) {\n errors.push(`${fullKey}: must be one of: [${format.join(\", \")}]`)\n }\n }\n }\n\n return value\n}\n","/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport { validateAndCoerce } from \"./format.js\"\nimport type {\n ConfigGroup,\n ResolveConfigGroup,\n ValidateSchema,\n} from \"./types.js\"\nimport type {\n CreateConfigOptions,\n EnvName,\n EnvsShape,\n Fallbacks,\n} from \"./util-types.js\"\n\n/**\n * Create a resolved, validated config for the given environment.\n *\n * Curried so the envs declaration is bound on the first call and the\n * schema is inferred (giving you autocomplete) on the second call.\n *\n * @typeParam E - The envs shape describing required/optional environments.\n *\n * @example\n * ```ts\n * type MyEnvs = {\n * dev?: unknown\n * staging: unknown\n * production: unknown\n * }\n * const config = createEnvironmentConfig<MyEnvs>()('dev', {\n * port: { doc: 'Port', format: Number, value: 3000 },\n * })\n * config.port // number\n * ```\n *\n * @example Fallback environments\n * ```ts\n * // When running in `dev`, any entry that does not declare a `dev` value\n * // falls back to the entry's `integ` value.\n * const config = createEnvironmentConfig<MyEnvs>()(\n * 'dev',\n * {\n * apiUrl: {\n * doc: 'API URL',\n * format: 'url',\n * integ: 'https://integ.example.com',\n * staging: 'https://staging.example.com',\n * production: 'https://api.example.com',\n * },\n * },\n * { fallbacks: { dev: 'integ' } },\n * )\n * ```\n */\nexport function createEnvironmentConfig<E extends EnvsShape>() {\n return <const G extends ConfigGroup<E>>(\n env: EnvName<E>,\n inputConfig: ValidateSchema<G, E>,\n options?: CreateConfigOptions<E>,\n ): ResolveConfigGroup<G> & { env: EnvName<E> } =>\n buildConfig<E, G>(env, inputConfig as unknown as G, options)\n}\n\nfunction buildConfig<E extends EnvsShape, G extends ConfigGroup<E>>(\n env: EnvName<E>,\n inputConfig: G,\n options?: CreateConfigOptions<E>,\n): ResolveConfigGroup<G> & { env: EnvName<E> } {\n const errors: string[] = []\n\n // Resolve the per-environment lookup chain once for the active env.\n // Throws synchronously on a circular fallback chain.\n const envChain = resolveFallbackChain<E>(env, options?.fallbacks)\n\n function processConfig(\n config: ConfigGroup<E>,\n keyPrefix: string,\n ): Record<string, any> {\n const output: Record<string, any> = {}\n\n for (const [key, entry] of Object.entries(config)) {\n if (key === \"env\") {\n throw new Error(\n `Config key \"env\" is reserved and cannot be used. It will already be present by default.`,\n )\n }\n\n const fullKey = keyPrefix ? `${keyPrefix}.${key}` : key\n\n if (!(\"doc\" in entry)) {\n output[key] = processConfig(entry as ConfigGroup<E>, fullKey)\n continue\n }\n\n const configEntry = entry as any\n\n // Value resolution — sources are evaluated in ascending priority order.\n // The highest-priority source that resolves to a defined value wins.\n //\n // Priority │ Source\n // ─────────┼────────────────────────────────────────────────────────────────\n // 1 (low) │ Static `value` — same value across all environments\n // 2 │ Per-environment field, walking the fallback chain\n // │ — overrides the static value for that specific environment\n // 3 (high) │ Runtime env var via `processEnv` or `importMetaEnv`\n // │ — always wins; intended for secrets and local dev overrides\n\n // Priority 1: static value (lowest precedence)\n let value: any = \"value\" in configEntry ? configEntry.value : undefined\n\n // Priority 2: per-environment value, walking the fallback chain.\n // The first env in the chain with a defined value wins.\n for (const candidateEnv of envChain) {\n const envValue = configEntry[candidateEnv]\n if (envValue !== undefined) {\n value = envValue\n break\n }\n }\n\n // Priority 3: runtime env var (highest precedence — always wins when defined)\n if (\"processEnv\" in configEntry) {\n const runtimeOverride =\n // @ts-expect-error process may not be defined in browser builds\n typeof process !== \"undefined\" && process.env\n ? // @ts-expect-error process may not be defined in browser builds\n process.env[configEntry.processEnv as string]\n : undefined\n if (runtimeOverride !== undefined) value = runtimeOverride\n } else if (\"importMetaEnv\" in configEntry) {\n const runtimeOverride =\n // @ts-expect-error import.meta.env may not be defined in Node builds\n typeof import.meta !== \"undefined\" && import.meta.env\n ? // @ts-expect-error import.meta.env may not be defined in Node builds\n import.meta.env[configEntry.importMetaEnv as string]\n : undefined\n if (runtimeOverride !== undefined) value = runtimeOverride\n }\n\n const hasValueSource =\n value !== undefined ||\n \"processEnv\" in configEntry ||\n \"importMetaEnv\" in configEntry ||\n configEntry.optional\n\n if (value === undefined && !hasValueSource) {\n errors.push(\n `${fullKey}: No value source declared and \"optional\" is not set.`,\n )\n continue\n }\n\n if (value === undefined) {\n if (configEntry.optional) {\n value = configEntry.default\n if (value === undefined) {\n output[key] = undefined\n continue\n }\n } else {\n errors.push(\n `${fullKey}: Missing required config value in environment ${env}`,\n )\n continue\n }\n }\n\n //\n // Format validation and coercion\n //\n value = validateAndCoerce(value, configEntry.format, fullKey, errors)\n\n output[key] = value\n }\n\n return output\n }\n\n let outputConfig = processConfig(inputConfig, \"\")\n\n if (errors.length > 0) {\n console.error(\"Environment config validation failed\", errors)\n throw new Error(\n `Environment config validation failed:\\n${errors.join(\"\\n\")}`,\n )\n }\n\n outputConfig = {\n env,\n ...outputConfig,\n }\n return outputConfig as ResolveConfigGroup<G> & { env: EnvName<E> }\n}\n\n/**\n * Build the ordered list of environments to consult for per-environment\n * value resolution. The active env is always first; each subsequent entry\n * is the fallback target declared for the previous env. Throws if the\n * chain is cyclic.\n */\nfunction resolveFallbackChain<E extends EnvsShape>(\n env: EnvName<E>,\n fallbacks: Fallbacks<E> | undefined,\n): EnvName<E>[] {\n const chain: EnvName<E>[] = [env]\n if (!fallbacks) return chain\n\n const seen = new Set<string>([env])\n let current: EnvName<E> = env\n while (fallbacks[current] !== undefined) {\n const next = fallbacks[current] as EnvName<E>\n if (seen.has(next)) {\n throw new Error(\n `Circular fallback chain detected: ${[...chain, next].join(\" -> \")}`,\n )\n }\n seen.add(next)\n chain.push(next)\n current = next\n }\n return chain\n}\n","import { createEnvironmentConfig } from \"./create-config.js\"\nimport type {\n ConfigGroup,\n ResolveConfigGroup,\n ValidateSchema,\n} from \"./types.js\"\nimport type { CreateConfigOptions, EnvName, EnvsShape } from \"./util-types.js\"\n\n/**\n * Like {@link createEnvironmentConfig}, but binds the schema first and the\n * environment later. Useful when the active environment is not known at\n * schema-definition time.\n *\n * @typeParam E - The envs shape describing required/optional environments.\n *\n * @example\n * ```ts\n * type MyEnvs = {\n * dev?: unknown\n * staging: unknown\n * production: unknown\n * }\n * const buildConfig = defineEnvironmentConfig<MyEnvs>()({\n * port: { doc: 'Port', format: Number, value: 3000 },\n * })\n * const config = buildConfig('dev')\n * ```\n *\n * @example Fallback environments\n * ```ts\n * const buildConfig = defineEnvironmentConfig<MyEnvs>()(\n * { apiUrl: { doc: 'API URL', format: 'url', staging: 'https://staging' } },\n * { fallbacks: { dev: 'staging' } },\n * )\n * const config = buildConfig('dev') // apiUrl resolved from `staging`\n * ```\n */\nexport function defineEnvironmentConfig<E extends EnvsShape>() {\n const create = createEnvironmentConfig<E>()\n return <const G extends ConfigGroup<E>>(\n inputConfig: ValidateSchema<G, E>,\n options?: CreateConfigOptions<E>,\n ): ((env: EnvName<E>) => ResolveConfigGroup<G> & { env: EnvName<E> }) =>\n (env: EnvName<E>) =>\n create(env, inputConfig as any, options)\n}\n"],"mappings":"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"}
package/dist/index.d.cts CHANGED
@@ -96,6 +96,7 @@ type ValueSourceRequired<T> = {
96
96
  processEnv?: never;
97
97
  importMetaEnv: string;
98
98
  } | {
99
+ optional: true;
99
100
  value?: T;
100
101
  processEnv?: never;
101
102
  importMetaEnv?: never;
@@ -109,19 +110,33 @@ type ConfigEntryBase<T, E extends EnvsShape> = {
109
110
  type ConfigGroup<E extends EnvsShape> = {
110
111
  [key: string]: ConfigEntry<any, E> | ConfigGroup<E>;
111
112
  };
113
+ type MaybeOptionalUndefined<E, T> = E extends {
114
+ optional: true;
115
+ } ? E extends {
116
+ default: any;
117
+ } ? T : T | undefined : T;
118
+ type ReservedEntryKeys = "doc" | "format" | "optional" | "default" | "value" | "processEnv" | "importMetaEnv";
119
+ type NeverToAny<T> = [T] extends [never] ? any : T;
120
+ type UntypedResolved<E> = NeverToAny<(E extends {
121
+ value: infer V;
122
+ } ? V : never) | (E extends {
123
+ default: infer D;
124
+ } ? D : never) | (E extends Record<string, any> ? E[Exclude<keyof E, ReservedEntryKeys>] : never)>;
125
+ type WideElement<T> = T extends string ? string : T extends number ? number : T extends boolean ? boolean : T;
126
+ type ArrayElementType<E> = UntypedResolved<E> extends readonly (infer Item)[] ? WideElement<Item> : any;
112
127
  type ResolveEntryType<E> = E extends {
113
128
  format: StringConstructor;
114
- } ? string : E extends {
129
+ } ? MaybeOptionalUndefined<E, string> : E extends {
115
130
  format: NumberConstructor;
116
- } ? number : E extends {
131
+ } ? MaybeOptionalUndefined<E, number> : E extends {
117
132
  format: BooleanConstructor;
118
- } ? boolean : E extends {
133
+ } ? MaybeOptionalUndefined<E, boolean> : E extends {
119
134
  format: 'url';
120
- } ? string : E extends {
135
+ } ? MaybeOptionalUndefined<E, string> : E extends {
121
136
  format: (infer F)[];
122
- } ? F : E extends {
137
+ } ? MaybeOptionalUndefined<E, F> : E extends {
123
138
  format: ArrayConstructor;
124
- } ? any[] : any;
139
+ } ? MaybeOptionalUndefined<E, ArrayElementType<E>[]> : MaybeOptionalUndefined<E, UntypedResolved<E>>;
125
140
  type ResolveConfigGroup<G> = { [K in keyof G]: G[K] extends {
126
141
  doc: string;
127
142
  } ? ResolveEntryType<G[K]> : ResolveConfigGroup<G[K]> };
@@ -158,6 +173,8 @@ type ValidateSchema<G, E extends EnvsShape> = { [K in keyof G]: G[K] extends {
158
173
  format: readonly (infer V)[];
159
174
  } ? { [P in keyof G[K]]: P extends EnvName<E> | "value" | "default" ? G[K][P] extends V | undefined ? G[K][P] : V : G[K][P] } : G[K] extends {
160
175
  format: unknown;
176
+ } ? G[K] : G[K] extends {
177
+ doc: string;
161
178
  } ? G[K] : ValidateSchema<G[K], E> };
162
179
  //#endregion
163
180
  //#region src/create-config.d.ts
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../src/util-types.ts","../src/types.ts","../src/create-config.ts","../src/define-config.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/util-types.ts","../src/types.ts","../src/create-config.ts","../src/define-config.ts"],"mappings":"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"}
package/dist/index.d.mts CHANGED
@@ -96,6 +96,7 @@ type ValueSourceRequired<T> = {
96
96
  processEnv?: never;
97
97
  importMetaEnv: string;
98
98
  } | {
99
+ optional: true;
99
100
  value?: T;
100
101
  processEnv?: never;
101
102
  importMetaEnv?: never;
@@ -109,19 +110,33 @@ type ConfigEntryBase<T, E extends EnvsShape> = {
109
110
  type ConfigGroup<E extends EnvsShape> = {
110
111
  [key: string]: ConfigEntry<any, E> | ConfigGroup<E>;
111
112
  };
113
+ type MaybeOptionalUndefined<E, T> = E extends {
114
+ optional: true;
115
+ } ? E extends {
116
+ default: any;
117
+ } ? T : T | undefined : T;
118
+ type ReservedEntryKeys = "doc" | "format" | "optional" | "default" | "value" | "processEnv" | "importMetaEnv";
119
+ type NeverToAny<T> = [T] extends [never] ? any : T;
120
+ type UntypedResolved<E> = NeverToAny<(E extends {
121
+ value: infer V;
122
+ } ? V : never) | (E extends {
123
+ default: infer D;
124
+ } ? D : never) | (E extends Record<string, any> ? E[Exclude<keyof E, ReservedEntryKeys>] : never)>;
125
+ type WideElement<T> = T extends string ? string : T extends number ? number : T extends boolean ? boolean : T;
126
+ type ArrayElementType<E> = UntypedResolved<E> extends readonly (infer Item)[] ? WideElement<Item> : any;
112
127
  type ResolveEntryType<E> = E extends {
113
128
  format: StringConstructor;
114
- } ? string : E extends {
129
+ } ? MaybeOptionalUndefined<E, string> : E extends {
115
130
  format: NumberConstructor;
116
- } ? number : E extends {
131
+ } ? MaybeOptionalUndefined<E, number> : E extends {
117
132
  format: BooleanConstructor;
118
- } ? boolean : E extends {
133
+ } ? MaybeOptionalUndefined<E, boolean> : E extends {
119
134
  format: 'url';
120
- } ? string : E extends {
135
+ } ? MaybeOptionalUndefined<E, string> : E extends {
121
136
  format: (infer F)[];
122
- } ? F : E extends {
137
+ } ? MaybeOptionalUndefined<E, F> : E extends {
123
138
  format: ArrayConstructor;
124
- } ? any[] : any;
139
+ } ? MaybeOptionalUndefined<E, ArrayElementType<E>[]> : MaybeOptionalUndefined<E, UntypedResolved<E>>;
125
140
  type ResolveConfigGroup<G> = { [K in keyof G]: G[K] extends {
126
141
  doc: string;
127
142
  } ? ResolveEntryType<G[K]> : ResolveConfigGroup<G[K]> };
@@ -158,6 +173,8 @@ type ValidateSchema<G, E extends EnvsShape> = { [K in keyof G]: G[K] extends {
158
173
  format: readonly (infer V)[];
159
174
  } ? { [P in keyof G[K]]: P extends EnvName<E> | "value" | "default" ? G[K][P] extends V | undefined ? G[K][P] : V : G[K][P] } : G[K] extends {
160
175
  format: unknown;
176
+ } ? G[K] : G[K] extends {
177
+ doc: string;
161
178
  } ? G[K] : ValidateSchema<G[K], E> };
162
179
  //#endregion
163
180
  //#region src/create-config.d.ts
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/util-types.ts","../src/types.ts","../src/create-config.ts","../src/define-config.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/util-types.ts","../src/types.ts","../src/create-config.ts","../src/define-config.ts"],"mappings":"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"}
package/dist/index.mjs CHANGED
@@ -2,28 +2,28 @@
2
2
  function validateAndCoerce(value, format, fullKey, errors) {
3
3
  switch (format) {
4
4
  case String:
5
- if (typeof value !== "string") errors.push(`Config value for ${fullKey} must be a string`);
5
+ if (typeof value !== "string") errors.push(`${fullKey}: must be a string`);
6
6
  break;
7
7
  case Number:
8
8
  value = Number(value);
9
- if (isNaN(value)) errors.push(`Config value for ${fullKey} must be a number`);
9
+ if (isNaN(value)) errors.push(`${fullKey}: must be a number`);
10
10
  break;
11
11
  case Boolean:
12
- if (typeof value !== "boolean" && value !== "true" && value !== "false" && value !== 1 && value !== 0) errors.push(`Config value for ${fullKey} must be a boolean`);
12
+ if (typeof value !== "boolean" && value !== "true" && value !== "false" && value !== 1 && value !== 0) errors.push(`${fullKey}: must be a boolean`);
13
13
  value = value === "true" ? true : value === "false" ? false : Boolean(value);
14
14
  break;
15
15
  case Array:
16
- if (!Array.isArray(value)) errors.push(`Config value for ${fullKey} must be an array`);
16
+ if (!Array.isArray(value)) errors.push(`${fullKey}: must be an array`);
17
17
  break;
18
18
  case "url":
19
19
  try {
20
20
  new URL(value);
21
21
  } catch {
22
- errors.push(`Config value for ${fullKey} must be a valid URL; found "${value}"`);
22
+ errors.push(`${fullKey}: must be a valid URL; found "${value}"`);
23
23
  }
24
24
  break;
25
25
  default: if (format instanceof Array) {
26
- if (!format.includes(value)) errors.push(`Config value for ${fullKey} must be one of: [${format.join(", ")}]`);
26
+ if (!format.includes(value)) errors.push(`${fullKey}: must be one of: [${format.join(", ")}]`);
27
27
  }
28
28
  }
29
29
  return value;
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/format.ts","../src/create-config.ts","../src/define-config.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\n\nexport function validateAndCoerce(\n value: any,\n format: any,\n fullKey: string,\n errors: string[],\n): any {\n switch (format) {\n case String:\n if (typeof value !== \"string\") {\n errors.push(`Config value for ${fullKey} must be a string`)\n }\n break\n case Number:\n value = Number(value)\n if (isNaN(value))\n errors.push(`Config value for ${fullKey} must be a number`)\n break\n case Boolean:\n if (\n typeof value !== \"boolean\" &&\n value !== \"true\" &&\n value !== \"false\" &&\n value !== 1 &&\n value !== 0\n ) {\n errors.push(`Config value for ${fullKey} must be a boolean`)\n }\n value =\n value === \"true\" ? true : value === \"false\" ? false : Boolean(value)\n break\n case Array:\n if (!Array.isArray(value)) {\n errors.push(`Config value for ${fullKey} must be an array`)\n }\n break\n case \"url\":\n try {\n new URL(value)\n } catch {\n errors.push(\n `Config value for ${fullKey} must be a valid URL; found \"${value}\"`,\n )\n }\n break\n default:\n if (format instanceof Array) {\n if (!format.includes(value)) {\n errors.push(\n `Config value for ${fullKey} must be one of: [${format.join(\", \")}]`,\n )\n }\n }\n }\n\n return value\n}\n","/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport { validateAndCoerce } from \"./format.js\"\nimport type {\n ConfigGroup,\n ResolveConfigGroup,\n ValidateSchema,\n} from \"./types.js\"\nimport type {\n CreateConfigOptions,\n EnvName,\n EnvsShape,\n Fallbacks,\n} from \"./util-types.js\"\n\n/**\n * Create a resolved, validated config for the given environment.\n *\n * Curried so the envs declaration is bound on the first call and the\n * schema is inferred (giving you autocomplete) on the second call.\n *\n * @typeParam E - The envs shape describing required/optional environments.\n *\n * @example\n * ```ts\n * type MyEnvs = {\n * dev?: unknown\n * staging: unknown\n * production: unknown\n * }\n * const config = createEnvironmentConfig<MyEnvs>()('dev', {\n * port: { doc: 'Port', format: Number, value: 3000 },\n * })\n * config.port // number\n * ```\n *\n * @example Fallback environments\n * ```ts\n * // When running in `dev`, any entry that does not declare a `dev` value\n * // falls back to the entry's `integ` value.\n * const config = createEnvironmentConfig<MyEnvs>()(\n * 'dev',\n * {\n * apiUrl: {\n * doc: 'API URL',\n * format: 'url',\n * integ: 'https://integ.example.com',\n * staging: 'https://staging.example.com',\n * production: 'https://api.example.com',\n * },\n * },\n * { fallbacks: { dev: 'integ' } },\n * )\n * ```\n */\nexport function createEnvironmentConfig<E extends EnvsShape>() {\n return <const G extends ConfigGroup<E>>(\n env: EnvName<E>,\n inputConfig: ValidateSchema<G, E>,\n options?: CreateConfigOptions<E>,\n ): ResolveConfigGroup<G> & { env: EnvName<E> } =>\n buildConfig<E, G>(env, inputConfig as unknown as G, options)\n}\n\nfunction buildConfig<E extends EnvsShape, G extends ConfigGroup<E>>(\n env: EnvName<E>,\n inputConfig: G,\n options?: CreateConfigOptions<E>,\n): ResolveConfigGroup<G> & { env: EnvName<E> } {\n const errors: string[] = []\n\n // Resolve the per-environment lookup chain once for the active env.\n // Throws synchronously on a circular fallback chain.\n const envChain = resolveFallbackChain<E>(env, options?.fallbacks)\n\n function processConfig(\n config: ConfigGroup<E>,\n keyPrefix: string,\n ): Record<string, any> {\n const output: Record<string, any> = {}\n\n for (const [key, entry] of Object.entries(config)) {\n if (key === \"env\") {\n throw new Error(\n `Config key \"env\" is reserved and cannot be used. It will already be present by default.`,\n )\n }\n\n const fullKey = keyPrefix ? `${keyPrefix}.${key}` : key\n\n if (!(\"doc\" in entry)) {\n output[key] = processConfig(entry as ConfigGroup<E>, fullKey)\n continue\n }\n\n const configEntry = entry as any\n\n // Value resolution — sources are evaluated in ascending priority order.\n // The highest-priority source that resolves to a defined value wins.\n //\n // Priority │ Source\n // ─────────┼────────────────────────────────────────────────────────────────\n // 1 (low) │ Static `value` — same value across all environments\n // 2 │ Per-environment field, walking the fallback chain\n // │ — overrides the static value for that specific environment\n // 3 (high) │ Runtime env var via `processEnv` or `importMetaEnv`\n // │ — always wins; intended for secrets and local dev overrides\n\n // Priority 1: static value (lowest precedence)\n let value: any = \"value\" in configEntry ? configEntry.value : undefined\n\n // Priority 2: per-environment value, walking the fallback chain.\n // The first env in the chain with a defined value wins.\n for (const candidateEnv of envChain) {\n const envValue = configEntry[candidateEnv]\n if (envValue !== undefined) {\n value = envValue\n break\n }\n }\n\n // Priority 3: runtime env var (highest precedence — always wins when defined)\n if (\"processEnv\" in configEntry) {\n const runtimeOverride =\n // @ts-expect-error process may not be defined in browser builds\n typeof process !== \"undefined\" && process.env\n ? // @ts-expect-error process may not be defined in browser builds\n process.env[configEntry.processEnv as string]\n : undefined\n if (runtimeOverride !== undefined) value = runtimeOverride\n } else if (\"importMetaEnv\" in configEntry) {\n const runtimeOverride =\n // @ts-expect-error import.meta.env may not be defined in Node builds\n typeof import.meta !== \"undefined\" && import.meta.env\n ? // @ts-expect-error import.meta.env may not be defined in Node builds\n import.meta.env[configEntry.importMetaEnv as string]\n : undefined\n if (runtimeOverride !== undefined) value = runtimeOverride\n }\n\n const hasValueSource =\n value !== undefined ||\n \"processEnv\" in configEntry ||\n \"importMetaEnv\" in configEntry ||\n configEntry.optional\n\n if (value === undefined && !hasValueSource) {\n errors.push(\n `${fullKey}: No value source declared and \"optional\" is not set.`,\n )\n continue\n }\n\n if (value === undefined) {\n if (configEntry.optional) {\n value = configEntry.default\n if (value === undefined) {\n output[key] = undefined\n continue\n }\n } else {\n errors.push(\n `${fullKey}: Missing required config value in environment ${env}`,\n )\n continue\n }\n }\n\n //\n // Format validation and coercion\n //\n value = validateAndCoerce(value, configEntry.format, fullKey, errors)\n\n output[key] = value\n }\n\n return output\n }\n\n let outputConfig = processConfig(inputConfig, \"\")\n\n if (errors.length > 0) {\n console.error(\"Environment config validation failed\", errors)\n throw new Error(\n `Environment config validation failed:\\n${errors.join(\"\\n\")}`,\n )\n }\n\n outputConfig = {\n env,\n ...outputConfig,\n }\n return outputConfig as ResolveConfigGroup<G> & { env: EnvName<E> }\n}\n\n/**\n * Build the ordered list of environments to consult for per-environment\n * value resolution. The active env is always first; each subsequent entry\n * is the fallback target declared for the previous env. Throws if the\n * chain is cyclic.\n */\nfunction resolveFallbackChain<E extends EnvsShape>(\n env: EnvName<E>,\n fallbacks: Fallbacks<E> | undefined,\n): EnvName<E>[] {\n const chain: EnvName<E>[] = [env]\n if (!fallbacks) return chain\n\n const seen = new Set<string>([env])\n let current: EnvName<E> = env\n while (fallbacks[current] !== undefined) {\n const next = fallbacks[current] as EnvName<E>\n if (seen.has(next)) {\n throw new Error(\n `Circular fallback chain detected: ${[...chain, next].join(\" -> \")}`,\n )\n }\n seen.add(next)\n chain.push(next)\n current = next\n }\n return chain\n}\n","import { createEnvironmentConfig } from \"./create-config.js\"\nimport type {\n ConfigGroup,\n ResolveConfigGroup,\n ValidateSchema,\n} from \"./types.js\"\nimport type { CreateConfigOptions, EnvName, EnvsShape } from \"./util-types.js\"\n\n/**\n * Like {@link createEnvironmentConfig}, but binds the schema first and the\n * environment later. Useful when the active environment is not known at\n * schema-definition time.\n *\n * @typeParam E - The envs shape describing required/optional environments.\n *\n * @example\n * ```ts\n * type MyEnvs = {\n * dev?: unknown\n * staging: unknown\n * production: unknown\n * }\n * const buildConfig = defineEnvironmentConfig<MyEnvs>()({\n * port: { doc: 'Port', format: Number, value: 3000 },\n * })\n * const config = buildConfig('dev')\n * ```\n *\n * @example Fallback environments\n * ```ts\n * const buildConfig = defineEnvironmentConfig<MyEnvs>()(\n * { apiUrl: { doc: 'API URL', format: 'url', staging: 'https://staging' } },\n * { fallbacks: { dev: 'staging' } },\n * )\n * const config = buildConfig('dev') // apiUrl resolved from `staging`\n * ```\n */\nexport function defineEnvironmentConfig<E extends EnvsShape>() {\n const create = createEnvironmentConfig<E>()\n return <const G extends ConfigGroup<E>>(\n inputConfig: ValidateSchema<G, E>,\n options?: CreateConfigOptions<E>,\n ): ((env: EnvName<E>) => ResolveConfigGroup<G> & { env: EnvName<E> }) =>\n (env: EnvName<E>) =>\n create(env, inputConfig as any, options)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/format.ts","../src/create-config.ts","../src/define-config.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\n\nexport function validateAndCoerce(\n value: any,\n format: any,\n fullKey: string,\n errors: string[],\n): any {\n switch (format) {\n case String:\n if (typeof value !== \"string\") {\n errors.push(`${fullKey}: must be a string`)\n }\n break\n case Number:\n value = Number(value)\n if (isNaN(value)) errors.push(`${fullKey}: must be a number`)\n break\n case Boolean:\n if (\n typeof value !== \"boolean\" &&\n value !== \"true\" &&\n value !== \"false\" &&\n value !== 1 &&\n value !== 0\n ) {\n errors.push(`${fullKey}: must be a boolean`)\n }\n value =\n value === \"true\" ? true : value === \"false\" ? false : Boolean(value)\n break\n case Array:\n if (!Array.isArray(value)) {\n errors.push(`${fullKey}: must be an array`)\n }\n break\n case \"url\":\n try {\n new URL(value)\n } catch {\n errors.push(`${fullKey}: must be a valid URL; found \"${value}\"`)\n }\n break\n default:\n if (format instanceof Array) {\n if (!format.includes(value)) {\n errors.push(`${fullKey}: must be one of: [${format.join(\", \")}]`)\n }\n }\n }\n\n return value\n}\n","/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport { validateAndCoerce } from \"./format.js\"\nimport type {\n ConfigGroup,\n ResolveConfigGroup,\n ValidateSchema,\n} from \"./types.js\"\nimport type {\n CreateConfigOptions,\n EnvName,\n EnvsShape,\n Fallbacks,\n} from \"./util-types.js\"\n\n/**\n * Create a resolved, validated config for the given environment.\n *\n * Curried so the envs declaration is bound on the first call and the\n * schema is inferred (giving you autocomplete) on the second call.\n *\n * @typeParam E - The envs shape describing required/optional environments.\n *\n * @example\n * ```ts\n * type MyEnvs = {\n * dev?: unknown\n * staging: unknown\n * production: unknown\n * }\n * const config = createEnvironmentConfig<MyEnvs>()('dev', {\n * port: { doc: 'Port', format: Number, value: 3000 },\n * })\n * config.port // number\n * ```\n *\n * @example Fallback environments\n * ```ts\n * // When running in `dev`, any entry that does not declare a `dev` value\n * // falls back to the entry's `integ` value.\n * const config = createEnvironmentConfig<MyEnvs>()(\n * 'dev',\n * {\n * apiUrl: {\n * doc: 'API URL',\n * format: 'url',\n * integ: 'https://integ.example.com',\n * staging: 'https://staging.example.com',\n * production: 'https://api.example.com',\n * },\n * },\n * { fallbacks: { dev: 'integ' } },\n * )\n * ```\n */\nexport function createEnvironmentConfig<E extends EnvsShape>() {\n return <const G extends ConfigGroup<E>>(\n env: EnvName<E>,\n inputConfig: ValidateSchema<G, E>,\n options?: CreateConfigOptions<E>,\n ): ResolveConfigGroup<G> & { env: EnvName<E> } =>\n buildConfig<E, G>(env, inputConfig as unknown as G, options)\n}\n\nfunction buildConfig<E extends EnvsShape, G extends ConfigGroup<E>>(\n env: EnvName<E>,\n inputConfig: G,\n options?: CreateConfigOptions<E>,\n): ResolveConfigGroup<G> & { env: EnvName<E> } {\n const errors: string[] = []\n\n // Resolve the per-environment lookup chain once for the active env.\n // Throws synchronously on a circular fallback chain.\n const envChain = resolveFallbackChain<E>(env, options?.fallbacks)\n\n function processConfig(\n config: ConfigGroup<E>,\n keyPrefix: string,\n ): Record<string, any> {\n const output: Record<string, any> = {}\n\n for (const [key, entry] of Object.entries(config)) {\n if (key === \"env\") {\n throw new Error(\n `Config key \"env\" is reserved and cannot be used. It will already be present by default.`,\n )\n }\n\n const fullKey = keyPrefix ? `${keyPrefix}.${key}` : key\n\n if (!(\"doc\" in entry)) {\n output[key] = processConfig(entry as ConfigGroup<E>, fullKey)\n continue\n }\n\n const configEntry = entry as any\n\n // Value resolution — sources are evaluated in ascending priority order.\n // The highest-priority source that resolves to a defined value wins.\n //\n // Priority │ Source\n // ─────────┼────────────────────────────────────────────────────────────────\n // 1 (low) │ Static `value` — same value across all environments\n // 2 │ Per-environment field, walking the fallback chain\n // │ — overrides the static value for that specific environment\n // 3 (high) │ Runtime env var via `processEnv` or `importMetaEnv`\n // │ — always wins; intended for secrets and local dev overrides\n\n // Priority 1: static value (lowest precedence)\n let value: any = \"value\" in configEntry ? configEntry.value : undefined\n\n // Priority 2: per-environment value, walking the fallback chain.\n // The first env in the chain with a defined value wins.\n for (const candidateEnv of envChain) {\n const envValue = configEntry[candidateEnv]\n if (envValue !== undefined) {\n value = envValue\n break\n }\n }\n\n // Priority 3: runtime env var (highest precedence — always wins when defined)\n if (\"processEnv\" in configEntry) {\n const runtimeOverride =\n // @ts-expect-error process may not be defined in browser builds\n typeof process !== \"undefined\" && process.env\n ? // @ts-expect-error process may not be defined in browser builds\n process.env[configEntry.processEnv as string]\n : undefined\n if (runtimeOverride !== undefined) value = runtimeOverride\n } else if (\"importMetaEnv\" in configEntry) {\n const runtimeOverride =\n // @ts-expect-error import.meta.env may not be defined in Node builds\n typeof import.meta !== \"undefined\" && import.meta.env\n ? // @ts-expect-error import.meta.env may not be defined in Node builds\n import.meta.env[configEntry.importMetaEnv as string]\n : undefined\n if (runtimeOverride !== undefined) value = runtimeOverride\n }\n\n const hasValueSource =\n value !== undefined ||\n \"processEnv\" in configEntry ||\n \"importMetaEnv\" in configEntry ||\n configEntry.optional\n\n if (value === undefined && !hasValueSource) {\n errors.push(\n `${fullKey}: No value source declared and \"optional\" is not set.`,\n )\n continue\n }\n\n if (value === undefined) {\n if (configEntry.optional) {\n value = configEntry.default\n if (value === undefined) {\n output[key] = undefined\n continue\n }\n } else {\n errors.push(\n `${fullKey}: Missing required config value in environment ${env}`,\n )\n continue\n }\n }\n\n //\n // Format validation and coercion\n //\n value = validateAndCoerce(value, configEntry.format, fullKey, errors)\n\n output[key] = value\n }\n\n return output\n }\n\n let outputConfig = processConfig(inputConfig, \"\")\n\n if (errors.length > 0) {\n console.error(\"Environment config validation failed\", errors)\n throw new Error(\n `Environment config validation failed:\\n${errors.join(\"\\n\")}`,\n )\n }\n\n outputConfig = {\n env,\n ...outputConfig,\n }\n return outputConfig as ResolveConfigGroup<G> & { env: EnvName<E> }\n}\n\n/**\n * Build the ordered list of environments to consult for per-environment\n * value resolution. The active env is always first; each subsequent entry\n * is the fallback target declared for the previous env. Throws if the\n * chain is cyclic.\n */\nfunction resolveFallbackChain<E extends EnvsShape>(\n env: EnvName<E>,\n fallbacks: Fallbacks<E> | undefined,\n): EnvName<E>[] {\n const chain: EnvName<E>[] = [env]\n if (!fallbacks) return chain\n\n const seen = new Set<string>([env])\n let current: EnvName<E> = env\n while (fallbacks[current] !== undefined) {\n const next = fallbacks[current] as EnvName<E>\n if (seen.has(next)) {\n throw new Error(\n `Circular fallback chain detected: ${[...chain, next].join(\" -> \")}`,\n )\n }\n seen.add(next)\n chain.push(next)\n current = next\n }\n return chain\n}\n","import { createEnvironmentConfig } from \"./create-config.js\"\nimport type {\n ConfigGroup,\n ResolveConfigGroup,\n ValidateSchema,\n} from \"./types.js\"\nimport type { CreateConfigOptions, EnvName, EnvsShape } from \"./util-types.js\"\n\n/**\n * Like {@link createEnvironmentConfig}, but binds the schema first and the\n * environment later. Useful when the active environment is not known at\n * schema-definition time.\n *\n * @typeParam E - The envs shape describing required/optional environments.\n *\n * @example\n * ```ts\n * type MyEnvs = {\n * dev?: unknown\n * staging: unknown\n * production: unknown\n * }\n * const buildConfig = defineEnvironmentConfig<MyEnvs>()({\n * port: { doc: 'Port', format: Number, value: 3000 },\n * })\n * const config = buildConfig('dev')\n * ```\n *\n * @example Fallback environments\n * ```ts\n * const buildConfig = defineEnvironmentConfig<MyEnvs>()(\n * { apiUrl: { doc: 'API URL', format: 'url', staging: 'https://staging' } },\n * { fallbacks: { dev: 'staging' } },\n * )\n * const config = buildConfig('dev') // apiUrl resolved from `staging`\n * ```\n */\nexport function defineEnvironmentConfig<E extends EnvsShape>() {\n const create = createEnvironmentConfig<E>()\n return <const G extends ConfigGroup<E>>(\n inputConfig: ValidateSchema<G, E>,\n options?: CreateConfigOptions<E>,\n ): ((env: EnvName<E>) => ResolveConfigGroup<G> & { env: EnvName<E> }) =>\n (env: EnvName<E>) =>\n create(env, inputConfig as any, options)\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "konfeeg",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "Build a validated, strongly-typed config object, and use it everywhere.",
5
5
  "author": "Zach Olivare <https://github.com/0livare>",
6
6
  "license": "ISC",