konfeeg 0.0.0 → 0.0.1

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
@@ -2,7 +2,11 @@
2
2
 
3
3
  Validated, strongly-typed config for Node and the browser. Define a schema once; values are resolved, coerced, and validated at startup — missing or invalid values throw immediately.
4
4
 
5
- > **Note:** Throws at startup if any required value is missing or fails format validation — broken `.env` files surface immediately, not at runtime.
5
+ > API is inspired by [convict](https://github.com/mozilla/node-convict/tree/master/packages/convict), but this package:
6
+ >
7
+ > - works in the browser (as well as node)
8
+ > - supports environment-specific values without requiring use of global env vars
9
+ > - supports `import.meta.env` (e.g. for Vite)
6
10
 
7
11
  ---
8
12
 
@@ -11,7 +15,7 @@ Validated, strongly-typed config for Node and the browser. Define a schema once;
11
15
  ```ts
12
16
  import { createEnvironmentConfig } from "konfeeg"
13
17
 
14
- // 1. Declare your environments (required vs optional)
18
+ // 1. Declare the names of your environments
15
19
  type MyEnvs = {
16
20
  dev?: unknown // optional (?) = per-env value may be omitted
17
21
  staging: unknown // required = must supply a value
@@ -28,6 +32,26 @@ const config = createEnvironmentConfig<MyEnvs>()("staging", {
28
32
  staging: "https://staging-api.example.com",
29
33
  production: "https://api.example.com",
30
34
  },
35
+ logLevel: {
36
+ doc: "Minimum log level",
37
+ format: ["debug", "info", "warn", "error"] as const, // Must be one of these literals
38
+ processEnv: "LOG_LEVEL",
39
+ dev: "debug",
40
+ staging: "info",
41
+ production: "warn",
42
+ },
43
+ port: {
44
+ doc: "HTTP port to listen on",
45
+ format: Number, // Numeric strings (e.g. from env vars) are coerced
46
+ processEnv: "PORT",
47
+ value: 3000,
48
+ },
49
+ allowedOrigins: {
50
+ doc: "CORS allow-list",
51
+ format: Array, // Value must be an array
52
+ staging: ["https://staging.example.com"],
53
+ production: ["https://example.com", "https://admin.example.com"],
54
+ },
31
55
  mongo: {
32
56
  dbName: {
33
57
  doc: "Mongo database name",
@@ -35,34 +59,56 @@ const config = createEnvironmentConfig<MyEnvs>()("staging", {
35
59
  processEnv: "MONGO_DB_NAME",
36
60
  value: "my-app-db", // static fallback (lowest priority)
37
61
  },
38
- password: {
39
- doc: "Mongo database password",
40
- format: String,
41
- // runtime-only, no static fallback
42
- importMetaEnv: "MONGO_PASSWORD", // uses import.meta.env instead of process.env (e.g. for Vite)
62
+ poolSize: {
63
+ doc: "Max connections in the Mongo pool",
64
+ format: Number,
65
+ optional: true, // missing value resolves to `default` instead of throwing
66
+ default: 10,
43
67
  },
44
68
  },
45
69
  })
46
70
 
47
71
  config.env // "staging"
48
72
  config.apiUrl // string (validated as URL)
73
+ config.logLevel // "debug" | "info" | "warn" | "error"
74
+ config.port // number
75
+ config.allowedOrigins // any[]
49
76
  config.mongo.dbName // string
77
+ config.mongo.poolSize // number
50
78
  ```
51
79
 
80
+ > [!important]
81
+ > The above example hardcodes the active environment (`"staging"`) for clarity, but you'll want that to be dynamic in a real app:
82
+ >
83
+ > ```ts
84
+ > type WhichEnvsAreRequired = {
85
+ > local?: unknown // optional (?) = per-env value may be omitted
86
+ > nonprod: unknown // required = must supply a value
87
+ > prod: unknown
88
+ > }
89
+ >
90
+ > // type AppEnvironment = keyof WhichEnvsAreRequired // "local" | "nonprod" | "prod"
91
+ >
92
+ > const appEnv = import.meta.env.VITE_APP_ENV as AppEnvironment
93
+ > if (!appEnv) throw new Error("VITE_APP_ENV is required")
94
+ >
95
+ > const config = createEnvironmentConfig<WhichEnvsAreRequired>()(appEnv, { ... })
96
+ > ```
97
+
52
98
  ---
53
99
 
54
100
  ## Schema fields
55
101
 
56
- | Field | Required | Description |
57
- | ---------------------------- | -------- | ---------------------------------------------------------------------- |
58
- | `doc` | required | Human-readable description |
59
- | `format` | required | Validation format — see below |
60
- | `value` | optional | Constant shared across all environments (lowest priority) |
61
- | `processEnv` | optional | `process.env` key — runtime override (highest priority) |
62
- | `importMetaEnv` | optional | `import.meta.env` key — runtime override (highest priority) |
63
- | `optional` | optional | When `true`, missing value resolves to `undefined` instead of throwing |
64
- | `default` | optional | Fallback when `optional: true` and no value is found |
65
- | env keys (`dev`, `staging`…) | optional | Per-environment value overrides |
102
+ | Field | Required | Description |
103
+ | --------------------------------- | -------- | ------------------------------------------------------------------------------------- |
104
+ | `doc` | required | Human-readable description |
105
+ | `format` | required | Validation format — see below |
106
+ | `value` | optional | Constant shared across all environments (lowest priority) |
107
+ | `processEnv` | optional | `process.env` key — runtime override (highest priority) |
108
+ | `importMetaEnv` | optional | `import.meta.env` key — runtime override (highest priority) |
109
+ | `optional` | optional | When `true`, missing value will not throw. Resolves to `default` field or `undefined` |
110
+ | `default` | optional | Fallback to replace `undefined` when `optional: true` and no value is found |
111
+ | env keys (e.g. `dev`, `staging`…) | optional | Per-environment value overrides. _(These are the env names that you pass in in)_ |
66
112
 
67
113
  ---
68
114
 
@@ -93,21 +139,6 @@ When multiple sources are declared on the same entry, the highest-priority sourc
93
139
 
94
140
  ---
95
141
 
96
- ## Defining environments
97
-
98
- Declare a plain object type. Required properties mean every entry that supplies per-env values must include that env. Optional properties (`?`) may be omitted.
99
-
100
- ```ts
101
- type MyEnvs = {
102
- dev?: unknown // optional — per-env value may be omitted
103
- integ?: unknown
104
- staging: unknown // required — every entry must supply a value
105
- production: unknown
106
- }
107
- ```
108
-
109
- ---
110
-
111
142
  ## Fallbacks
112
143
 
113
144
  When an entry has no value for the active environment, resolution can fall back to another env's value. Chains are transitive. Only affects per-env resolution (priority 2) — runtime env vars still win.
package/dist/index.cjs ADDED
@@ -0,0 +1,195 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region src/format.ts
3
+ function validateAndCoerce(value, format, fullKey, errors) {
4
+ switch (format) {
5
+ case String:
6
+ if (typeof value !== "string") errors.push(`Config value for ${fullKey} must be a string`);
7
+ break;
8
+ case Number:
9
+ value = Number(value);
10
+ if (isNaN(value)) errors.push(`Config value for ${fullKey} must be a number`);
11
+ break;
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`);
14
+ value = value === "true" ? true : value === "false" ? false : Boolean(value);
15
+ break;
16
+ case Array:
17
+ if (!Array.isArray(value)) errors.push(`Config value for ${fullKey} must be an array`);
18
+ break;
19
+ case "url":
20
+ try {
21
+ new URL(value);
22
+ } catch {
23
+ errors.push(`Config value for ${fullKey} must be a valid URL; found "${value}"`);
24
+ }
25
+ break;
26
+ default: if (format instanceof Array) {
27
+ if (!format.includes(value)) errors.push(`Config value for ${fullKey} must be one of: [${format.join(", ")}]`);
28
+ }
29
+ }
30
+ return value;
31
+ }
32
+ //#endregion
33
+ //#region src/create-config.ts
34
+ /**
35
+ * Create a resolved, validated config for the given environment.
36
+ *
37
+ * Curried so the envs declaration is bound on the first call and the
38
+ * schema is inferred (giving you autocomplete) on the second call.
39
+ *
40
+ * @typeParam E - The envs shape describing required/optional environments.
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * type MyEnvs = {
45
+ * dev?: unknown
46
+ * staging: unknown
47
+ * production: unknown
48
+ * }
49
+ * const config = createEnvironmentConfig<MyEnvs>()('dev', {
50
+ * port: { doc: 'Port', format: Number, value: 3000 },
51
+ * })
52
+ * config.port // number
53
+ * ```
54
+ *
55
+ * @example Fallback environments
56
+ * ```ts
57
+ * // When running in `dev`, any entry that does not declare a `dev` value
58
+ * // falls back to the entry's `integ` value.
59
+ * const config = createEnvironmentConfig<MyEnvs>()(
60
+ * 'dev',
61
+ * {
62
+ * apiUrl: {
63
+ * doc: 'API URL',
64
+ * format: 'url',
65
+ * integ: 'https://integ.example.com',
66
+ * staging: 'https://staging.example.com',
67
+ * production: 'https://api.example.com',
68
+ * },
69
+ * },
70
+ * { fallbacks: { dev: 'integ' } },
71
+ * )
72
+ * ```
73
+ */
74
+ function createEnvironmentConfig() {
75
+ return (env, inputConfig, options) => buildConfig(env, inputConfig, options);
76
+ }
77
+ function buildConfig(env, inputConfig, options) {
78
+ const errors = [];
79
+ const envChain = resolveFallbackChain(env, options?.fallbacks);
80
+ function processConfig(config, keyPrefix) {
81
+ const output = {};
82
+ for (const [key, entry] of Object.entries(config)) {
83
+ if (key === "env") throw new Error(`Config key "env" is reserved and cannot be used. It will already be present by default.`);
84
+ const fullKey = keyPrefix ? `${keyPrefix}.${key}` : key;
85
+ if (!("doc" in entry)) {
86
+ output[key] = processConfig(entry, fullKey);
87
+ continue;
88
+ }
89
+ const configEntry = entry;
90
+ let value = "value" in configEntry ? configEntry.value : void 0;
91
+ for (const candidateEnv of envChain) {
92
+ const envValue = configEntry[candidateEnv];
93
+ if (envValue !== void 0) {
94
+ value = envValue;
95
+ break;
96
+ }
97
+ }
98
+ if ("processEnv" in configEntry) {
99
+ const runtimeOverride = typeof process !== "undefined" && process.env ? process.env[configEntry.processEnv] : void 0;
100
+ if (runtimeOverride !== void 0) value = runtimeOverride;
101
+ } else if ("importMetaEnv" in configEntry) {
102
+ const runtimeOverride = {}.env ? {}.env[configEntry.importMetaEnv] : void 0;
103
+ if (runtimeOverride !== void 0) value = runtimeOverride;
104
+ }
105
+ const hasValueSource = value !== void 0 || "processEnv" in configEntry || "importMetaEnv" in configEntry || configEntry.optional;
106
+ if (value === void 0 && !hasValueSource) {
107
+ errors.push(`${fullKey}: No value source declared and "optional" is not set.`);
108
+ continue;
109
+ }
110
+ if (value === void 0) if (configEntry.optional) {
111
+ value = configEntry.default;
112
+ if (value === void 0) {
113
+ output[key] = void 0;
114
+ continue;
115
+ }
116
+ } else {
117
+ errors.push(`${fullKey}: Missing required config value in environment ${env}`);
118
+ continue;
119
+ }
120
+ value = validateAndCoerce(value, configEntry.format, fullKey, errors);
121
+ output[key] = value;
122
+ }
123
+ return output;
124
+ }
125
+ let outputConfig = processConfig(inputConfig, "");
126
+ if (errors.length > 0) {
127
+ console.error("Environment config validation failed", errors);
128
+ throw new Error(`Environment config validation failed:\n${errors.join("\n")}`);
129
+ }
130
+ outputConfig = {
131
+ env,
132
+ ...outputConfig
133
+ };
134
+ return outputConfig;
135
+ }
136
+ /**
137
+ * Build the ordered list of environments to consult for per-environment
138
+ * value resolution. The active env is always first; each subsequent entry
139
+ * is the fallback target declared for the previous env. Throws if the
140
+ * chain is cyclic.
141
+ */
142
+ function resolveFallbackChain(env, fallbacks) {
143
+ const chain = [env];
144
+ if (!fallbacks) return chain;
145
+ const seen = new Set([env]);
146
+ let current = env;
147
+ while (fallbacks[current] !== void 0) {
148
+ const next = fallbacks[current];
149
+ if (seen.has(next)) throw new Error(`Circular fallback chain detected: ${[...chain, next].join(" -> ")}`);
150
+ seen.add(next);
151
+ chain.push(next);
152
+ current = next;
153
+ }
154
+ return chain;
155
+ }
156
+ //#endregion
157
+ //#region src/define-config.ts
158
+ /**
159
+ * Like {@link createEnvironmentConfig}, but binds the schema first and the
160
+ * environment later. Useful when the active environment is not known at
161
+ * schema-definition time.
162
+ *
163
+ * @typeParam E - The envs shape describing required/optional environments.
164
+ *
165
+ * @example
166
+ * ```ts
167
+ * type MyEnvs = {
168
+ * dev?: unknown
169
+ * staging: unknown
170
+ * production: unknown
171
+ * }
172
+ * const buildConfig = defineEnvironmentConfig<MyEnvs>()({
173
+ * port: { doc: 'Port', format: Number, value: 3000 },
174
+ * })
175
+ * const config = buildConfig('dev')
176
+ * ```
177
+ *
178
+ * @example Fallback environments
179
+ * ```ts
180
+ * const buildConfig = defineEnvironmentConfig<MyEnvs>()(
181
+ * { apiUrl: { doc: 'API URL', format: 'url', staging: 'https://staging' } },
182
+ * { fallbacks: { dev: 'staging' } },
183
+ * )
184
+ * const config = buildConfig('dev') // apiUrl resolved from `staging`
185
+ * ```
186
+ */
187
+ function defineEnvironmentConfig() {
188
+ const create = createEnvironmentConfig();
189
+ return (inputConfig, options) => (env) => create(env, inputConfig, options);
190
+ }
191
+ //#endregion
192
+ exports.createEnvironmentConfig = createEnvironmentConfig;
193
+ exports.defineEnvironmentConfig = defineEnvironmentConfig;
194
+
195
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +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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"}
@@ -0,0 +1,243 @@
1
+ //#region src/util-types.d.ts
2
+ /**
3
+ * Declaration of the environments a config supports: an object whose keys
4
+ * are environment names, with required envs as required properties and
5
+ * optional envs as optional properties.
6
+ *
7
+ * The property value type is not used by the library — only the keys and
8
+ * their required/optional status matter — so `unknown` (or any other type)
9
+ * is fine.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * type MyEnvs = {
14
+ * dev?: unknown
15
+ * integ?: unknown
16
+ * staging: unknown
17
+ * production: unknown
18
+ * }
19
+ * ```
20
+ */
21
+ type EnvsShape = Record<string, any>;
22
+ type RequiredKeys<T> = { [K in keyof T]-?: {} extends Pick<T, K> ? never : K }[keyof T] & string;
23
+ type OptionalKeys<T> = { [K in keyof T]-?: {} extends Pick<T, K> ? K : never }[keyof T] & string;
24
+ /** Union of all environment names declared on `E` (required ∪ optional). */
25
+ type EnvName<E extends EnvsShape> = keyof E & string;
26
+ /**
27
+ * Per-environment value shape for a value of type `T`:
28
+ * required envs are required, optional envs are optional.
29
+ */
30
+ type PerEnv<E extends EnvsShape, T> = { [K in RequiredKeys<E>]: T } & { [K in OptionalKeys<E>]?: T };
31
+ /**
32
+ * Map of environment names to a fallback environment name.
33
+ *
34
+ * If a per-environment value is not declared for the active environment on
35
+ * a given config entry, resolution falls back to the value declared for the
36
+ * environment named here. Fallbacks chain transitively until a value is
37
+ * found or the chain ends.
38
+ *
39
+ * Both keys and values must be names declared on the envs shape `E`.
40
+ *
41
+ * @example
42
+ * ```ts
43
+ * type MyEnvs = {
44
+ * dev?: unknown
45
+ * integ?: unknown
46
+ * staging: unknown
47
+ * production: unknown
48
+ * }
49
+ *
50
+ * // When running in `dev`, fall back to `integ`; if `integ` is also
51
+ * // missing a value, fall back to `staging`.
52
+ * const fallbacks: Fallbacks<MyEnvs> = {
53
+ * dev: 'integ',
54
+ * integ: 'staging',
55
+ * }
56
+ * ```
57
+ */
58
+ type Fallbacks<E extends EnvsShape> = Partial<Record<EnvName<E>, EnvName<E>>>;
59
+ /**
60
+ * Options accepted as the third argument to `createEnvironmentConfig` (and
61
+ * the second argument to `defineEnvironmentConfig`).
62
+ */
63
+ type CreateConfigOptions<E extends EnvsShape> = {
64
+ /**
65
+ * Map of environment names to a fallback environment name. See
66
+ * {@link Fallbacks} for details.
67
+ */
68
+ fallbacks?: Fallbacks<E>;
69
+ };
70
+ //#endregion
71
+ //#region src/types.d.ts
72
+ type NoEnvKeys<E extends EnvsShape> = { [K in EnvName<E>]?: never };
73
+ type RuntimeSourceOptional<T> = {
74
+ value?: T;
75
+ processEnv?: never;
76
+ importMetaEnv?: never;
77
+ } | {
78
+ value?: T;
79
+ processEnv: string;
80
+ importMetaEnv?: never;
81
+ } | {
82
+ value?: T;
83
+ processEnv?: never;
84
+ importMetaEnv: string;
85
+ };
86
+ type ValueSourceRequired<T> = {
87
+ value: T;
88
+ processEnv?: never;
89
+ importMetaEnv?: never;
90
+ } | {
91
+ value?: T;
92
+ processEnv: string;
93
+ importMetaEnv?: never;
94
+ } | {
95
+ value?: T;
96
+ processEnv?: never;
97
+ importMetaEnv: string;
98
+ } | {
99
+ value?: T;
100
+ processEnv?: never;
101
+ importMetaEnv?: never;
102
+ default: T;
103
+ };
104
+ type ValueSource<T, E extends EnvsShape> = (PerEnv<E, T> & RuntimeSourceOptional<T>) | (NoEnvKeys<E> & ValueSourceRequired<T>);
105
+ type ConfigEntryBase<T, E extends EnvsShape> = {
106
+ doc: string;
107
+ optional?: boolean;
108
+ } & ValueSource<T, E>;
109
+ type ConfigGroup<E extends EnvsShape> = {
110
+ [key: string]: ConfigEntry<any, E> | ConfigGroup<E>;
111
+ };
112
+ type ResolveEntryType<E> = E extends {
113
+ format: StringConstructor;
114
+ } ? string : E extends {
115
+ format: NumberConstructor;
116
+ } ? number : E extends {
117
+ format: BooleanConstructor;
118
+ } ? boolean : E extends {
119
+ format: 'url';
120
+ } ? string : E extends {
121
+ format: (infer F)[];
122
+ } ? F : E extends {
123
+ format: ArrayConstructor;
124
+ } ? any[] : any;
125
+ type ResolveConfigGroup<G> = { [K in keyof G]: G[K] extends {
126
+ doc: string;
127
+ } ? ResolveEntryType<G[K]> : ResolveConfigGroup<G[K]> };
128
+ type ConfigEntry<T, E extends EnvsShape> = UntypedEntry<E> | StringEntry<E> | NumberEntry<E> | BooleanEntry<E> | ArrayEntry<T, E> | EnumEntry<T, E> | UrlEntry<E>;
129
+ type StringEntry<E extends EnvsShape> = ConfigEntryBase<string, E> & {
130
+ format: StringConstructor;
131
+ default?: string;
132
+ };
133
+ type NumberEntry<E extends EnvsShape> = ConfigEntryBase<number, E> & {
134
+ format: NumberConstructor;
135
+ default?: number;
136
+ };
137
+ type BooleanEntry<E extends EnvsShape> = ConfigEntryBase<boolean, E> & {
138
+ format: BooleanConstructor;
139
+ default?: boolean;
140
+ };
141
+ type ArrayEntry<T, E extends EnvsShape> = ConfigEntryBase<T[], E> & {
142
+ format: ArrayConstructor;
143
+ default?: T[];
144
+ };
145
+ type EnumEntry<T, E extends EnvsShape> = ConfigEntryBase<T, E> & {
146
+ format: T[];
147
+ default?: T;
148
+ };
149
+ type UrlEntry<E extends EnvsShape> = ConfigEntryBase<string, E> & {
150
+ format: "url";
151
+ default?: string;
152
+ };
153
+ type UntypedEntry<E extends EnvsShape> = ConfigEntryBase<any, E> & {
154
+ format?: never;
155
+ default?: any;
156
+ };
157
+ type ValidateSchema<G, E extends EnvsShape> = { [K in keyof G]: G[K] extends {
158
+ format: readonly (infer V)[];
159
+ } ? { [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
+ format: unknown;
161
+ } ? G[K] : ValidateSchema<G[K], E> };
162
+ //#endregion
163
+ //#region src/create-config.d.ts
164
+ /**
165
+ * Create a resolved, validated config for the given environment.
166
+ *
167
+ * Curried so the envs declaration is bound on the first call and the
168
+ * schema is inferred (giving you autocomplete) on the second call.
169
+ *
170
+ * @typeParam E - The envs shape describing required/optional environments.
171
+ *
172
+ * @example
173
+ * ```ts
174
+ * type MyEnvs = {
175
+ * dev?: unknown
176
+ * staging: unknown
177
+ * production: unknown
178
+ * }
179
+ * const config = createEnvironmentConfig<MyEnvs>()('dev', {
180
+ * port: { doc: 'Port', format: Number, value: 3000 },
181
+ * })
182
+ * config.port // number
183
+ * ```
184
+ *
185
+ * @example Fallback environments
186
+ * ```ts
187
+ * // When running in `dev`, any entry that does not declare a `dev` value
188
+ * // falls back to the entry's `integ` value.
189
+ * const config = createEnvironmentConfig<MyEnvs>()(
190
+ * 'dev',
191
+ * {
192
+ * apiUrl: {
193
+ * doc: 'API URL',
194
+ * format: 'url',
195
+ * integ: 'https://integ.example.com',
196
+ * staging: 'https://staging.example.com',
197
+ * production: 'https://api.example.com',
198
+ * },
199
+ * },
200
+ * { fallbacks: { dev: 'integ' } },
201
+ * )
202
+ * ```
203
+ */
204
+ declare function createEnvironmentConfig<E extends EnvsShape>(): <const G extends ConfigGroup<E>>(env: EnvName<E>, inputConfig: ValidateSchema<G, E>, options?: CreateConfigOptions<E>) => ResolveConfigGroup<G> & {
205
+ env: EnvName<E>;
206
+ };
207
+ //#endregion
208
+ //#region src/define-config.d.ts
209
+ /**
210
+ * Like {@link createEnvironmentConfig}, but binds the schema first and the
211
+ * environment later. Useful when the active environment is not known at
212
+ * schema-definition time.
213
+ *
214
+ * @typeParam E - The envs shape describing required/optional environments.
215
+ *
216
+ * @example
217
+ * ```ts
218
+ * type MyEnvs = {
219
+ * dev?: unknown
220
+ * staging: unknown
221
+ * production: unknown
222
+ * }
223
+ * const buildConfig = defineEnvironmentConfig<MyEnvs>()({
224
+ * port: { doc: 'Port', format: Number, value: 3000 },
225
+ * })
226
+ * const config = buildConfig('dev')
227
+ * ```
228
+ *
229
+ * @example Fallback environments
230
+ * ```ts
231
+ * const buildConfig = defineEnvironmentConfig<MyEnvs>()(
232
+ * { apiUrl: { doc: 'API URL', format: 'url', staging: 'https://staging' } },
233
+ * { fallbacks: { dev: 'staging' } },
234
+ * )
235
+ * const config = buildConfig('dev') // apiUrl resolved from `staging`
236
+ * ```
237
+ */
238
+ declare function defineEnvironmentConfig<E extends EnvsShape>(): <const G extends ConfigGroup<E>>(inputConfig: ValidateSchema<G, E>, options?: CreateConfigOptions<E>) => ((env: EnvName<E>) => ResolveConfigGroup<G> & {
239
+ env: EnvName<E>;
240
+ });
241
+ //#endregion
242
+ export { type CreateConfigOptions, type EnvName, type EnvsShape, type Fallbacks, type PerEnv, createEnvironmentConfig, defineEnvironmentConfig };
243
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +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":";;AAoBA;;;;AAA8B;AAAa;;;;;;;;;;;;;KAA/B,SAAA,GAAY,MAAM;AAAA,KAEzB,YAAA,oBAES,CAAA,gBAAiB,IAAA,CAAK,CAAA,EAAG,CAAA,YAAa,CAAA,SAC5C,CAAA;AAAA,KAGH,YAAA,oBAES,CAAA,gBAAiB,IAAA,CAAK,CAAA,EAAG,CAAA,IAAK,CAAA,iBACpC,CAAA;AANC;AAAA,KAUG,OAAA,WAAkB,SAAA,UAAmB,CAAC;;;;;KAMtC,MAAA,WAAiB,SAAA,eAAwB,YAAA,CAAa,CAAA,IAAK,CAAA,aAC/D,YAAA,CAAa,CAAA,KAAM,CAAA;;;;;;;;;;;AAXlB;AAIT;;;;;;;;AAAkD;AAMlD;;;;;;;KA+BY,SAAA,WAAoB,SAAA,IAAa,OAAA,CAC3C,MAAA,CAAO,OAAA,CAAQ,CAAA,GAAI,OAAA,CAAQ,CAAA;;;;;KAOjB,mBAAA,WAA8B,SAAA;EAvCb;;;;EA4C3B,SAAA,GAAY,SAAA,CAAU,CAAA;AAAA;;;KCjFnB,SAAA,WAAoB,SAAA,YAAqB,OAAA,CAAQ,CAAA;AAAA,KAGjD,qBAAA;EACC,KAAA,GAAQ,CAAA;EAAG,UAAA;EAAoB,aAAA;AAAA;EAC/B,KAAA,GAAQ,CAAA;EAAG,UAAA;EAAoB,aAAA;AAAA;EAC/B,KAAA,GAAQ,CAAA;EAAG,UAAA;EAAoB,aAAA;AAAA;AAAA,KAGhC,mBAAA;EACC,KAAA,EAAO,CAAA;EAAG,UAAA;EAAoB,aAAA;AAAA;EAC9B,KAAA,GAAQ,CAAA;EAAG,UAAA;EAAoB,aAAA;AAAA;EAC/B,KAAA,GAAQ,CAAA;EAAG,UAAA;EAAoB,aAAA;AAAA;EAC/B,KAAA,GAAQ,CAAA;EAAG,UAAA;EAAoB,aAAA;EAAuB,OAAA,EAAS,CAAA;AAAA;AAAA,KAKhE,WAAA,cAAyB,SAAA,KACzB,MAAA,CAAO,CAAA,EAAG,CAAA,IAAK,qBAAA,CAAsB,CAAA,MACrC,SAAA,CAAU,CAAA,IAAK,mBAAA,CAAoB,CAAA;AAAA,KAE5B,eAAA,cAA6B,SAAA;EACvC,GAAA;EACA,QAAA;AAAA,IACE,WAAA,CAAY,CAAA,EAAG,CAAA;AAAA,KAEP,WAAA,WAAsB,SAAA;EAAA,CAC/B,GAAA,WAAc,WAAA,MAAiB,CAAA,IAAK,WAAA,CAAY,CAAA;AAAA;AAAA,KAIvC,gBAAA,MACV,CAAA;EAAW,MAAA,EAAQ,iBAAA;AAAA,aACnB,CAAA;EAAW,MAAA,EAAQ,iBAAA;AAAA,aACnB,CAAA;EAAW,MAAA,EAAQ,kBAAA;AAAA,cACnB,CAAA;EAAW,MAAA;AAAA,aACX,CAAA;EAAW,MAAA;AAAA,IAAuB,CAAA,GAClC,CAAA;EAAW,MAAA,EAAQ,gBAAA;AAAA;AAAA,KAGT,kBAAA,oBACE,CAAA,GAAI,CAAA,CAAE,CAAA;EAAa,GAAA;AAAA,IAC3B,gBAAA,CAAiB,CAAA,CAAE,CAAA,KACnB,kBAAA,CAAmB,CAAA,CAAE,CAAA;AAAA,KAOf,WAAA,cAAyB,SAAA,IACjC,YAAA,CAAa,CAAA,IACb,WAAA,CAAY,CAAA,IACZ,WAAA,CAAY,CAAA,IACZ,YAAA,CAAa,CAAA,IACb,UAAA,CAAW,CAAA,EAAG,CAAA,IACd,SAAA,CAAU,CAAA,EAAG,CAAA,IACb,QAAA,CAAS,CAAA;AAAA,KAER,WAAA,WAAsB,SAAA,IAAa,eAAA,SAAwB,CAAA;EAC9D,MAAA,EAAQ,iBAAA;EACR,OAAA;AAAA;AAAA,KAGG,WAAA,WAAsB,SAAA,IAAa,eAAA,SAAwB,CAAA;EAC9D,MAAA,EAAQ,iBAAA;EACR,OAAA;AAAA;AAAA,KAGG,YAAA,WAAuB,SAAA,IAAa,eAAA,UAAyB,CAAA;EAChE,MAAA,EAAQ,kBAAA;EACR,OAAA;AAAA;AAAA,KAGG,UAAA,cAAwB,SAAA,IAAa,eAAA,CAAgB,CAAA,IAAK,CAAA;EAC7D,MAAA,EAAQ,gBAAA;EACR,OAAA,GAAU,CAAA;AAAA;AAAA,KAGP,SAAA,cAAuB,SAAA,IAAa,eAAA,CAAgB,CAAA,EAAG,CAAA;EAC1D,MAAA,EAAQ,CAAA;EACR,OAAA,GAAU,CAAA;AAAA;AAAA,KAGP,QAAA,WAAmB,SAAA,IAAa,eAAA,SAAwB,CAAA;EAC3D,MAAA;EACA,OAAA;AAAA;AAAA,KAGG,YAAA,WAAuB,SAAA,IAAa,eAAA,MAAqB,CAAA;EAC5D,MAAA;EACA,OAAA;AAAA;AAAA,KAMU,cAAA,cAA4B,SAAA,kBAC1B,CAAA,GAAI,CAAA,CAAE,CAAA;EAAa,MAAA;AAAA,kBAEb,CAAA,CAAE,CAAA,IAAK,CAAA,SAAU,OAAA,CAAQ,CAAA,0BACjC,CAAA,CAAE,CAAA,EAAG,CAAA,UAAW,CAAA,eACd,CAAA,CAAE,CAAA,EAAG,CAAA,IACL,CAAA,GACF,CAAA,CAAE,CAAA,EAAG,CAAA,MAEX,CAAA,CAAE,CAAA;EAAa,MAAA;AAAA,IACb,CAAA,CAAE,CAAA,IACF,cAAA,CAAe,CAAA,CAAE,CAAA,GAAI,CAAA;;;;;;AD7FC;AAAa;;;;;;;;;;;;;;;;;;;;AAKlC;AAAA;;;;;;;;;;;;;;;iBE8BO,uBAAA,WAAkC,SAAA,sBACxB,WAAA,CAAY,CAAA,GAClC,GAAA,EAAK,OAAA,CAAQ,CAAA,GACb,WAAA,EAAa,cAAA,CAAe,CAAA,EAAG,CAAA,GAC/B,OAAA,GAAU,mBAAA,CAAoB,CAAA,MAC7B,kBAAA,CAAmB,CAAA;EAAO,GAAA,EAAK,OAAA,CAAQ,CAAA;AAAA;;;;;;AFxCd;AAAa;;;;;;;;;;;;;;;;;;;;AAKlC;AAAA;;;;iBGYO,uBAAA,WAAkC,SAAA,sBAExB,WAAA,CAAY,CAAA,GAClC,WAAA,EAAa,cAAA,CAAe,CAAA,EAAG,CAAA,GAC/B,OAAA,GAAU,mBAAA,CAAoB,CAAA,QAC3B,GAAA,EAAK,OAAA,CAAQ,CAAA,MAAO,kBAAA,CAAmB,CAAA;EAAO,GAAA,EAAK,OAAA,CAAQ,CAAA;AAAA"}
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- //#region lib/util-types.d.ts
1
+ //#region src/util-types.d.ts
2
2
  /**
3
3
  * Declaration of the environments a config supports: an object whose keys
4
4
  * are environment names, with required envs as required properties and
@@ -68,7 +68,7 @@ type CreateConfigOptions<E extends EnvsShape> = {
68
68
  fallbacks?: Fallbacks<E>;
69
69
  };
70
70
  //#endregion
71
- //#region lib/types.d.ts
71
+ //#region src/types.d.ts
72
72
  type NoEnvKeys<E extends EnvsShape> = { [K in EnvName<E>]?: never };
73
73
  type RuntimeSourceOptional<T> = {
74
74
  value?: T;
@@ -95,6 +95,11 @@ type ValueSourceRequired<T> = {
95
95
  value?: T;
96
96
  processEnv?: never;
97
97
  importMetaEnv: string;
98
+ } | {
99
+ value?: T;
100
+ processEnv?: never;
101
+ importMetaEnv?: never;
102
+ default: T;
98
103
  };
99
104
  type ValueSource<T, E extends EnvsShape> = (PerEnv<E, T> & RuntimeSourceOptional<T>) | (NoEnvKeys<E> & ValueSourceRequired<T>);
100
105
  type ConfigEntryBase<T, E extends EnvsShape> = {
@@ -149,8 +154,13 @@ type UntypedEntry<E extends EnvsShape> = ConfigEntryBase<any, E> & {
149
154
  format?: never;
150
155
  default?: any;
151
156
  };
157
+ type ValidateSchema<G, E extends EnvsShape> = { [K in keyof G]: G[K] extends {
158
+ format: readonly (infer V)[];
159
+ } ? { [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
+ format: unknown;
161
+ } ? G[K] : ValidateSchema<G[K], E> };
152
162
  //#endregion
153
- //#region lib/create-config.d.ts
163
+ //#region src/create-config.d.ts
154
164
  /**
155
165
  * Create a resolved, validated config for the given environment.
156
166
  *
@@ -191,11 +201,11 @@ type UntypedEntry<E extends EnvsShape> = ConfigEntryBase<any, E> & {
191
201
  * )
192
202
  * ```
193
203
  */
194
- declare function createEnvironmentConfig<E extends EnvsShape>(): <G extends ConfigGroup<E>>(env: EnvName<E>, inputConfig: G, options?: CreateConfigOptions<E>) => ResolveConfigGroup<G> & {
204
+ declare function createEnvironmentConfig<E extends EnvsShape>(): <const G extends ConfigGroup<E>>(env: EnvName<E>, inputConfig: ValidateSchema<G, E>, options?: CreateConfigOptions<E>) => ResolveConfigGroup<G> & {
195
205
  env: EnvName<E>;
196
206
  };
197
207
  //#endregion
198
- //#region lib/define-config.d.ts
208
+ //#region src/define-config.d.ts
199
209
  /**
200
210
  * Like {@link createEnvironmentConfig}, but binds the schema first and the
201
211
  * environment later. Useful when the active environment is not known at
@@ -225,7 +235,7 @@ declare function createEnvironmentConfig<E extends EnvsShape>(): <G extends Conf
225
235
  * const config = buildConfig('dev') // apiUrl resolved from `staging`
226
236
  * ```
227
237
  */
228
- declare function defineEnvironmentConfig<E extends EnvsShape>(): <G extends ConfigGroup<E>>(inputConfig: G, options?: CreateConfigOptions<E>) => ((env: EnvName<E>) => ResolveConfigGroup<G> & {
238
+ declare function defineEnvironmentConfig<E extends EnvsShape>(): <const G extends ConfigGroup<E>>(inputConfig: ValidateSchema<G, E>, options?: CreateConfigOptions<E>) => ((env: EnvName<E>) => ResolveConfigGroup<G> & {
229
239
  env: EnvName<E>;
230
240
  });
231
241
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../lib/util-types.ts","../lib/types.ts","../lib/create-config.ts","../lib/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
@@ -1,4 +1,4 @@
1
- //#region lib/format.ts
1
+ //#region src/format.ts
2
2
  function validateAndCoerce(value, format, fullKey, errors) {
3
3
  switch (format) {
4
4
  case String:
@@ -29,7 +29,7 @@ function validateAndCoerce(value, format, fullKey, errors) {
29
29
  return value;
30
30
  }
31
31
  //#endregion
32
- //#region lib/create-config.ts
32
+ //#region src/create-config.ts
33
33
  /**
34
34
  * Create a resolved, validated config for the given environment.
35
35
  *
@@ -101,9 +101,9 @@ function buildConfig(env, inputConfig, options) {
101
101
  const runtimeOverride = typeof import.meta !== "undefined" && import.meta.env ? import.meta.env[configEntry.importMetaEnv] : void 0;
102
102
  if (runtimeOverride !== void 0) value = runtimeOverride;
103
103
  }
104
- const hasValueSource = value !== void 0 || "processEnv" in configEntry || "importMetaEnv" in configEntry;
104
+ const hasValueSource = value !== void 0 || "processEnv" in configEntry || "importMetaEnv" in configEntry || configEntry.optional;
105
105
  if (value === void 0 && !hasValueSource) {
106
- errors.push(`No value source declared for ${fullKey}. Supply a value using environment names, "value", "processEnv", or "importMetaEnv".`);
106
+ errors.push(`${fullKey}: No value source declared and "optional" is not set.`);
107
107
  continue;
108
108
  }
109
109
  if (value === void 0) if (configEntry.optional) {
@@ -113,7 +113,7 @@ function buildConfig(env, inputConfig, options) {
113
113
  continue;
114
114
  }
115
115
  } else {
116
- errors.push(`Missing required config value for ${fullKey} in environment ${env}`);
116
+ errors.push(`${fullKey}: Missing required config value in environment ${env}`);
117
117
  continue;
118
118
  }
119
119
  value = validateAndCoerce(value, configEntry.format, fullKey, errors);
@@ -121,12 +121,15 @@ function buildConfig(env, inputConfig, options) {
121
121
  }
122
122
  return output;
123
123
  }
124
- const outputConfig = processConfig(inputConfig, "");
124
+ let outputConfig = processConfig(inputConfig, "");
125
125
  if (errors.length > 0) {
126
126
  console.error("Environment config validation failed", errors);
127
127
  throw new Error(`Environment config validation failed:\n${errors.join("\n")}`);
128
128
  }
129
- outputConfig.env = env;
129
+ outputConfig = {
130
+ env,
131
+ ...outputConfig
132
+ };
130
133
  return outputConfig;
131
134
  }
132
135
  /**
@@ -150,7 +153,7 @@ function resolveFallbackChain(env, fallbacks) {
150
153
  return chain;
151
154
  }
152
155
  //#endregion
153
- //#region lib/define-config.ts
156
+ //#region src/define-config.ts
154
157
  /**
155
158
  * Like {@link createEnvironmentConfig}, but binds the schema first and the
156
159
  * environment later. Useful when the active environment is not known at
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../lib/format.ts","../lib/create-config.ts","../lib/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 type {\n EnvsShape,\n EnvName,\n CreateConfigOptions,\n Fallbacks,\n} from \"./util-types.js\"\nimport type { ConfigGroup, ResolveConfigGroup } from \"./types.js\"\nimport { validateAndCoerce } from \"./format.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 <G extends ConfigGroup<E>>(\n env: EnvName<E>,\n inputConfig: G,\n options?: CreateConfigOptions<E>,\n ): ResolveConfigGroup<G> & { env: EnvName<E> } =>\n buildConfig<E, G>(env, inputConfig, 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\n if (value === undefined && !hasValueSource) {\n errors.push(\n `No value source declared for ${fullKey}. Supply a value using environment names, \"value\", \"processEnv\", or \"importMetaEnv\".`,\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 `Missing required config value for ${fullKey} 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 const 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.env = env\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 type { EnvsShape, EnvName, CreateConfigOptions } from \"./util-types.js\"\nimport type { ConfigGroup, ResolveConfigGroup } from \"./types.js\"\nimport { createEnvironmentConfig } from \"./create-config.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 <G extends ConfigGroup<E>>(\n inputConfig: G,\n options?: CreateConfigOptions<E>,\n ): ((env: EnvName<E>) => ResolveConfigGroup<G> & { env: EnvName<E> }) =>\n (env: EnvName<E>) =>\n create(env, inputConfig, options)\n}\n"],"mappings":";AAEA,SAAgB,kBACd,OACA,QACA,SACA,QACK;CACL,QAAQ,QAAR;EACE,KAAK;GACH,IAAI,OAAO,UAAU,UACnB,OAAO,KAAK,oBAAoB,QAAQ,kBAAkB;GAE5D;EACF,KAAK;GACH,QAAQ,OAAO,KAAK;GACpB,IAAI,MAAM,KAAK,GACb,OAAO,KAAK,oBAAoB,QAAQ,kBAAkB;GAC5D;EACF,KAAK;GACH,IACE,OAAO,UAAU,aACjB,UAAU,UACV,UAAU,WACV,UAAU,KACV,UAAU,GAEV,OAAO,KAAK,oBAAoB,QAAQ,mBAAmB;GAE7D,QACE,UAAU,SAAS,OAAO,UAAU,UAAU,QAAQ,QAAQ,KAAK;GACrE;EACF,KAAK;GACH,IAAI,CAAC,MAAM,QAAQ,KAAK,GACtB,OAAO,KAAK,oBAAoB,QAAQ,kBAAkB;GAE5D;EACF,KAAK;GACH,IAAI;IACF,IAAI,IAAI,KAAK;GACf,QAAQ;IACN,OAAO,KACL,oBAAoB,QAAQ,+BAA+B,MAAM,EACnE;GACF;GACA;EACF,SACE,IAAI,kBAAkB;OAChB,CAAC,OAAO,SAAS,KAAK,GACxB,OAAO,KACL,oBAAoB,QAAQ,oBAAoB,OAAO,KAAK,IAAI,EAAE,EACpE;EAAA;CAGR;CAEA,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACNA,SAAgB,0BAA+C;CAC7D,QACE,KACA,aACA,YAEA,YAAkB,KAAK,aAAa,OAAO;AAC/C;AAEA,SAAS,YACP,KACA,aACA,SAC6C;CAC7C,MAAM,SAAmB,CAAC;CAI1B,MAAM,WAAW,qBAAwB,KAAK,SAAS,SAAS;CAEhE,SAAS,cACP,QACA,WACqB;EACrB,MAAM,SAA8B,CAAC;EAErC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,GAAG;GACjD,IAAI,QAAQ,OACV,MAAM,IAAI,MACR,yFACF;GAGF,MAAM,UAAU,YAAY,GAAG,UAAU,GAAG,QAAQ;GAEpD,IAAI,EAAE,SAAS,QAAQ;IACrB,OAAO,OAAO,cAAc,OAAyB,OAAO;IAC5D;GACF;GAEA,MAAM,cAAc;GAcpB,IAAI,QAAa,WAAW,cAAc,YAAY,QAAQ,KAAA;GAI9D,KAAK,MAAM,gBAAgB,UAAU;IACnC,MAAM,WAAW,YAAY;IAC7B,IAAI,aAAa,KAAA,GAAW;KAC1B,QAAQ;KACR;IACF;GACF;GAGA,IAAI,gBAAgB,aAAa;IAC/B,MAAM,kBAEJ,OAAO,YAAY,eAAe,QAAQ,MAEtC,QAAQ,IAAI,YAAY,cACxB,KAAA;IACN,IAAI,oBAAoB,KAAA,GAAW,QAAQ;GAC7C,OAAO,IAAI,mBAAmB,aAAa;IACzC,MAAM,kBAEJ,OAAO,OAAO,SAAS,eAAe,OAAO,KAAK,MAE9C,OAAO,KAAK,IAAI,YAAY,iBAC5B,KAAA;IACN,IAAI,oBAAoB,KAAA,GAAW,QAAQ;GAC7C;GAEA,MAAM,iBACJ,UAAU,KAAA,KACV,gBAAgB,eAChB,mBAAmB;GAErB,IAAI,UAAU,KAAA,KAAa,CAAC,gBAAgB;IAC1C,OAAO,KACL,gCAAgC,QAAQ,qFAC1C;IACA;GACF;GAEA,IAAI,UAAU,KAAA,GACZ,IAAI,YAAY,UAAU;IACxB,QAAQ,YAAY;IACpB,IAAI,UAAU,KAAA,GAAW;KACvB,OAAO,OAAO,KAAA;KACd;IACF;GACF,OAAO;IACL,OAAO,KACL,qCAAqC,QAAQ,kBAAkB,KACjE;IACA;GACF;GAMF,QAAQ,kBAAkB,OAAO,YAAY,QAAQ,SAAS,MAAM;GAEpE,OAAO,OAAO;EAChB;EAEA,OAAO;CACT;CAEA,MAAM,eAAe,cAAc,aAAa,EAAE;CAElD,IAAI,OAAO,SAAS,GAAG;EACrB,QAAQ,MAAM,wCAAwC,MAAM;EAC5D,MAAM,IAAI,MACR,0CAA0C,OAAO,KAAK,IAAI,GAC5D;CACF;CAEA,aAAa,MAAM;CACnB,OAAO;AACT;;;;;;;AAQA,SAAS,qBACP,KACA,WACc;CACd,MAAM,QAAsB,CAAC,GAAG;CAChC,IAAI,CAAC,WAAW,OAAO;CAEvB,MAAM,OAAO,IAAI,IAAY,CAAC,GAAG,CAAC;CAClC,IAAI,UAAsB;CAC1B,OAAO,UAAU,aAAa,KAAA,GAAW;EACvC,MAAM,OAAO,UAAU;EACvB,IAAI,KAAK,IAAI,IAAI,GACf,MAAM,IAAI,MACR,qCAAqC,CAAC,GAAG,OAAO,IAAI,CAAC,CAAC,KAAK,MAAM,GACnE;EAEF,KAAK,IAAI,IAAI;EACb,MAAM,KAAK,IAAI;EACf,UAAU;CACZ;CACA,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACrLA,SAAgB,0BAA+C;CAC7D,MAAM,SAAS,wBAA2B;CAC1C,QACI,aACA,aAED,QACC,OAAO,KAAK,aAAa,OAAO;AACtC"}
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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "konfeeg",
3
- "version": "0.0.0",
3
+ "version": "0.0.1",
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",
@@ -9,13 +9,33 @@
9
9
  "url": "https://github.com/0livare/konfeeg"
10
10
  },
11
11
  "type": "module",
12
- "main": "dist/index.mjs",
13
- "module": "dist/index.mjs",
12
+ "main": "./dist/index.cjs",
13
+ "module": "./dist/index.mjs",
14
+ "types": "./dist/index.d.mts",
15
+ "exports": {
16
+ ".": {
17
+ "import": {
18
+ "types": "./dist/index.d.mts",
19
+ "default": "./dist/index.mjs"
20
+ },
21
+ "require": {
22
+ "types": "./dist/index.d.cts",
23
+ "default": "./dist/index.cjs"
24
+ }
25
+ }
26
+ },
14
27
  "files": [
15
28
  "dist"
16
29
  ],
30
+ "nx": {
31
+ "targets": {
32
+ "types": {},
33
+ "format": {},
34
+ "lint": {}
35
+ }
36
+ },
17
37
  "devDependencies": {
18
- "prettier": "^3.8.4",
38
+ "@biomejs/biome": "^2.4.16",
19
39
  "tsdown": "^0.22.2",
20
40
  "typescript": "^6.0.3",
21
41
  "vitest": "^4.1.8"
@@ -31,10 +51,9 @@
31
51
  "node"
32
52
  ],
33
53
  "scripts": {
34
- "build": "tsdown lib/index.ts",
54
+ "dev": "tsdown --watch",
55
+ "build": "tsdown",
35
56
  "test": "vitest --run",
36
- "types": "tsc",
37
- "format": "prettier --write .",
38
- "pr": "tsc && prettier --check . && pnpm run test && pnpm run build"
57
+ "pub": "npx bumpp && pnpm publish"
39
58
  }
40
59
  }