@cleverbrush/env 0.0.0-beta-20260414101326
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 +199 -0
- package/dist/env.d.ts +20 -0
- package/dist/errors.d.ts +37 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/parseEnv.d.ts +33 -0
- package/dist/parseEnvFlat.d.ts +32 -0
- package/dist/splitBy.d.ts +17 -0
- package/dist/types.d.ts +38 -0
- package/package.json +54 -0
package/README.md
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# @cleverbrush/env
|
|
2
|
+
|
|
3
|
+
[](https://github.com/cleverbrush/framework/actions/workflows/ci.yml)
|
|
4
|
+
[](../../LICENSE)
|
|
5
|
+
|
|
6
|
+
Type-safe environment variable parsing with `@cleverbrush/schema` — validated, coerced, structured configs from `process.env`.
|
|
7
|
+
|
|
8
|
+
## Why @cleverbrush/env?
|
|
9
|
+
|
|
10
|
+
**The problem:** Environment variables are untyped strings. Most apps access them via `process.env.SOME_VAR!` — no validation, no coercion, no structure. Missing variables surface as runtime crashes. Secrets accidentally leak into frontend bundles. Config objects are ad-hoc and fragile.
|
|
11
|
+
|
|
12
|
+
**The solution:** `@cleverbrush/env` uses `@cleverbrush/schema` builders for validation and coercion, and a branded `env()` wrapper for type-safe variable binding. TypeScript enforces at **compile time** that every config leaf is bound to an environment variable. At runtime, all variables are validated at once and coerced to the correct types — with clear error messages for CI and startup logs.
|
|
13
|
+
|
|
14
|
+
**What makes it different:**
|
|
15
|
+
|
|
16
|
+
- **Compile-time enforcement** — forgetting `env()` on a config leaf is a TypeScript error, not a runtime surprise
|
|
17
|
+
- **Structured configs** — nest objects arbitrarily deep; env vars map to leaf fields
|
|
18
|
+
- **Validation & coercion** — full `@cleverbrush/schema` power: `.minLength()`, `.coerce()`, `.default()`, custom validators
|
|
19
|
+
- **Array support** — `splitBy(',')` preprocessor for comma-separated values
|
|
20
|
+
- **Clear error reporting** — lists all missing and invalid vars at once with paths and types
|
|
21
|
+
- **Computed values** — derive values from resolved config via a type-safe callback
|
|
22
|
+
- **Flat mode** — `parseEnvFlat()` for simple apps where keys = env var names
|
|
23
|
+
|
|
24
|
+
| Feature | @cleverbrush/env | t3-env | envalid |
|
|
25
|
+
| --- | --- | --- | --- |
|
|
26
|
+
| Compile-time leaf enforcement | ✓ | ✗ | ✗ |
|
|
27
|
+
| Nested config structures | ✓ | ✗ | ✗ |
|
|
28
|
+
| Schema-based validation | ✓ | ✓ | ~ |
|
|
29
|
+
| Type coercion (number, boolean, date) | ✓ | Manual | ✓ |
|
|
30
|
+
| Array support | ✓ | ✗ | ✗ |
|
|
31
|
+
| Computed / derived values | ✓ | ✗ | ✗ |
|
|
32
|
+
| All-at-once error reporting | ✓ | ✓ | ✓ |
|
|
33
|
+
| No Zod dependency | ✓ | ✗ | ✓ |
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm install @cleverbrush/env @cleverbrush/schema
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Peer dependency:** `@cleverbrush/schema` must be installed separately.
|
|
42
|
+
|
|
43
|
+
## Quick Start
|
|
44
|
+
|
|
45
|
+
### Structured config (nested)
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
import { env, parseEnv, splitBy } from '@cleverbrush/env';
|
|
49
|
+
import { string, number, boolean, array } from '@cleverbrush/schema';
|
|
50
|
+
|
|
51
|
+
const config = parseEnv({
|
|
52
|
+
db: {
|
|
53
|
+
host: env('DB_HOST', string().default('localhost')),
|
|
54
|
+
port: env('DB_PORT', number().coerce().default(5432)),
|
|
55
|
+
name: env('DB_NAME', string()),
|
|
56
|
+
},
|
|
57
|
+
jwt: {
|
|
58
|
+
secret: env('JWT_SECRET', string().minLength(32)),
|
|
59
|
+
},
|
|
60
|
+
debug: env('DEBUG', boolean().coerce().default(false)),
|
|
61
|
+
allowedOrigins: env(
|
|
62
|
+
'ALLOWED_ORIGINS',
|
|
63
|
+
array(string()).addPreprocessor(splitBy(','), { mutates: false })
|
|
64
|
+
),
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Type is fully inferred:
|
|
68
|
+
// {
|
|
69
|
+
// db: { host: string, port: number, name: string },
|
|
70
|
+
// jwt: { secret: string },
|
|
71
|
+
// debug: boolean,
|
|
72
|
+
// allowedOrigins: string[]
|
|
73
|
+
// }
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Flat config
|
|
77
|
+
|
|
78
|
+
For simple apps where each key is both the property name and the env var name:
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
import { parseEnvFlat } from '@cleverbrush/env';
|
|
82
|
+
import { string, number } from '@cleverbrush/schema';
|
|
83
|
+
|
|
84
|
+
const config = parseEnvFlat({
|
|
85
|
+
DB_HOST: string().default('localhost'),
|
|
86
|
+
DB_PORT: number().coerce().default(5432),
|
|
87
|
+
JWT_SECRET: string().minLength(32),
|
|
88
|
+
});
|
|
89
|
+
// Type: { DB_HOST: string, DB_PORT: number, JWT_SECRET: string }
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Type enforcement
|
|
93
|
+
|
|
94
|
+
Forgetting `env()` is a compile-time error:
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
parseEnv({
|
|
98
|
+
db: {
|
|
99
|
+
host: string(), // ← TypeScript ERROR: not assignable to EnvConfigNode
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Array support
|
|
105
|
+
|
|
106
|
+
Use the `splitBy()` preprocessor helper:
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
// Comma-separated strings
|
|
110
|
+
env('ALLOWED_ORIGINS', array(string()).addPreprocessor(splitBy(','), { mutates: false }))
|
|
111
|
+
// "a, b, c" → ['a', 'b', 'c']
|
|
112
|
+
|
|
113
|
+
// Comma-separated numbers
|
|
114
|
+
env('PORTS', array(number().coerce()).addPreprocessor(splitBy(','), { mutates: false }))
|
|
115
|
+
// "3000, 4000" → [3000, 4000]
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Error reporting
|
|
119
|
+
|
|
120
|
+
When variables are missing or invalid, `EnvValidationError` is thrown with a formatted message:
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
Missing environment variables:
|
|
124
|
+
- DB_NAME (required by db.name) [string]
|
|
125
|
+
- JWT_SECRET (required by jwt.secret) [string]
|
|
126
|
+
Invalid environment variables:
|
|
127
|
+
- DB_PORT: "abc" (required by db.port) — number expected
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
The error also exposes structured `.missing` and `.invalid` properties for programmatic access.
|
|
131
|
+
|
|
132
|
+
### Custom source
|
|
133
|
+
|
|
134
|
+
By default, `parseEnv()` reads from `process.env`. Pass a custom source for testing or alternative runtimes:
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
const config = parseEnv(schema, {
|
|
138
|
+
DB_HOST: 'test-host',
|
|
139
|
+
DB_PORT: '9999',
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Computed values
|
|
144
|
+
|
|
145
|
+
Derive values from the resolved config by passing a compute callback as the second argument. The callback receives the fully typed base config and returns an object that is deep-merged into the result:
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
import { env, parseEnv } from '@cleverbrush/env';
|
|
149
|
+
import { string, number } from '@cleverbrush/schema';
|
|
150
|
+
|
|
151
|
+
const config = parseEnv(
|
|
152
|
+
{
|
|
153
|
+
db: {
|
|
154
|
+
host: env('DB_HOST', string().default('localhost')),
|
|
155
|
+
port: env('DB_PORT', number().coerce().default(5432)),
|
|
156
|
+
name: env('DB_NAME', string()),
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
(base) => ({
|
|
160
|
+
db: {
|
|
161
|
+
connectionString: `postgres://${base.db.host}:${base.db.port}/${base.db.name}`,
|
|
162
|
+
},
|
|
163
|
+
})
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
// base is fully typed: { db: { host: string, port: number, name: string } }
|
|
167
|
+
// Result type is deep-merged:
|
|
168
|
+
// { db: { host: string, port: number, name: string, connectionString: string } }
|
|
169
|
+
|
|
170
|
+
config.db.connectionString // "postgres://localhost:5432/mydb"
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
When using a compute callback, the optional source is passed as the third argument:
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
const config = parseEnv(
|
|
177
|
+
{ host: env('HOST', string()) },
|
|
178
|
+
(base) => ({ url: `http://${base.host}` }),
|
|
179
|
+
{ HOST: 'example.com' } // custom source
|
|
180
|
+
);
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## API Reference
|
|
184
|
+
|
|
185
|
+
| Export | Type | Description |
|
|
186
|
+
| --- | --- | --- |
|
|
187
|
+
| `env(varName, schema)` | Function | Binds a schema to an env var name. Required for every leaf in `parseEnv()`. |
|
|
188
|
+
| `parseEnv(config, source?)` | Function | Parses env vars into a validated, typed nested config object. |
|
|
189
|
+
| `parseEnv(config, compute, source?)` | Function | Parses env vars, then deep-merges computed values from the callback. |
|
|
190
|
+
| `parseEnvFlat(schemas, source?)` | Function | Flat convenience — keys are env var names, no `env()` needed. |
|
|
191
|
+
| `splitBy(separator)` | Function | Preprocessor that splits a string into an array. |
|
|
192
|
+
| `EnvValidationError` | Class | Thrown when env vars are missing or invalid. Has `.missing` and `.invalid`. |
|
|
193
|
+
| `EnvField<T>` | Type | Branded wrapper type created by `env()`. |
|
|
194
|
+
| `EnvConfig` | Type | Config descriptor tree type (input to `parseEnv`). |
|
|
195
|
+
| `InferEnvConfig<T>` | Type | Infers the runtime type from a config descriptor. |
|
|
196
|
+
|
|
197
|
+
## License
|
|
198
|
+
|
|
199
|
+
[BSD-3-Clause](../../LICENSE)
|
package/dist/env.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { SchemaBuilder } from '@cleverbrush/schema';
|
|
2
|
+
import { type EnvField } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Associates a schema builder with an environment variable name.
|
|
5
|
+
*
|
|
6
|
+
* Every leaf field in a config descriptor passed to `parseEnv()` must be
|
|
7
|
+
* wrapped with `env()`. This is enforced at the TypeScript level — passing
|
|
8
|
+
* a bare schema builder produces a compile-time error.
|
|
9
|
+
*
|
|
10
|
+
* @param varName - The environment variable name to read (e.g. `'DB_HOST'`).
|
|
11
|
+
* @param schema - A `@cleverbrush/schema` builder describing the expected
|
|
12
|
+
* type, constraints, coercion, and defaults.
|
|
13
|
+
* @returns A branded `EnvField` descriptor.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* env('DB_PORT', number().coerce().default(5432))
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export declare function env<T extends SchemaBuilder<any, any, any, any, any>>(varName: string, schema: T): EnvField<T>;
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Describes a single missing environment variable.
|
|
3
|
+
*/
|
|
4
|
+
export interface MissingEnvVar {
|
|
5
|
+
/** The environment variable name (e.g. `'DB_HOST'`). */
|
|
6
|
+
varName: string;
|
|
7
|
+
/** Dot-separated config path (e.g. `'db.host'`). */
|
|
8
|
+
configPath: string;
|
|
9
|
+
/** Schema type identifier (e.g. `'string'`, `'number'`). */
|
|
10
|
+
type: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Describes a single invalid environment variable.
|
|
14
|
+
*/
|
|
15
|
+
export interface InvalidEnvVar {
|
|
16
|
+
/** The environment variable name (e.g. `'DB_PORT'`). */
|
|
17
|
+
varName: string;
|
|
18
|
+
/** Dot-separated config path (e.g. `'db.port'`). */
|
|
19
|
+
configPath: string;
|
|
20
|
+
/** The raw string value that failed validation. */
|
|
21
|
+
value: string;
|
|
22
|
+
/** Validation error messages. */
|
|
23
|
+
errors: string[];
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Thrown by `parseEnv()` when one or more environment variables are missing
|
|
27
|
+
* or fail validation.
|
|
28
|
+
*
|
|
29
|
+
* The structured `missing` and `invalid` properties allow programmatic
|
|
30
|
+
* inspection, while the formatted `message` provides a human-readable
|
|
31
|
+
* summary suitable for CI logs and startup output.
|
|
32
|
+
*/
|
|
33
|
+
export declare class EnvValidationError extends Error {
|
|
34
|
+
readonly missing: readonly MissingEnvVar[];
|
|
35
|
+
readonly invalid: readonly InvalidEnvVar[];
|
|
36
|
+
constructor(missing: MissingEnvVar[], invalid: InvalidEnvVar[]);
|
|
37
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { env } from './env.js';
|
|
2
|
+
export type { InvalidEnvVar, MissingEnvVar } from './errors.js';
|
|
3
|
+
export { EnvValidationError } from './errors.js';
|
|
4
|
+
export { parseEnv } from './parseEnv.js';
|
|
5
|
+
export { parseEnvFlat } from './parseEnvFlat.js';
|
|
6
|
+
export { splitBy } from './splitBy.js';
|
|
7
|
+
export type { EnvConfig, EnvConfigNode, EnvField, InferEnvConfig } from './types.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
var p=Symbol("ENV_FIELD_BRAND");function l(n,e){if(typeof n!="string"||!n)throw new Error("env(): varName must be a non-empty string");return{[p]:!0,varName:n,schema:e}}var d=class extends Error{missing;invalid;constructor(e,t){let r=[];if(e.length>0){r.push("Missing environment variables:");for(let o of e)r.push(` - ${o.varName} (required by ${o.configPath}) [${o.type}]`)}if(t.length>0){r.push("Invalid environment variables:");for(let o of t)r.push(` - ${o.varName}: ${JSON.stringify(o.value)} (required by ${o.configPath}) \u2014 ${o.errors.join("; ")}`)}super(r.join(`
|
|
2
|
+
`)),this.name="EnvValidationError",this.missing=e,this.invalid=t}};import{deepExtend as w}from"@cleverbrush/deep";import{object as k}from"@cleverbrush/schema";function T(n){return typeof n=="object"&&n!==null&&p in n&&n[p]===!0}function x(n,e,t){let r={},o=[];for(let f of Object.keys(n)){let s=n[f],v=t?`${t}.${f}`:f;if(T(s)){let a=e[s.varName];r[f]=a===""?void 0:a,o.push({varName:s.varName,configPath:v,schema:s.schema})}else if(typeof s=="object"&&s!==null){let a=x(s,e,v);r[f]=a.rawObject,o.push(...a.mappings)}}return{rawObject:r,mappings:o}}function N(n){let e={};for(let t of Object.keys(n)){let r=n[t];T(r)?e[t]=r.schema:typeof r=="object"&&r!==null&&(e[t]=N(r))}return k(e)}function m(n,e,t){let r=typeof e=="function",o=r?t??(typeof process<"u"?process.env:{}):e??(typeof process<"u"?process.env:{}),{rawObject:f,mappings:s}=x(n,o,""),a=N(n).validate(f,{doNotStopOnFirstError:!0});if(a.valid){let i=a.object;if(r){let c=e(i);return w(i,c)}return i}let y=[],g=[],E=a.errors?.map(i=>i.message)??[];for(let i of s){let c=o[i.varName],u=i.schema.introspect(),R=u.isRequired,b=u.hasDefault;if(c===void 0||c==="")R&&!b&&y.push({varName:i.varName,configPath:i.configPath,type:u.type});else{let h=i.schema.validate(c);h.valid||g.push({varName:i.varName,configPath:i.configPath,value:c,errors:h.errors?.map(I=>I.message)??[]})}}throw y.length===0&&g.length===0&&E.length>0&&g.push({varName:"(unknown)",configPath:"(unknown)",value:"",errors:E}),new d(y,g)}function C(n,e){let t={};for(let r of Object.keys(n))t[r]=l(r,n[r]);return m(t,e)}function F(n){return(e=>typeof e=="string"?e.split(n).map(t=>t.trim()):e)}export{d as EnvValidationError,l as env,m as parseEnv,C as parseEnvFlat,F as splitBy};
|
|
3
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/types.ts","../src/env.ts","../src/errors.ts","../src/parseEnv.ts","../src/parseEnvFlat.ts","../src/splitBy.ts"],"sourcesContent":["import type { InferType, SchemaBuilder } from '@cleverbrush/schema';\n\n/**\n * Brand symbol used to distinguish EnvField from plain objects at the type level.\n */\nexport const ENV_FIELD_BRAND: unique symbol = Symbol('ENV_FIELD_BRAND');\n\n/**\n * A branded wrapper around a schema builder that associates it with an\n * environment variable name. Created exclusively by the {@link env} function.\n */\nexport type EnvField<T extends SchemaBuilder<any, any, any, any, any>> = {\n readonly [ENV_FIELD_BRAND]: true;\n readonly varName: string;\n readonly schema: T;\n};\n\n/**\n * A node in the config descriptor tree.\n * - Leaf: an `EnvField` (created via `env()`)\n * - Branch: a plain object whose values are themselves `EnvConfigNode`s\n *\n * Schema builders without `env()` wrapping intentionally fail to satisfy\n * this type, producing a compile-time error.\n */\nexport type EnvConfigNode =\n | EnvField<any>\n | { readonly [key: string]: EnvConfigNode };\n\n/**\n * Top-level config descriptor: a record of `EnvConfigNode`s.\n */\nexport type EnvConfig = Record<string, EnvConfigNode>;\n\n/**\n * Recursively infers the runtime type from an `EnvConfig` descriptor tree.\n *\n * - `EnvField<S>` leaves resolve to `InferType<S>`\n * - Object branches resolve to `{ [K]: InferEnvConfig<V> }`\n */\nexport type InferEnvConfig<T> = {\n [K in keyof T]: T[K] extends EnvField<infer S>\n ? InferType<S>\n : T[K] extends Record<string, any>\n ? InferEnvConfig<T[K]>\n : never;\n};\n","import type { SchemaBuilder } from '@cleverbrush/schema';\nimport { ENV_FIELD_BRAND, type EnvField } from './types.js';\n\n/**\n * Associates a schema builder with an environment variable name.\n *\n * Every leaf field in a config descriptor passed to `parseEnv()` must be\n * wrapped with `env()`. This is enforced at the TypeScript level — passing\n * a bare schema builder produces a compile-time error.\n *\n * @param varName - The environment variable name to read (e.g. `'DB_HOST'`).\n * @param schema - A `@cleverbrush/schema` builder describing the expected\n * type, constraints, coercion, and defaults.\n * @returns A branded `EnvField` descriptor.\n *\n * @example\n * ```ts\n * env('DB_PORT', number().coerce().default(5432))\n * ```\n */\nexport function env<T extends SchemaBuilder<any, any, any, any, any>>(\n varName: string,\n schema: T\n): EnvField<T> {\n if (typeof varName !== 'string' || !varName) {\n throw new Error('env(): varName must be a non-empty string');\n }\n return {\n [ENV_FIELD_BRAND]: true,\n varName,\n schema\n } as EnvField<T>;\n}\n","/**\n * Describes a single missing environment variable.\n */\nexport interface MissingEnvVar {\n /** The environment variable name (e.g. `'DB_HOST'`). */\n varName: string;\n /** Dot-separated config path (e.g. `'db.host'`). */\n configPath: string;\n /** Schema type identifier (e.g. `'string'`, `'number'`). */\n type: string;\n}\n\n/**\n * Describes a single invalid environment variable.\n */\nexport interface InvalidEnvVar {\n /** The environment variable name (e.g. `'DB_PORT'`). */\n varName: string;\n /** Dot-separated config path (e.g. `'db.port'`). */\n configPath: string;\n /** The raw string value that failed validation. */\n value: string;\n /** Validation error messages. */\n errors: string[];\n}\n\n/**\n * Thrown by `parseEnv()` when one or more environment variables are missing\n * or fail validation.\n *\n * The structured `missing` and `invalid` properties allow programmatic\n * inspection, while the formatted `message` provides a human-readable\n * summary suitable for CI logs and startup output.\n */\nexport class EnvValidationError extends Error {\n public readonly missing: readonly MissingEnvVar[];\n public readonly invalid: readonly InvalidEnvVar[];\n\n constructor(missing: MissingEnvVar[], invalid: InvalidEnvVar[]) {\n const lines: string[] = [];\n\n if (missing.length > 0) {\n lines.push('Missing environment variables:');\n for (const m of missing) {\n lines.push(\n ` - ${m.varName} (required by ${m.configPath}) [${m.type}]`\n );\n }\n }\n\n if (invalid.length > 0) {\n lines.push('Invalid environment variables:');\n for (const inv of invalid) {\n lines.push(\n ` - ${inv.varName}: ${JSON.stringify(inv.value)} (required by ${inv.configPath}) — ${inv.errors.join('; ')}`\n );\n }\n }\n\n super(lines.join('\\n'));\n this.name = 'EnvValidationError';\n this.missing = missing;\n this.invalid = invalid;\n }\n}\n","import { deepExtend, type Merge } from '@cleverbrush/deep';\nimport { object, type SchemaBuilder } from '@cleverbrush/schema';\nimport {\n EnvValidationError,\n type InvalidEnvVar,\n type MissingEnvVar\n} from './errors.js';\nimport {\n ENV_FIELD_BRAND,\n type EnvConfig,\n type EnvField,\n type InferEnvConfig\n} from './types.js';\n\n/**\n * Returns `true` if the value is an `EnvField` (created by `env()`).\n */\nfunction isEnvField(value: unknown): value is EnvField<any> {\n return (\n typeof value === 'object' &&\n value !== null &&\n ENV_FIELD_BRAND in value &&\n (value as any)[ENV_FIELD_BRAND] === true\n );\n}\n\n/**\n * Metadata collected during the config tree walk, tracking which env var\n * maps to which config path for error reporting.\n */\ninterface EnvMapping {\n varName: string;\n configPath: string;\n schema: SchemaBuilder<any, any, any, any, any>;\n}\n\n/**\n * Recursively walks the config descriptor tree:\n * - For `EnvField` leaves: reads `source[varName]` and places the raw value\n * - For object branches: recurses into children\n *\n * @returns `rawObject` — a nested object with raw string values at the\n * correct paths, and `mappings` — an array tracking varName→configPath\n * for error reporting.\n */\nfunction walkConfig(\n config: Record<string, unknown>,\n source: Record<string, string | undefined>,\n pathPrefix: string\n): { rawObject: Record<string, unknown>; mappings: EnvMapping[] } {\n const rawObject: Record<string, unknown> = {};\n const mappings: EnvMapping[] = [];\n\n for (const key of Object.keys(config)) {\n const node = config[key];\n const configPath = pathPrefix ? `${pathPrefix}.${key}` : key;\n\n if (isEnvField(node)) {\n const raw = source[node.varName];\n rawObject[key] = raw === '' ? undefined : raw;\n mappings.push({\n varName: node.varName,\n configPath,\n schema: node.schema\n });\n } else if (typeof node === 'object' && node !== null) {\n const child = walkConfig(\n node as Record<string, unknown>,\n source,\n configPath\n );\n rawObject[key] = child.rawObject;\n mappings.push(...child.mappings);\n }\n }\n\n return { rawObject, mappings };\n}\n\n/**\n * Recursively builds an `ObjectSchemaBuilder` from the config descriptor tree.\n *\n * For `EnvField` leaves the inner schema is used directly.\n * For object branches a nested `object()` schema is constructed.\n */\nfunction buildSchema(\n config: Record<string, unknown>\n): SchemaBuilder<any, any, any, any, any> {\n const props: Record<string, SchemaBuilder<any, any, any, any, any>> = {};\n\n for (const key of Object.keys(config)) {\n const node = config[key];\n\n if (isEnvField(node)) {\n props[key] = node.schema;\n } else if (typeof node === 'object' && node !== null) {\n props[key] = buildSchema(node as Record<string, unknown>);\n }\n }\n\n return object(props as any) as any;\n}\n\n/**\n * Parses environment variables into a validated, typed config object.\n *\n * Every leaf field in the config descriptor must be wrapped with `env()` —\n * this is enforced at the TypeScript level.\n *\n * The function:\n * 1. Walks the descriptor tree, reading raw values from `source` (defaults\n * to `process.env`).\n * 2. Assembles a `@cleverbrush/schema` object schema from the leaf schemas.\n * 3. Validates and coerces the raw values via `schema.validate()`.\n * 4. Returns the typed result or throws an `EnvValidationError` listing\n * all missing and invalid variables.\n *\n * @param config - A descriptor tree where leaves are `EnvField`s and\n * branches are plain objects.\n * @param source - The environment variable source. Defaults to `process.env`.\n *\n * @example\n * ```ts\n * const config = parseEnv({\n * db: {\n * host: env('DB_HOST', string().default('localhost')),\n * port: env('DB_PORT', number().coerce().default(5432)),\n * },\n * debug: env('DEBUG', boolean().coerce().default(false)),\n * });\n * ```\n */\nexport function parseEnv<T extends EnvConfig>(\n config: T,\n source?: Record<string, string | undefined>\n): InferEnvConfig<T>;\nexport function parseEnv<\n T extends EnvConfig,\n C extends Record<string, unknown>\n>(\n config: T,\n compute: (base: InferEnvConfig<T>) => C,\n source?: Record<string, string | undefined>\n): Merge<[InferEnvConfig<T>, C]>;\nexport function parseEnv<T extends EnvConfig>(\n config: T,\n computeOrSource?:\n | ((base: InferEnvConfig<T>) => Record<string, unknown>)\n | Record<string, string | undefined>,\n maybeSource?: Record<string, string | undefined>\n): unknown {\n const isCompute = typeof computeOrSource === 'function';\n const source: Record<string, string | undefined> = isCompute\n ? (maybeSource ?? (typeof process !== 'undefined' ? process.env : {}))\n : (computeOrSource ??\n (typeof process !== 'undefined' ? process.env : {}));\n\n const { rawObject, mappings } = walkConfig(config, source, '');\n const schema = buildSchema(config);\n\n const result = schema.validate(rawObject, {\n doNotStopOnFirstError: true\n });\n\n if (result.valid) {\n const base = result.object as InferEnvConfig<T>;\n if (isCompute) {\n const computed = (\n computeOrSource as (\n base: InferEnvConfig<T>\n ) => Record<string, unknown>\n )(base);\n return deepExtend(base, computed);\n }\n return base;\n }\n\n // Map schema validation errors back to env var names\n const missing: MissingEnvVar[] = [];\n const invalid: InvalidEnvVar[] = [];\n\n const errorMessages = result.errors?.map(e => e.message) ?? [];\n\n // For each mapping, check if the var was present and if there's an error\n for (const mapping of mappings) {\n const raw = source[mapping.varName];\n const introspected = mapping.schema.introspect();\n const isRequired = introspected.isRequired;\n const hasDefault = introspected.hasDefault;\n\n if (raw === undefined || raw === '') {\n if (isRequired && !hasDefault) {\n missing.push({\n varName: mapping.varName,\n configPath: mapping.configPath,\n type: introspected.type\n });\n }\n } else {\n // Validate this individual field to see if it's invalid\n const fieldResult = mapping.schema.validate(raw);\n if (!fieldResult.valid) {\n invalid.push({\n varName: mapping.varName,\n configPath: mapping.configPath,\n value: raw,\n errors: fieldResult.errors?.map(e => e.message) ?? []\n });\n }\n }\n }\n\n // If we couldn't categorize any errors but validation still failed,\n // add the raw error messages as a generic invalid entry\n if (\n missing.length === 0 &&\n invalid.length === 0 &&\n errorMessages.length > 0\n ) {\n invalid.push({\n varName: '(unknown)',\n configPath: '(unknown)',\n value: '',\n errors: errorMessages\n });\n }\n\n throw new EnvValidationError(missing, invalid);\n}\n","import type { InferType, SchemaBuilder } from '@cleverbrush/schema';\nimport { env } from './env.js';\nimport { parseEnv } from './parseEnv.js';\n\n/**\n * Flat schema map: keys are environment variable names, values are schema builders.\n */\ntype FlatEnvSchemas = Record<string, SchemaBuilder<any, any, any, any, any>>;\n\n/**\n * Infers the runtime type from a flat schema map.\n */\ntype InferFlatEnv<T extends FlatEnvSchemas> = {\n [K in keyof T]: InferType<T[K]>;\n};\n\n/**\n * Convenience wrapper for simple flat configs where each key is both the\n * config property name and the environment variable name.\n *\n * Equivalent to calling `parseEnv()` with every entry wrapped in `env()`.\n *\n * @param schemas - A record mapping env var names to schema builders.\n * @param source - The environment variable source. Defaults to `process.env`.\n *\n * @example\n * ```ts\n * const config = parseEnvFlat({\n * DB_HOST: string().default('localhost'),\n * DB_PORT: number().coerce().default(5432),\n * JWT_SECRET: string().minLength(32),\n * });\n * // Type: { DB_HOST: string, DB_PORT: number, JWT_SECRET: string }\n * ```\n */\nexport function parseEnvFlat<T extends FlatEnvSchemas>(\n schemas: T,\n source?: Record<string, string | undefined>\n): InferFlatEnv<T> {\n const config: Record<string, ReturnType<typeof env>> = {};\n for (const key of Object.keys(schemas)) {\n config[key] = env(key, schemas[key]);\n }\n return parseEnv(config as any, source) as InferFlatEnv<T>;\n}\n","/**\n * Creates a preprocessor function that splits a string value by the given\n * separator and trims each resulting element.\n *\n * Intended for use with `array()` schemas to parse comma-separated (or\n * similarly delimited) environment variable values.\n *\n * @param separator - The delimiter string (e.g. `','`, `';'`, `' '`).\n * @returns A preprocessor function suitable for `schema.addPreprocessor()`.\n *\n * @example\n * ```ts\n * env('ALLOWED_ORIGINS', array(string()).addPreprocessor(splitBy(','), { mutates: false }))\n * // \"a, b, c\" → ['a', 'b', 'c']\n * ```\n */\nexport function splitBy<T = string>(separator: string): (value: T[]) => T[] {\n return ((value: unknown): unknown => {\n if (typeof value === 'string') {\n return value.split(separator).map(s => s.trim());\n }\n return value;\n }) as (value: T[]) => T[];\n}\n"],"mappings":"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","names":["ENV_FIELD_BRAND","env","varName","schema","ENV_FIELD_BRAND","EnvValidationError","missing","invalid","lines","m","inv","deepExtend","object","isEnvField","value","ENV_FIELD_BRAND","walkConfig","config","source","pathPrefix","rawObject","mappings","key","node","configPath","raw","child","buildSchema","props","object","parseEnv","computeOrSource","maybeSource","isCompute","result","base","computed","deepExtend","missing","invalid","errorMessages","e","mapping","introspected","isRequired","hasDefault","fieldResult","EnvValidationError","parseEnvFlat","schemas","source","config","key","env","parseEnv","splitBy","separator","value","s"]}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type Merge } from '@cleverbrush/deep';
|
|
2
|
+
import { type EnvConfig, type InferEnvConfig } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Parses environment variables into a validated, typed config object.
|
|
5
|
+
*
|
|
6
|
+
* Every leaf field in the config descriptor must be wrapped with `env()` —
|
|
7
|
+
* this is enforced at the TypeScript level.
|
|
8
|
+
*
|
|
9
|
+
* The function:
|
|
10
|
+
* 1. Walks the descriptor tree, reading raw values from `source` (defaults
|
|
11
|
+
* to `process.env`).
|
|
12
|
+
* 2. Assembles a `@cleverbrush/schema` object schema from the leaf schemas.
|
|
13
|
+
* 3. Validates and coerces the raw values via `schema.validate()`.
|
|
14
|
+
* 4. Returns the typed result or throws an `EnvValidationError` listing
|
|
15
|
+
* all missing and invalid variables.
|
|
16
|
+
*
|
|
17
|
+
* @param config - A descriptor tree where leaves are `EnvField`s and
|
|
18
|
+
* branches are plain objects.
|
|
19
|
+
* @param source - The environment variable source. Defaults to `process.env`.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* const config = parseEnv({
|
|
24
|
+
* db: {
|
|
25
|
+
* host: env('DB_HOST', string().default('localhost')),
|
|
26
|
+
* port: env('DB_PORT', number().coerce().default(5432)),
|
|
27
|
+
* },
|
|
28
|
+
* debug: env('DEBUG', boolean().coerce().default(false)),
|
|
29
|
+
* });
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export declare function parseEnv<T extends EnvConfig>(config: T, source?: Record<string, string | undefined>): InferEnvConfig<T>;
|
|
33
|
+
export declare function parseEnv<T extends EnvConfig, C extends Record<string, unknown>>(config: T, compute: (base: InferEnvConfig<T>) => C, source?: Record<string, string | undefined>): Merge<[InferEnvConfig<T>, C]>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { InferType, SchemaBuilder } from '@cleverbrush/schema';
|
|
2
|
+
/**
|
|
3
|
+
* Flat schema map: keys are environment variable names, values are schema builders.
|
|
4
|
+
*/
|
|
5
|
+
type FlatEnvSchemas = Record<string, SchemaBuilder<any, any, any, any, any>>;
|
|
6
|
+
/**
|
|
7
|
+
* Infers the runtime type from a flat schema map.
|
|
8
|
+
*/
|
|
9
|
+
type InferFlatEnv<T extends FlatEnvSchemas> = {
|
|
10
|
+
[K in keyof T]: InferType<T[K]>;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Convenience wrapper for simple flat configs where each key is both the
|
|
14
|
+
* config property name and the environment variable name.
|
|
15
|
+
*
|
|
16
|
+
* Equivalent to calling `parseEnv()` with every entry wrapped in `env()`.
|
|
17
|
+
*
|
|
18
|
+
* @param schemas - A record mapping env var names to schema builders.
|
|
19
|
+
* @param source - The environment variable source. Defaults to `process.env`.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* const config = parseEnvFlat({
|
|
24
|
+
* DB_HOST: string().default('localhost'),
|
|
25
|
+
* DB_PORT: number().coerce().default(5432),
|
|
26
|
+
* JWT_SECRET: string().minLength(32),
|
|
27
|
+
* });
|
|
28
|
+
* // Type: { DB_HOST: string, DB_PORT: number, JWT_SECRET: string }
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export declare function parseEnvFlat<T extends FlatEnvSchemas>(schemas: T, source?: Record<string, string | undefined>): InferFlatEnv<T>;
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a preprocessor function that splits a string value by the given
|
|
3
|
+
* separator and trims each resulting element.
|
|
4
|
+
*
|
|
5
|
+
* Intended for use with `array()` schemas to parse comma-separated (or
|
|
6
|
+
* similarly delimited) environment variable values.
|
|
7
|
+
*
|
|
8
|
+
* @param separator - The delimiter string (e.g. `','`, `';'`, `' '`).
|
|
9
|
+
* @returns A preprocessor function suitable for `schema.addPreprocessor()`.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* env('ALLOWED_ORIGINS', array(string()).addPreprocessor(splitBy(','), { mutates: false }))
|
|
14
|
+
* // "a, b, c" → ['a', 'b', 'c']
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export declare function splitBy<T = string>(separator: string): (value: T[]) => T[];
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { InferType, SchemaBuilder } from '@cleverbrush/schema';
|
|
2
|
+
/**
|
|
3
|
+
* Brand symbol used to distinguish EnvField from plain objects at the type level.
|
|
4
|
+
*/
|
|
5
|
+
export declare const ENV_FIELD_BRAND: unique symbol;
|
|
6
|
+
/**
|
|
7
|
+
* A branded wrapper around a schema builder that associates it with an
|
|
8
|
+
* environment variable name. Created exclusively by the {@link env} function.
|
|
9
|
+
*/
|
|
10
|
+
export type EnvField<T extends SchemaBuilder<any, any, any, any, any>> = {
|
|
11
|
+
readonly [ENV_FIELD_BRAND]: true;
|
|
12
|
+
readonly varName: string;
|
|
13
|
+
readonly schema: T;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* A node in the config descriptor tree.
|
|
17
|
+
* - Leaf: an `EnvField` (created via `env()`)
|
|
18
|
+
* - Branch: a plain object whose values are themselves `EnvConfigNode`s
|
|
19
|
+
*
|
|
20
|
+
* Schema builders without `env()` wrapping intentionally fail to satisfy
|
|
21
|
+
* this type, producing a compile-time error.
|
|
22
|
+
*/
|
|
23
|
+
export type EnvConfigNode = EnvField<any> | {
|
|
24
|
+
readonly [key: string]: EnvConfigNode;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Top-level config descriptor: a record of `EnvConfigNode`s.
|
|
28
|
+
*/
|
|
29
|
+
export type EnvConfig = Record<string, EnvConfigNode>;
|
|
30
|
+
/**
|
|
31
|
+
* Recursively infers the runtime type from an `EnvConfig` descriptor tree.
|
|
32
|
+
*
|
|
33
|
+
* - `EnvField<S>` leaves resolve to `InferType<S>`
|
|
34
|
+
* - Object branches resolve to `{ [K]: InferEnvConfig<V> }`
|
|
35
|
+
*/
|
|
36
|
+
export type InferEnvConfig<T> = {
|
|
37
|
+
[K in keyof T]: T[K] extends EnvField<infer S> ? InferType<S> : T[K] extends Record<string, any> ? InferEnvConfig<T[K]> : never;
|
|
38
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"author": "Andrew Zolotukhin <andrew_zol@cleverbrush.com>",
|
|
3
|
+
"bugs": {
|
|
4
|
+
"url": "https://github.com/cleverbrush/framework/issues",
|
|
5
|
+
"email": "andrew_zol@cleverbrush.com"
|
|
6
|
+
},
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"@cleverbrush/deep": "0.0.0-beta-20260414101326"
|
|
9
|
+
},
|
|
10
|
+
"peerDependencies": {
|
|
11
|
+
"@cleverbrush/schema": "0.0.0-beta-20260414101326"
|
|
12
|
+
},
|
|
13
|
+
"description": "Type-safe environment variable parsing with @cleverbrush/schema — validated, coerced, structured configs from process.env",
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"homepage": "https://docs.cleverbrush.com/env",
|
|
18
|
+
"keywords": [
|
|
19
|
+
"environment variables",
|
|
20
|
+
"env",
|
|
21
|
+
"dotenv",
|
|
22
|
+
"config",
|
|
23
|
+
"type-safe",
|
|
24
|
+
"validation",
|
|
25
|
+
"typescript",
|
|
26
|
+
"cleverbrush"
|
|
27
|
+
],
|
|
28
|
+
"license": "BSD 3-Clause",
|
|
29
|
+
"main": "./dist/index.js",
|
|
30
|
+
"exports": {
|
|
31
|
+
".": {
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"import": "./dist/index.js"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"sideEffects": false,
|
|
37
|
+
"name": "@cleverbrush/env",
|
|
38
|
+
"readme": "https://github.com/cleverbrush/framework/tree/master/libs/env#readme",
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "github:cleverbrush/framework"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"watch": "tsc --build tsconfig.build.json --watch",
|
|
45
|
+
"build": "tsup && tsc --project tsconfig.build.json --emitDeclarationOnly",
|
|
46
|
+
"clean": "rm -rf dist tsconfig.build.tsbuildinfo"
|
|
47
|
+
},
|
|
48
|
+
"type": "module",
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@types/node": "^25.4.0"
|
|
51
|
+
},
|
|
52
|
+
"types": "./dist/index.d.ts",
|
|
53
|
+
"version": "0.0.0-beta-20260414101326"
|
|
54
|
+
}
|