@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.
@@ -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
+ }