@geekmidas/envkit 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 +295 -0
- package/dist/EnvironmentParser-Dd6TbwJC.mjs +99 -0
- package/dist/EnvironmentParser-nZnZXM_Y.cjs +133 -0
- package/dist/EnvironmentParser.cjs +4 -0
- package/dist/EnvironmentParser.mjs +3 -0
- package/dist/index.cjs +3 -0
- package/dist/index.mjs +3 -0
- package/docs/api-reference.md +302 -0
- package/examples/basic-usage.ts +390 -0
- package/package.json +22 -0
- package/src/EnvironmentParser.ts +159 -0
- package/src/index.ts +1 -0
- package/sst-env.d.ts +9 -0
- package/tsdown.config.ts +3 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
# API Reference - @geekmidas/envkit
|
|
2
|
+
|
|
3
|
+
## Classes
|
|
4
|
+
|
|
5
|
+
### `EnvironmentParser`
|
|
6
|
+
|
|
7
|
+
The main class for creating configuration parsers.
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
class EnvironmentParser {
|
|
11
|
+
constructor(config: Record<string, unknown>)
|
|
12
|
+
create<T>(schemaBuilder: (get: GetFunction) => T): ConfigParser<T>
|
|
13
|
+
}
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
#### Constructor
|
|
17
|
+
|
|
18
|
+
Creates a new environment parser instance.
|
|
19
|
+
|
|
20
|
+
**Parameters:**
|
|
21
|
+
- `config: Record<string, unknown>` - The configuration object to parse (typically `process.env`)
|
|
22
|
+
|
|
23
|
+
**Example:**
|
|
24
|
+
```typescript
|
|
25
|
+
const parser = new EnvironmentParser(process.env);
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
#### Methods
|
|
29
|
+
|
|
30
|
+
##### `create<T>(schemaBuilder: (get: GetFunction) => T): ConfigParser<T>`
|
|
31
|
+
|
|
32
|
+
Creates a configuration parser with the specified schema.
|
|
33
|
+
|
|
34
|
+
**Parameters:**
|
|
35
|
+
- `schemaBuilder: (get: GetFunction) => T` - A function that receives a `get` function and returns the schema definition
|
|
36
|
+
|
|
37
|
+
**Returns:**
|
|
38
|
+
- `ConfigParser<T>` - A configuration parser instance
|
|
39
|
+
|
|
40
|
+
**Example:**
|
|
41
|
+
```typescript
|
|
42
|
+
const config = parser.create((get) => ({
|
|
43
|
+
port: get('PORT').string().transform(Number),
|
|
44
|
+
apiKey: get('API_KEY').string()
|
|
45
|
+
}));
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### `ConfigParser<T>`
|
|
49
|
+
|
|
50
|
+
The configuration parser returned by `EnvironmentParser.create()`.
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
class ConfigParser<T> {
|
|
54
|
+
parse(): T
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
#### Methods
|
|
59
|
+
|
|
60
|
+
##### `parse(): T`
|
|
61
|
+
|
|
62
|
+
Parses and validates the configuration according to the defined schema.
|
|
63
|
+
|
|
64
|
+
**Returns:**
|
|
65
|
+
- `T` - The parsed and validated configuration object
|
|
66
|
+
|
|
67
|
+
**Throws:**
|
|
68
|
+
- `ZodError` - If validation fails. The error contains all validation failures aggregated together.
|
|
69
|
+
|
|
70
|
+
**Example:**
|
|
71
|
+
```typescript
|
|
72
|
+
try {
|
|
73
|
+
const config = parser.create((get) => ({
|
|
74
|
+
port: get('PORT').string().transform(Number)
|
|
75
|
+
})).parse();
|
|
76
|
+
|
|
77
|
+
console.log(config.port); // number
|
|
78
|
+
} catch (error) {
|
|
79
|
+
if (error instanceof z.ZodError) {
|
|
80
|
+
console.error('Validation errors:', error.errors);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Type Definitions
|
|
86
|
+
|
|
87
|
+
### `GetFunction`
|
|
88
|
+
|
|
89
|
+
The function passed to the schema builder for accessing configuration values.
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
type GetFunction = (path: string) => ZodTypeAny
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Parameters:**
|
|
96
|
+
- `path: string` - The path to the configuration value. Supports:
|
|
97
|
+
- Simple paths: `'PORT'`, `'API_KEY'`
|
|
98
|
+
- Nested paths with dots: `'database.host'`, `'api.endpoints.users'`
|
|
99
|
+
|
|
100
|
+
**Returns:**
|
|
101
|
+
- `ZodTypeAny` - A Zod schema that will be used to validate the value at the specified path
|
|
102
|
+
|
|
103
|
+
**Usage:**
|
|
104
|
+
```typescript
|
|
105
|
+
const config = parser.create((get) => ({
|
|
106
|
+
// Simple path
|
|
107
|
+
port: get('PORT').string(),
|
|
108
|
+
|
|
109
|
+
// Nested path - looks for 'DATABASE_HOST' in config
|
|
110
|
+
database: {
|
|
111
|
+
host: get('DATABASE_HOST').string()
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
// With transformations
|
|
115
|
+
maxRetries: get('MAX_RETRIES').string().transform(Number),
|
|
116
|
+
|
|
117
|
+
// With validation
|
|
118
|
+
email: get('ADMIN_EMAIL').string().email(),
|
|
119
|
+
|
|
120
|
+
// With defaults
|
|
121
|
+
logLevel: get('LOG_LEVEL').string().default('info'),
|
|
122
|
+
|
|
123
|
+
// Optional values
|
|
124
|
+
debugMode: get('DEBUG').string().optional()
|
|
125
|
+
}));
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Error Handling
|
|
129
|
+
|
|
130
|
+
### Validation Errors
|
|
131
|
+
|
|
132
|
+
When validation fails, the parser throws a `ZodError` containing all validation failures:
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
import { z } from 'zod';
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
const config = parser.create((get) => ({
|
|
139
|
+
port: get('PORT').string().transform(Number),
|
|
140
|
+
apiKey: get('API_KEY').string().min(32),
|
|
141
|
+
email: get('ADMIN_EMAIL').string().email()
|
|
142
|
+
})).parse();
|
|
143
|
+
} catch (error) {
|
|
144
|
+
if (error instanceof z.ZodError) {
|
|
145
|
+
// error.errors is an array of all validation errors
|
|
146
|
+
error.errors.forEach(err => {
|
|
147
|
+
console.error(`${err.path.join('.')}: ${err.message}`);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// Example output:
|
|
151
|
+
// port: Expected string, received undefined
|
|
152
|
+
// apiKey: String must contain at least 32 character(s)
|
|
153
|
+
// email: Invalid email
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Error Aggregation
|
|
159
|
+
|
|
160
|
+
The parser collects all validation errors before throwing, allowing you to see all configuration problems at once:
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
// If multiple values are invalid, all errors are reported together
|
|
164
|
+
const config = parser.create((get) => ({
|
|
165
|
+
database: {
|
|
166
|
+
host: get('DB_HOST').string(), // Missing
|
|
167
|
+
port: get('DB_PORT').string(), // Missing
|
|
168
|
+
name: get('DB_NAME').string() // Missing
|
|
169
|
+
},
|
|
170
|
+
api: {
|
|
171
|
+
key: get('API_KEY').string(), // Missing
|
|
172
|
+
url: get('API_URL').url() // Invalid format
|
|
173
|
+
}
|
|
174
|
+
}));
|
|
175
|
+
|
|
176
|
+
// Throws ZodError with all 5 validation errors
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Path Resolution
|
|
180
|
+
|
|
181
|
+
The parser uses lodash's `get` and `set` functions for path resolution:
|
|
182
|
+
|
|
183
|
+
### Simple Paths
|
|
184
|
+
```typescript
|
|
185
|
+
// Configuration object
|
|
186
|
+
const config = {
|
|
187
|
+
PORT: '3000',
|
|
188
|
+
API_KEY: 'secret'
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
// Access with get()
|
|
192
|
+
get('PORT') // Looks for config.PORT
|
|
193
|
+
get('API_KEY') // Looks for config.API_KEY
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Nested Objects
|
|
197
|
+
```typescript
|
|
198
|
+
// Configuration object
|
|
199
|
+
const config = {
|
|
200
|
+
database: {
|
|
201
|
+
host: 'localhost',
|
|
202
|
+
port: '5432'
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
// Access with get()
|
|
207
|
+
get('database.host') // Returns 'localhost'
|
|
208
|
+
get('database.port') // Returns '5432'
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Environment Variable Mapping
|
|
212
|
+
When using `process.env`, nested paths are automatically mapped:
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
// These environment variables:
|
|
216
|
+
// DATABASE_HOST=localhost
|
|
217
|
+
// DATABASE_PORT=5432
|
|
218
|
+
|
|
219
|
+
// Can be accessed as:
|
|
220
|
+
const config = parser.create((get) => ({
|
|
221
|
+
database: {
|
|
222
|
+
host: get('DATABASE_HOST').string(), // Maps to DATABASE_HOST
|
|
223
|
+
port: get('DATABASE_PORT').string() // Maps to DATABASE_PORT
|
|
224
|
+
}
|
|
225
|
+
}));
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Zod Schema Integration
|
|
229
|
+
|
|
230
|
+
The parser returns Zod schemas, allowing you to use all Zod validation features:
|
|
231
|
+
|
|
232
|
+
### Transformations
|
|
233
|
+
```typescript
|
|
234
|
+
get('PORT').string().transform(Number)
|
|
235
|
+
get('ENABLED').string().transform(v => v === 'true')
|
|
236
|
+
get('TAGS').string().transform(v => v.split(','))
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Validations
|
|
240
|
+
```typescript
|
|
241
|
+
get('EMAIL').string().email()
|
|
242
|
+
get('URL').string().url()
|
|
243
|
+
get('PORT').string().transform(Number).int().min(1).max(65535)
|
|
244
|
+
get('NODE_ENV').enum(['development', 'production', 'test'])
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Refinements
|
|
248
|
+
```typescript
|
|
249
|
+
get('PASSWORD')
|
|
250
|
+
.string()
|
|
251
|
+
.min(8)
|
|
252
|
+
.refine(password => /[A-Z]/.test(password), {
|
|
253
|
+
message: 'Password must contain at least one uppercase letter'
|
|
254
|
+
})
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Complex Types
|
|
258
|
+
```typescript
|
|
259
|
+
// Arrays
|
|
260
|
+
get('ALLOWED_ORIGINS')
|
|
261
|
+
.string()
|
|
262
|
+
.transform(v => v.split(','))
|
|
263
|
+
.pipe(z.array(z.string().url()))
|
|
264
|
+
|
|
265
|
+
// Objects
|
|
266
|
+
get('CONFIG_JSON')
|
|
267
|
+
.string()
|
|
268
|
+
.transform(v => JSON.parse(v))
|
|
269
|
+
.pipe(z.object({
|
|
270
|
+
timeout: z.number(),
|
|
271
|
+
retries: z.number()
|
|
272
|
+
}))
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
## TypeScript Integration
|
|
276
|
+
|
|
277
|
+
The parser provides full type inference:
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
const parser = new EnvironmentParser(process.env);
|
|
281
|
+
|
|
282
|
+
const config = parser.create((get) => ({
|
|
283
|
+
server: {
|
|
284
|
+
port: get('PORT').string().transform(Number),
|
|
285
|
+
host: get('HOST').string().default('localhost')
|
|
286
|
+
},
|
|
287
|
+
features: {
|
|
288
|
+
auth: get('FEATURE_AUTH').string().transform(v => v === 'true'),
|
|
289
|
+
rateLimit: get('FEATURE_RATE_LIMIT').string().transform(v => v === 'true')
|
|
290
|
+
}
|
|
291
|
+
}));
|
|
292
|
+
|
|
293
|
+
const parsed = config.parse();
|
|
294
|
+
|
|
295
|
+
// TypeScript knows:
|
|
296
|
+
// parsed.server.port: number
|
|
297
|
+
// parsed.server.host: string
|
|
298
|
+
// parsed.features.auth: boolean
|
|
299
|
+
// parsed.features.rateLimit: boolean
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
The types are automatically inferred from the Zod schemas, providing complete type safety without manual type definitions.
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
import { EnvironmentParser } from '@geekmidas/envkit';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
const parser = new EnvironmentParser(process.env as {});
|
|
5
|
+
// Example 1: Basic configuration
|
|
6
|
+
export function basicExample() {
|
|
7
|
+
const config = parser.create((get) => ({
|
|
8
|
+
appName: get('APP_NAME').string().default('My App'),
|
|
9
|
+
port: get('PORT').string().transform(Number).default(3000),
|
|
10
|
+
isDevelopment: get('NODE_ENV')
|
|
11
|
+
.string()
|
|
12
|
+
.transform((env) => env === 'development'),
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
const result = config.parse();
|
|
16
|
+
|
|
17
|
+
return result;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Example 2: Database configuration
|
|
21
|
+
export function databaseExample() {
|
|
22
|
+
const config = parser.create((get) => ({
|
|
23
|
+
database: {
|
|
24
|
+
host: get('DB_HOST').string().default('localhost'),
|
|
25
|
+
port: get('DB_PORT').string().transform(Number).default(5432),
|
|
26
|
+
name: get('DB_NAME').string(),
|
|
27
|
+
user: get('DB_USER').string(),
|
|
28
|
+
password: get('DB_PASSWORD').string(),
|
|
29
|
+
ssl: get('DB_SSL')
|
|
30
|
+
.string()
|
|
31
|
+
.transform((v) => v === 'true')
|
|
32
|
+
.default(false),
|
|
33
|
+
poolSize: get('DB_POOL_SIZE')
|
|
34
|
+
.string()
|
|
35
|
+
.transform(Number)
|
|
36
|
+
.int()
|
|
37
|
+
.min(1)
|
|
38
|
+
.max(100)
|
|
39
|
+
.default(10),
|
|
40
|
+
},
|
|
41
|
+
}));
|
|
42
|
+
|
|
43
|
+
return config.parse();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Example 3: API configuration with validation
|
|
47
|
+
export function apiConfigExample() {
|
|
48
|
+
const config = parser.create((get) => ({
|
|
49
|
+
api: {
|
|
50
|
+
baseUrl: get('API_BASE_URL').url(),
|
|
51
|
+
key: get('API_KEY').string().min(32),
|
|
52
|
+
secret: get('API_SECRET').string().min(64),
|
|
53
|
+
timeout: get('API_TIMEOUT').string().transform(Number).default(5000),
|
|
54
|
+
retries: get('API_RETRIES').string().transform(Number).default(3),
|
|
55
|
+
endpoints: {
|
|
56
|
+
users: get('API_ENDPOINT_USERS').string().default('/api/v1/users'),
|
|
57
|
+
auth: get('API_ENDPOINT_AUTH').string().default('/api/v1/auth'),
|
|
58
|
+
products: get('API_ENDPOINT_PRODUCTS')
|
|
59
|
+
.string()
|
|
60
|
+
.default('/api/v1/products'),
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
}));
|
|
64
|
+
|
|
65
|
+
return config.parse();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Example 4: Feature flags and complex validation
|
|
69
|
+
export function featureFlagsExample() {
|
|
70
|
+
const config = parser.create((get) => ({
|
|
71
|
+
features: {
|
|
72
|
+
authentication: get('FEATURE_AUTH')
|
|
73
|
+
.string()
|
|
74
|
+
.transform((v) => v === 'true')
|
|
75
|
+
.default(true),
|
|
76
|
+
rateLimit: get('FEATURE_RATE_LIMIT')
|
|
77
|
+
.string()
|
|
78
|
+
.transform((v) => v === 'true')
|
|
79
|
+
.default(true),
|
|
80
|
+
cache: get('FEATURE_CACHE')
|
|
81
|
+
.string()
|
|
82
|
+
.transform((v) => v === 'true')
|
|
83
|
+
.default(false),
|
|
84
|
+
beta: {
|
|
85
|
+
enabled: get('FEATURE_BETA')
|
|
86
|
+
.string()
|
|
87
|
+
.transform((v) => v === 'true')
|
|
88
|
+
.default(false),
|
|
89
|
+
allowedUsers: get('FEATURE_BETA_USERS')
|
|
90
|
+
.string()
|
|
91
|
+
.transform((users) =>
|
|
92
|
+
users ? users.split(',').map((u) => u.trim()) : [],
|
|
93
|
+
)
|
|
94
|
+
.default([]),
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
rateLimit: {
|
|
98
|
+
windowMs: get('RATE_LIMIT_WINDOW_MS')
|
|
99
|
+
.string()
|
|
100
|
+
.transform(Number)
|
|
101
|
+
.default(60000),
|
|
102
|
+
maxRequests: get('RATE_LIMIT_MAX_REQUESTS')
|
|
103
|
+
.string()
|
|
104
|
+
.transform(Number)
|
|
105
|
+
.default(100),
|
|
106
|
+
},
|
|
107
|
+
}));
|
|
108
|
+
|
|
109
|
+
return config.parse();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Example 5: Email configuration with refinements
|
|
113
|
+
export function emailConfigExample() {
|
|
114
|
+
const config = parser.create((get) => ({
|
|
115
|
+
email: {
|
|
116
|
+
provider: get('EMAIL_PROVIDER').enum(['sendgrid', 'mailgun', 'ses']),
|
|
117
|
+
apiKey: get('EMAIL_API_KEY').string().min(20),
|
|
118
|
+
from: {
|
|
119
|
+
name: get('EMAIL_FROM_NAME').string().default('Support Team'),
|
|
120
|
+
address: get('EMAIL_FROM_ADDRESS').string().email(),
|
|
121
|
+
},
|
|
122
|
+
replyTo: get('EMAIL_REPLY_TO').string().email().optional(),
|
|
123
|
+
templates: {
|
|
124
|
+
welcome: get('EMAIL_TEMPLATE_WELCOME').string().uuid(),
|
|
125
|
+
resetPassword: get('EMAIL_TEMPLATE_RESET_PASSWORD').string().uuid(),
|
|
126
|
+
invoice: get('EMAIL_TEMPLATE_INVOICE').string().uuid().optional(),
|
|
127
|
+
},
|
|
128
|
+
smtp: {
|
|
129
|
+
host: get('SMTP_HOST').string().optional(),
|
|
130
|
+
port: get('SMTP_PORT').string().transform(Number).optional(),
|
|
131
|
+
secure: get('SMTP_SECURE')
|
|
132
|
+
.string()
|
|
133
|
+
.transform((v) => v === 'true')
|
|
134
|
+
.default(true),
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
}));
|
|
138
|
+
|
|
139
|
+
return config.parse();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Example 6: Multi-environment configuration
|
|
143
|
+
export function multiEnvironmentExample() {
|
|
144
|
+
const env = process.env.NODE_ENV || 'development';
|
|
145
|
+
|
|
146
|
+
// Different config sources based on environment
|
|
147
|
+
const configSource = {
|
|
148
|
+
...process.env,
|
|
149
|
+
// Override with environment-specific values
|
|
150
|
+
...(env === 'production'
|
|
151
|
+
? {
|
|
152
|
+
LOG_LEVEL: 'error',
|
|
153
|
+
DEBUG: 'false',
|
|
154
|
+
}
|
|
155
|
+
: {
|
|
156
|
+
LOG_LEVEL: 'debug',
|
|
157
|
+
DEBUG: 'true',
|
|
158
|
+
}),
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const parser = new EnvironmentParser(configSource);
|
|
162
|
+
|
|
163
|
+
const config = parser.create((get) => ({
|
|
164
|
+
env: get('NODE_ENV')
|
|
165
|
+
.enum(['development', 'staging', 'production'])
|
|
166
|
+
.default('development'),
|
|
167
|
+
logging: {
|
|
168
|
+
level: get('LOG_LEVEL').enum([
|
|
169
|
+
'trace',
|
|
170
|
+
'debug',
|
|
171
|
+
'info',
|
|
172
|
+
'warn',
|
|
173
|
+
'error',
|
|
174
|
+
'fatal',
|
|
175
|
+
]),
|
|
176
|
+
pretty: get('LOG_PRETTY')
|
|
177
|
+
.string()
|
|
178
|
+
.transform((v) => v === 'true')
|
|
179
|
+
.default(env !== 'production'),
|
|
180
|
+
debug: get('DEBUG')
|
|
181
|
+
.string()
|
|
182
|
+
.transform((v) => v === 'true'),
|
|
183
|
+
},
|
|
184
|
+
server: {
|
|
185
|
+
host: get('HOST').string().default('0.0.0.0'),
|
|
186
|
+
port: get('PORT')
|
|
187
|
+
.string()
|
|
188
|
+
.transform(Number)
|
|
189
|
+
.default(env === 'production' ? 80 : 3000),
|
|
190
|
+
cors: {
|
|
191
|
+
enabled: get('CORS_ENABLED')
|
|
192
|
+
.string()
|
|
193
|
+
.transform((v) => v === 'true')
|
|
194
|
+
.default(true),
|
|
195
|
+
origins: get('CORS_ORIGINS')
|
|
196
|
+
.string()
|
|
197
|
+
.transform((origins) => origins.split(',').map((o) => o.trim()))
|
|
198
|
+
.refine((origins) => origins.every((o) => o.startsWith('http')), {
|
|
199
|
+
message: 'All CORS origins must be valid URLs',
|
|
200
|
+
})
|
|
201
|
+
.default(['http://localhost:3000']),
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
}));
|
|
205
|
+
|
|
206
|
+
return config.parse();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Example 7: Error handling
|
|
210
|
+
export function errorHandlingExample() {
|
|
211
|
+
try {
|
|
212
|
+
const config = parser
|
|
213
|
+
.create((get) => ({
|
|
214
|
+
required: {
|
|
215
|
+
apiKey: get('API_KEY').string().min(32),
|
|
216
|
+
databaseUrl: get('DATABASE_URL').string().url(),
|
|
217
|
+
adminEmail: get('ADMIN_EMAIL').string().email(),
|
|
218
|
+
},
|
|
219
|
+
optional: {
|
|
220
|
+
sentryDsn: get('SENTRY_DSN').string().url().optional(),
|
|
221
|
+
slackWebhook: get('SLACK_WEBHOOK').string().url().optional(),
|
|
222
|
+
},
|
|
223
|
+
}))
|
|
224
|
+
.parse();
|
|
225
|
+
|
|
226
|
+
return config;
|
|
227
|
+
} catch (error) {
|
|
228
|
+
if (error instanceof z.ZodError) {
|
|
229
|
+
console.error('Configuration validation failed:');
|
|
230
|
+
console.error('Missing or invalid environment variables:');
|
|
231
|
+
|
|
232
|
+
error.errors.forEach((err) => {
|
|
233
|
+
const path = err.path.join('.');
|
|
234
|
+
console.error(` ${path}: ${err.message}`);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// In a real app, you might want to exit
|
|
238
|
+
process.exit(1);
|
|
239
|
+
}
|
|
240
|
+
throw error;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Example 8: Using with dotenv
|
|
245
|
+
export function dotenvExample() {
|
|
246
|
+
// Load .env file
|
|
247
|
+
require('dotenv').config();
|
|
248
|
+
|
|
249
|
+
const config = parser.create((get) => ({
|
|
250
|
+
app: {
|
|
251
|
+
name: get('APP_NAME').string(),
|
|
252
|
+
version: get('APP_VERSION')
|
|
253
|
+
.string()
|
|
254
|
+
.regex(/^\d+\.\d+\.\d+$/),
|
|
255
|
+
description: get('APP_DESCRIPTION').string().optional(),
|
|
256
|
+
},
|
|
257
|
+
secrets: {
|
|
258
|
+
jwtSecret: get('JWT_SECRET').string().min(64),
|
|
259
|
+
encryptionKey: get('ENCRYPTION_KEY').string().length(32),
|
|
260
|
+
apiKeys: get('API_KEYS')
|
|
261
|
+
.string()
|
|
262
|
+
.transform((keys) => keys.split(',').map((k) => k.trim()))
|
|
263
|
+
.pipe(z.array(z.string().min(32))),
|
|
264
|
+
},
|
|
265
|
+
}));
|
|
266
|
+
|
|
267
|
+
return config.parse();
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Example 9: Custom transformations
|
|
271
|
+
export function customTransformationsExample() {
|
|
272
|
+
const config = parser.create((get) => ({
|
|
273
|
+
// Parse JSON
|
|
274
|
+
features: get('FEATURES_JSON')
|
|
275
|
+
.string()
|
|
276
|
+
.transform((str) => JSON.parse(str))
|
|
277
|
+
.pipe(z.record(z.boolean())),
|
|
278
|
+
|
|
279
|
+
// Parse duration strings
|
|
280
|
+
timeouts: {
|
|
281
|
+
request: get('TIMEOUT_REQUEST')
|
|
282
|
+
.string()
|
|
283
|
+
.transform(parseDuration)
|
|
284
|
+
.default('30s'),
|
|
285
|
+
idle: get('TIMEOUT_IDLE').string().transform(parseDuration).default('5m'),
|
|
286
|
+
},
|
|
287
|
+
|
|
288
|
+
// Parse memory sizes
|
|
289
|
+
limits: {
|
|
290
|
+
memory: get('MEMORY_LIMIT')
|
|
291
|
+
.string()
|
|
292
|
+
.transform(parseMemorySize)
|
|
293
|
+
.default('512MB'),
|
|
294
|
+
upload: get('UPLOAD_LIMIT')
|
|
295
|
+
.string()
|
|
296
|
+
.transform(parseMemorySize)
|
|
297
|
+
.default('10MB'),
|
|
298
|
+
},
|
|
299
|
+
|
|
300
|
+
// Complex array parsing
|
|
301
|
+
allowedDomains: get('ALLOWED_DOMAINS')
|
|
302
|
+
.string()
|
|
303
|
+
.transform((domains) =>
|
|
304
|
+
domains
|
|
305
|
+
.split(',')
|
|
306
|
+
.map((d) => d.trim())
|
|
307
|
+
.filter(Boolean),
|
|
308
|
+
)
|
|
309
|
+
.pipe(z.array(z.string().regex(/^[a-z0-9.-]+$/i))),
|
|
310
|
+
}));
|
|
311
|
+
|
|
312
|
+
return config.parse();
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Helper functions for custom transformations
|
|
316
|
+
function parseDuration(duration: string): number {
|
|
317
|
+
const match = duration.match(/^(\d+)(ms|s|m|h)$/);
|
|
318
|
+
if (!match) throw new Error(`Invalid duration: ${duration}`);
|
|
319
|
+
|
|
320
|
+
const [, value, unit] = match;
|
|
321
|
+
const multipliers = { ms: 1, s: 1000, m: 60000, h: 3600000 };
|
|
322
|
+
return parseInt(value) * multipliers[unit as keyof typeof multipliers];
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function parseMemorySize(size: string): number {
|
|
326
|
+
const match = size.match(/^(\d+)(B|KB|MB|GB)$/i);
|
|
327
|
+
if (!match) throw new Error(`Invalid memory size: ${size}`);
|
|
328
|
+
|
|
329
|
+
const [, value, unit] = match;
|
|
330
|
+
const multipliers = { B: 1, KB: 1024, MB: 1048576, GB: 1073741824 };
|
|
331
|
+
return (
|
|
332
|
+
parseInt(value) *
|
|
333
|
+
multipliers[unit.toUpperCase() as keyof typeof multipliers]
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Example 10: Type-safe configuration module
|
|
338
|
+
// config.ts - This is how you'd typically use it in a real app
|
|
339
|
+
export const loadConfig = () => {
|
|
340
|
+
const parser = new EnvironmentParser(process.env as any);
|
|
341
|
+
|
|
342
|
+
return parser
|
|
343
|
+
.create((get) => ({
|
|
344
|
+
app: {
|
|
345
|
+
name: get('APP_NAME').string().default('My Application'),
|
|
346
|
+
env: get('NODE_ENV')
|
|
347
|
+
.enum(['development', 'staging', 'production'])
|
|
348
|
+
.default('development'),
|
|
349
|
+
port: get('PORT').string().transform(Number).default(3000),
|
|
350
|
+
host: get('HOST').string().default('localhost'),
|
|
351
|
+
},
|
|
352
|
+
database: {
|
|
353
|
+
url: get('DATABASE_URL').string().url(),
|
|
354
|
+
maxConnections: get('DB_MAX_CONNECTIONS')
|
|
355
|
+
.string()
|
|
356
|
+
.transform(Number)
|
|
357
|
+
.default(10),
|
|
358
|
+
ssl: get('DB_SSL')
|
|
359
|
+
.string()
|
|
360
|
+
.transform((v) => v === 'true')
|
|
361
|
+
.default(false),
|
|
362
|
+
},
|
|
363
|
+
redis: {
|
|
364
|
+
url: get('REDIS_URL').string().url().optional(),
|
|
365
|
+
ttl: get('REDIS_TTL').string().transform(Number).default(3600),
|
|
366
|
+
},
|
|
367
|
+
auth: {
|
|
368
|
+
jwtSecret: get('JWT_SECRET').string().min(32),
|
|
369
|
+
jwtExpiry: get('JWT_EXPIRY').string().default('7d'),
|
|
370
|
+
bcryptRounds: get('BCRYPT_ROUNDS')
|
|
371
|
+
.string()
|
|
372
|
+
.transform(Number)
|
|
373
|
+
.default(10),
|
|
374
|
+
},
|
|
375
|
+
features: {
|
|
376
|
+
signups: get('FEATURE_SIGNUPS')
|
|
377
|
+
.string()
|
|
378
|
+
.transform((v) => v === 'true')
|
|
379
|
+
.default(true),
|
|
380
|
+
subscriptions: get('FEATURE_SUBSCRIPTIONS')
|
|
381
|
+
.string()
|
|
382
|
+
.transform((v) => v === 'true')
|
|
383
|
+
.default(false),
|
|
384
|
+
},
|
|
385
|
+
}))
|
|
386
|
+
.parse();
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
// Export the config for use throughout the app
|
|
390
|
+
export const config = loadConfig();
|
package/package.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@geekmidas/envkit",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./src/index.ts"
|
|
8
|
+
},
|
|
9
|
+
"publishConfig": {
|
|
10
|
+
"registry": "https://registry.npmjs.org/",
|
|
11
|
+
"access": "public"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"zod": "~3.25.67",
|
|
15
|
+
"lodash.set": "~4.3.2",
|
|
16
|
+
"lodash.get": "~4.4.2"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@types/lodash.set": "~4.3.9",
|
|
20
|
+
"@types/lodash.get": "~4.4.9"
|
|
21
|
+
}
|
|
22
|
+
}
|