molex-env 0.2.0 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +723 -114
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -3,181 +3,790 @@
3
3
  [![npm](https://img.shields.io/npm/v/molex-env)](https://www.npmjs.com/package/molex-env)
4
4
  [![downloads](https://img.shields.io/npm/dm/molex-env)](https://www.npmjs.com/package/molex-env)
5
5
  [![license](https://img.shields.io/npm/l/molex-env)](LICENSE)
6
- [![deps](https://img.shields.io/badge/deps-0-brightgreen)](package.json)
6
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D14-brightgreen.svg)](https://nodejs.org)
7
+ [![deps](https://img.shields.io/badge/dependencies-0-success.svg)](package.json)
7
8
 
8
- > Native .menv loader with profiles, typing, origin tracking, and optional reload.
9
+ > **Native .menv environment loader with profile support, typed parsing, origin tracking, and live reload. Zero dependencies.**
9
10
 
10
11
  ## Features
11
- - Deterministic profile merging - Predictable resolution across base and profile files
12
- - Typed parsing - Booleans, numbers, JSON, and dates with optional casting control
13
- - Strict mode - Reject unknown keys, duplicates, and invalid lines
14
- - Origin tracking - See the file and line for every value
15
- - Immutable config - Optional deep-freeze for safety
16
- - Live reload - Watch files and reload on change
17
-
18
- ## Install
12
+
13
+ - **Zero dependencies** - Pure Node.js implementation
14
+ - **Profile support** - Environment-specific configs (dev, prod, staging)
15
+ - **Type-safe parsing** - Automatic conversion of booleans, numbers, JSON, and dates
16
+ - **Strict validation** - Schema enforcement with required fields and type checking
17
+ - **Origin tracking** - Know exactly which file and line each value came from
18
+ - **Immutable config** - Deep-freeze protection prevents accidental modifications
19
+ - **Live reload** - Watch mode automatically reloads on file changes
20
+ - **Deterministic merging** - Predictable cascading from base to profile files
21
+
22
+ ## Installation
23
+
19
24
  ```bash
20
25
  npm install molex-env
21
26
  ```
22
27
 
23
- ## Quick start
24
- ```js
25
- // simplest usage
26
- require('molex-env').load();
28
+ ## Quick Start
27
29
 
28
- // more detailed usage
30
+ ```javascript
29
31
  const { load } = require('molex-env');
30
32
 
33
+ // Simplest usage - loads .menv files and attaches to process.menv
34
+ require('molex-env').load();
35
+ console.log(process.menv.PORT); // Access typed values
36
+
37
+ // With profile and schema validation
31
38
  const result = load({
32
39
  profile: 'prod',
33
40
  strict: true,
34
41
  schema: {
35
42
  PORT: 'number',
36
43
  DEBUG: 'boolean',
37
- SERVICE_URL: { type: 'string', required: true }
44
+ SERVICE_URL: { type: 'string', required: true },
45
+ METADATA: 'json'
38
46
  }
39
47
  });
40
48
 
41
- console.log(result.parsed.PORT);
42
- console.log(result.origins.SERVICE_URL);
43
- console.log(process.menv.PORT);
49
+ console.log(result.parsed.PORT); // 3000 (number)
50
+ console.log(result.parsed.DEBUG); // false (boolean)
51
+ console.log(result.origins.SERVICE_URL); // { file: '.menv', line: 3 }
52
+ console.log(process.menv.METADATA.region); // 'us-east-1' (parsed JSON)
44
53
  ```
45
54
 
46
55
  ## Setup
47
- 1) Add one or more .menv files in your project root.
48
- 2) Call `load()` during startup.
49
- 3) Optionally enable profile-specific files (e.g. `prod`).
50
56
 
51
- ## File format
57
+ **1. Create .menv files in your project root:**
58
+
59
+ ```env
60
+ # .menv (base configuration)
61
+ PORT=3000
62
+ DEBUG=false
63
+ SERVICE_URL=https://api.example.com
64
+ DATABASE_URL=postgres://localhost:5432/myapp
65
+ ```
66
+
67
+ **2. Add profile-specific overrides (optional):**
68
+
69
+ ```env
70
+ # .menv.prod (production overrides)
71
+ DEBUG=false
72
+ SERVICE_URL=https://api.production.com
73
+ DATABASE_URL=postgres://prod-server:5432/myapp
74
+ ```
75
+
76
+ ```env
77
+ # .menv.local (local machine overrides - add to .gitignore)
78
+ DEBUG=true
79
+ DATABASE_URL=postgres://localhost:5432/myapp_dev
80
+ ```
81
+
82
+ **3. Load during application startup:**
83
+
84
+ ```javascript
85
+ // Load with production profile
86
+ require('molex-env').load({ profile: 'prod' });
87
+
88
+ // Now use your typed config
89
+ const app = express();
90
+ app.listen(process.menv.PORT);
91
+ ```
92
+
93
+ ## File Format
94
+
95
+ molex-env supports simple key=value syntax with automatic type detection:
96
+
52
97
  ```env
53
98
  # Comments start with #
99
+ # Strings (quotes are optional)
100
+ SERVICE_URL=https://api.example.com
101
+ API_KEY="secret-key-123"
102
+
103
+ # Numbers (integers and floats)
54
104
  PORT=3000
105
+ TIMEOUT=30.5
106
+
107
+ # Booleans (case-insensitive)
55
108
  DEBUG=true
56
- SERVICE_URL="https://api.example.com"
57
- METADATA={"region":"us-east-1"}
109
+ ENABLE_CACHE=FALSE
110
+
111
+ # JSON objects and arrays
112
+ METADATA={"region":"us-east-1","tier":"premium"}
113
+ ALLOWED_IPS=["192.168.1.1","10.0.0.1"]
114
+
115
+ # Dates (ISO 8601 format)
58
116
  START_DATE=2026-02-02
117
+ EXPIRES_AT=2026-12-31T23:59:59Z
118
+
119
+ # Empty values
120
+ OPTIONAL_KEY=
59
121
  ```
60
122
 
61
- ## File precedence
62
- 1) .menv
63
- 2) .menv.local
64
- 3) .menv.{profile}
65
- 4) .menv.{profile}.local
66
-
67
- ## API
68
-
69
- ## load(options)
70
- Load, merge, parse, and validate .menv files.
71
-
72
- ## Options
73
- - cwd: base directory (default: process.cwd())
74
- - profile: profile name for .menv.{profile}
75
- - files: custom file list (absolute or relative to cwd)
76
- - schema: allowed keys, types, defaults, required
77
- - strict: reject unknown keys, duplicates, and invalid lines
78
- - cast: true | false | { boolean, number, json, date }
79
- - exportEnv: write values to process.env
80
- - override: override existing process.env values
81
- - attach: attach parsed values to process.menv (default true)
82
- - freeze: deep-freeze parsed config (default true)
83
- - onWarning: function(info) for non-strict duplicates
84
-
85
- ## Returns
86
- ```js
123
+ ## File Precedence
124
+
125
+ Files are loaded and merged in this order (later files override earlier ones):
126
+
127
+ 1. `.menv` - Base configuration (committed to git)
128
+ 2. `.menv.local` - Local overrides (ignored by git)
129
+ 3. `.menv.{profile}` - Profile-specific config (e.g., `.menv.prod`)
130
+ 4. `.menv.{profile}.local` - Profile + local overrides (e.g., `.menv.prod.local`)
131
+
132
+ **Example with `profile: 'prod'`:**
133
+ ```
134
+ .menv → PORT=3000, DEBUG=true
135
+ .menv.local → (overrides) DEBUG=false
136
+ .menv.prod → (overrides) PORT=8080
137
+ .menv.prod.local (overrides) PORT=9000
138
+ Final result: PORT=9000, DEBUG=false
139
+ ```
140
+
141
+ ## API Reference
142
+
143
+ ### `load(options)` `Object`
144
+
145
+ Load, merge, parse, and validate .menv files. This is the primary method you'll use.
146
+
147
+ **Options:**
148
+
149
+ | Option | Type | Default | Description |
150
+ |--------|------|---------|-------------|
151
+ | `cwd` | `string` | `process.cwd()` | Base directory to resolve files from |
152
+ | `profile` | `string` | `undefined` | Profile name for `.menv.{profile}` files |
153
+ | `files` | `Array<string>` | Auto-detected | Custom file list (absolute or relative to cwd) |
154
+ | `schema` | `Object` | `{}` | Schema definition for validation and typing |
155
+ | `strict` | `boolean` | `false` | Reject unknown keys, duplicates, and invalid lines |
156
+ | `cast` | `boolean\|Object` | `true` | Enable/disable type casting (see Type Casting) |
157
+ | `exportEnv` | `boolean` | `false` | Write parsed values to `process.env` |
158
+ | `override` | `boolean` | `false` | Override existing `process.env` values |
159
+ | `attach` | `boolean` | `true` | Attach parsed values to `process.menv` |
160
+ | `freeze` | `boolean` | `true` | Deep-freeze the parsed config object |
161
+ | `onWarning` | `Function` | `undefined` | Callback for non-strict warnings |
162
+
163
+ **Returns:**
164
+
165
+ ```javascript
87
166
  {
88
- parsed, // typed values
89
- raw, // raw strings
90
- origins, // { KEY: { file, line } }
91
- files // list of resolved files
167
+ parsed: Object, // Typed configuration values
168
+ raw: Object, // Raw string values before parsing
169
+ origins: Object, // Source tracking: { KEY: { file, line } }
170
+ files: Array // List of resolved file paths
92
171
  }
93
172
  ```
94
173
 
95
- ## parse(text, options)
96
- Parse a string of .menv content using the same typing rules as `load()`.
174
+ **Examples:**
97
175
 
98
- ## Options
99
- - schema
100
- - strict
101
- - cast
102
- - freeze
176
+ ```javascript
177
+ // Basic usage
178
+ const result = load();
179
+ console.log(result.parsed);
103
180
 
104
- ## watch(options, onChange)
105
- Watch resolved files and reload on change.
181
+ // With profile
182
+ const result = load({ profile: 'production' });
106
183
 
107
- ## Arguments
108
- - options: same as `load()`
109
- - onChange: function(err, result)
184
+ // Custom directory
185
+ const result = load({ cwd: '/app/config' });
110
186
 
111
- ## Schema
112
- You can define simple types or richer objects per key.
187
+ // Export to process.env
188
+ load({ exportEnv: true });
189
+ console.log(process.env.PORT); // Now available in process.env
113
190
 
114
- ```js
115
- const schema = {
116
- PORT: { type: 'number', default: 3000 },
117
- DEBUG: { type: 'boolean', default: false },
118
- METADATA: { type: 'json' },
119
- START_DATE: { type: 'date' },
120
- SERVICE_URL: { type: 'string', required: true }
121
- };
191
+ // Custom files
192
+ load({
193
+ files: ['config/.menv', 'config/.menv.custom'],
194
+ schema: { PORT: 'number', HOST: 'string' }
195
+ });
196
+
197
+ // Override existing environment variables
198
+ load({
199
+ exportEnv: true,
200
+ override: true // Will replace existing process.env values
201
+ });
122
202
  ```
123
203
 
124
- ## Schema options per key
125
- - type: string | boolean | number | json | date
126
- - default: value used when key is missing
127
- - required: true | false
204
+ ---
205
+
206
+ ### `parse(text, options)` `Object`
128
207
 
129
- ## Typing rules
130
- - boolean: true/false (case-insensitive)
131
- - number: integer or float
132
- - json: JSON.parse on the value
133
- - date: Date.parse on the value
208
+ Parse a string of .menv content without loading files. Useful for testing or processing environment strings from other sources.
134
209
 
135
- ## Strict mode
136
- When `strict` is true, the loader rejects:
137
- - unknown keys not in `schema`
138
- - duplicate keys across files
139
- - invalid lines or parse errors
210
+ **Options:**
140
211
 
141
- ## Non-strict mode
142
- Duplicates are allowed and `onWarning(info)` is called with:
143
- ```js
212
+ | Option | Type | Default | Description |
213
+ |--------|------|---------|-------------|
214
+ | `schema` | `Object` | `{}` | Schema definition for validation |
215
+ | `strict` | `boolean` | `false` | Enable strict validation |
216
+ | `cast` | `boolean\|Object` | `true` | Enable/disable type casting |
217
+ | `freeze` | `boolean` | `true` | Deep-freeze the result |
218
+
219
+ **Returns:**
220
+
221
+ ```javascript
144
222
  {
145
- key,
146
- previous: { file, line },
147
- next: { file, line }
223
+ parsed: Object, // Typed values
224
+ raw: Object, // Raw string values
225
+ origins: Object // Line numbers: { KEY: { line } }
226
+ }
227
+ ```
228
+
229
+ **Example:**
230
+
231
+ ```javascript
232
+ const { parse } = require('molex-env');
233
+
234
+ const envContent = `
235
+ PORT=3000
236
+ DEBUG=true
237
+ METADATA={"env":"production"}
238
+ `;
239
+
240
+ const result = parse(envContent, {
241
+ schema: {
242
+ PORT: 'number',
243
+ DEBUG: 'boolean',
244
+ METADATA: 'json'
245
+ },
246
+ strict: true
247
+ });
248
+
249
+ console.log(result.parsed.PORT); // 3000 (number)
250
+ console.log(result.parsed.DEBUG); // true (boolean)
251
+ console.log(result.parsed.METADATA); // { env: 'production' } (object)
252
+ console.log(result.origins.PORT); // { line: 2 }
253
+ ```
254
+
255
+ ---
256
+
257
+ ### `watch(options, onChange)`
258
+
259
+ Watch .menv files and reload automatically when they change. Perfect for development environments.
260
+
261
+ **Arguments:**
262
+
263
+ - `options` - Same options as `load()`
264
+ - `onChange(error, result)` - Callback fired on file changes
265
+
266
+ **Example:**
267
+
268
+ ```javascript
269
+ const { watch } = require('molex-env');
270
+
271
+ // Watch with callback
272
+ watch({ profile: 'dev', strict: true }, (err, result) => {
273
+ if (err) {
274
+ console.error('Config reload failed:', err.message);
275
+ return;
276
+ }
277
+
278
+ console.log('Config reloaded!');
279
+ console.log('New PORT:', result.parsed.PORT);
280
+
281
+ // Restart your server or update app state here
282
+ if (global.server) {
283
+ global.server.close();
284
+ global.server = startServer(result.parsed);
285
+ }
286
+ });
287
+
288
+ console.log('Watching for .menv file changes...');
289
+ ```
290
+
291
+ **Example with Express hot reload:**
292
+
293
+ ```javascript
294
+ const express = require('express');
295
+ const { watch } = require('molex-env');
296
+
297
+ let server;
298
+
299
+ function startServer(config) {
300
+ const app = express();
301
+ app.get('/', (req, res) => res.json({ port: config.PORT }));
302
+ return app.listen(config.PORT, () => {
303
+ console.log(`Server running on port ${config.PORT}`);
304
+ });
148
305
  }
306
+
307
+ // Start with initial config
308
+ const initial = require('molex-env').load({ profile: 'dev' });
309
+ server = startServer(initial.parsed);
310
+
311
+ // Watch for changes
312
+ watch({ profile: 'dev' }, (err, result) => {
313
+ if (!err && result.parsed.PORT !== initial.parsed.PORT) {
314
+ console.log('Port changed, restarting...');
315
+ server.close(() => {
316
+ server = startServer(result.parsed);
317
+ });
318
+ }
319
+ });
320
+ ```
321
+
322
+ ---
323
+
324
+ ## Schema Definition
325
+
326
+ Schemas provide type validation, required field enforcement, and default values.
327
+
328
+ ### Schema Formats
329
+
330
+ **Simple string format:**
331
+ ```javascript
332
+ const schema = {
333
+ PORT: 'number',
334
+ DEBUG: 'boolean',
335
+ SERVICE_URL: 'string',
336
+ METADATA: 'json',
337
+ START_DATE: 'date'
338
+ };
149
339
  ```
150
340
 
151
- ## Examples
341
+ **Object format with options:**
342
+ ```javascript
343
+ const schema = {
344
+ PORT: {
345
+ type: 'number',
346
+ default: 3000
347
+ },
348
+ DEBUG: {
349
+ type: 'boolean',
350
+ default: false
351
+ },
352
+ SERVICE_URL: {
353
+ type: 'string',
354
+ required: true // Will throw error if missing
355
+ },
356
+ METADATA: {
357
+ type: 'json',
358
+ default: { region: 'us-east-1' }
359
+ },
360
+ START_DATE: {
361
+ type: 'date'
362
+ }
363
+ };
364
+ ```
152
365
 
153
- ## Custom files
154
- ```js
366
+ ### Schema Options
367
+
368
+ | Option | Type | Description |
369
+ |--------|------|-------------|
370
+ | `type` | `string` | Value type: `'string'`, `'boolean'`, `'number'`, `'json'`, or `'date'` |
371
+ | `default` | `any` | Default value if key is missing (must match type) |
372
+ | `required` | `boolean` | If `true`, throws error when key is missing |
373
+
374
+ ### Type Parsing Rules
375
+
376
+ | Type | Description | Examples |
377
+ |------|-------------|----------|
378
+ | `string` | Plain text (default) | `"hello"`, `hello`, `"123"` |
379
+ | `boolean` | Case-insensitive true/false | `true`, `TRUE`, `false`, `False` |
380
+ | `number` | Integer or float | `3000`, `3.14`, `-42`, `1e6` |
381
+ | `json` | Valid JSON string | `{"key":"value"}`, `[1,2,3]`, `null` |
382
+ | `date` | ISO 8601 date string | `2026-02-02`, `2026-02-02T10:30:00Z` |
383
+
384
+ **Example with all types:**
385
+
386
+ ```javascript
155
387
  load({
156
- files: ['config/.menv', 'config/.menv.local'],
157
- schema: { PORT: 'number' }
388
+ schema: {
389
+ // String (explicit)
390
+ API_KEY: { type: 'string', required: true },
391
+
392
+ // Boolean
393
+ DEBUG: { type: 'boolean', default: false },
394
+ ENABLE_LOGGING: 'boolean',
395
+
396
+ // Number
397
+ PORT: { type: 'number', default: 3000 },
398
+ TIMEOUT: 'number',
399
+ RETRY_COUNT: { type: 'number', default: 3 },
400
+
401
+ // JSON (objects and arrays)
402
+ METADATA: { type: 'json', default: {} },
403
+ ALLOWED_HOSTS: 'json', // Can be array or object
404
+
405
+ // Date
406
+ START_DATE: 'date',
407
+ EXPIRES_AT: { type: 'date', required: true }
408
+ },
409
+ strict: true
158
410
  });
159
411
  ```
160
412
 
161
- ## Disable casting
162
- ```js
413
+ ---
414
+
415
+ ## Type Casting
416
+
417
+ Control how values are automatically converted from strings.
418
+
419
+ ### Enable/Disable All Casting
420
+
421
+ ```javascript
422
+ // Default: all types are cast
423
+ load({ cast: true });
424
+
425
+ // Disable all casting (everything stays as strings)
163
426
  load({ cast: false });
427
+ console.log(typeof process.menv.PORT); // 'string' (was '3000')
164
428
  ```
165
429
 
166
- ## Freeze control
167
- ```js
168
- load({ freeze: false });
430
+ ### Selective Casting
431
+
432
+ ```javascript
433
+ // Only cast specific types
434
+ load({
435
+ cast: {
436
+ boolean: true, // Cast booleans
437
+ number: true, // Cast numbers
438
+ json: false, // Keep JSON as strings
439
+ date: false // Keep dates as strings
440
+ }
441
+ });
169
442
  ```
170
443
 
171
- ## Notes
172
- - Use .menv.local for machine-specific values
173
- - Use strict mode to detect surprises early
174
- - Parsed values are attached to process.menv by default (disable with attach: false)
444
+ ---
445
+
446
+ ## Strict Mode
447
+
448
+ Strict mode provides rigorous validation to catch configuration errors early.
449
+
450
+ ### What Strict Mode Enforces
451
+
452
+ When `strict: true`:
453
+ - ❌ **Unknown keys** - Keys not in schema are rejected
454
+ - ❌ **Duplicate keys** - Same key in multiple files throws error
455
+ - ❌ **Invalid lines** - Malformed lines throw errors
456
+ - ❌ **Type mismatches** - Values that can't be parsed as specified type
457
+
458
+ **Example:**
459
+
460
+ ```javascript
461
+ // .menv file
462
+ PORT=3000
463
+ DEBUG=true
464
+ UNKNOWN_KEY=value // ← Not in schema
465
+
466
+ load({
467
+ schema: {
468
+ PORT: 'number',
469
+ DEBUG: 'boolean'
470
+ },
471
+ strict: true // Will throw error about UNKNOWN_KEY
472
+ });
473
+ ```
474
+
475
+ ### Non-Strict Mode (Default)
476
+
477
+ Without strict mode:
478
+ - ✅ Unknown keys are allowed and parsed
479
+ - ✅ Duplicates override (later files win)
480
+ - ✅ Invalid lines are skipped
481
+ - ⚠️ Warnings can be logged via `onWarning` callback
482
+
483
+ **Example with warning handler:**
484
+
485
+ ```javascript
486
+ load({
487
+ schema: { PORT: 'number' },
488
+ strict: false,
489
+ onWarning: (info) => {
490
+ console.warn(`Warning: ${info.key} redefined`);
491
+ console.warn(` Previous: ${info.previous.file}:${info.previous.line}`);
492
+ console.warn(` New: ${info.next.file}:${info.next.line}`);
493
+ }
494
+ });
495
+ ```
496
+
497
+ ---
498
+
499
+ ## Origin Tracking
500
+
501
+ Every configuration value includes its source file and line number, making debugging easy.
502
+
503
+ **Example:**
504
+
505
+ ```javascript
506
+ const result = load({ profile: 'prod' });
507
+
508
+ console.log(result.origins);
509
+ // {
510
+ // PORT: { file: '.menv', line: 1 },
511
+ // DEBUG: { file: '.menv.local', line: 2 },
512
+ // SERVICE_URL: { file: '.menv.prod', line: 3 }
513
+ // }
514
+
515
+ // Debug where a value came from
516
+ const portOrigin = result.origins.PORT;
517
+ console.log(`PORT is defined in ${portOrigin.file} at line ${portOrigin.line}`);
518
+ ```
519
+
520
+ **Practical debugging use case:**
521
+
522
+ ```javascript
523
+ const { load } = require('molex-env');
524
+
525
+ const result = load({ profile: 'prod', strict: true });
526
+
527
+ // Verify configuration sources before deployment
528
+ Object.keys(result.parsed).forEach(key => {
529
+ const origin = result.origins[key];
530
+ console.log(`${key}=${result.parsed[key]} (from ${origin.file}:${origin.line})`);
531
+ });
532
+
533
+ // Example output:
534
+ // PORT=8080 (from .menv.prod:1)
535
+ // DEBUG=false (from .menv.prod:2)
536
+ // DATABASE_URL=postgres://prod:5432/db (from .menv.prod.local:3)
537
+ ```
538
+
539
+ ---
540
+
541
+ ## Advanced Examples
542
+
543
+ ### Complete Production Setup
544
+
545
+ ```javascript
546
+ const { load } = require('molex-env');
547
+
548
+ const config = load({
549
+ profile: process.env.NODE_ENV || 'development',
550
+ strict: true,
551
+ exportEnv: true,
552
+ schema: {
553
+ // Server config
554
+ NODE_ENV: { type: 'string', required: true },
555
+ PORT: { type: 'number', default: 3000 },
556
+ HOST: { type: 'string', default: '0.0.0.0' },
557
+
558
+ // Database
559
+ DATABASE_URL: { type: 'string', required: true },
560
+ DB_POOL_SIZE: { type: 'number', default: 10 },
561
+
562
+ // Redis
563
+ REDIS_URL: { type: 'string', required: true },
564
+ REDIS_TTL: { type: 'number', default: 3600 },
565
+
566
+ // Feature flags
567
+ ENABLE_CACHE: { type: 'boolean', default: true },
568
+ ENABLE_METRICS: { type: 'boolean', default: false },
569
+
570
+ // API config
571
+ API_KEYS: { type: 'json', required: true },
572
+ RATE_LIMITS: { type: 'json', default: { default: 100 } },
573
+
574
+ // Dates
575
+ MAINTENANCE_START: 'date',
576
+ MAINTENANCE_END: 'date'
577
+ }
578
+ });
579
+
580
+ console.log('Configuration loaded successfully');
581
+ console.log(`Running in ${config.parsed.NODE_ENV} mode on port ${config.parsed.PORT}`);
582
+
583
+ module.exports = config.parsed;
584
+ ```
585
+
586
+ ### Dynamic Profile from Command Line
587
+
588
+ ```javascript
589
+ // Load profile from CLI argument
590
+ // Usage: node app.js --env=staging
591
+
592
+ const args = process.argv.slice(2);
593
+ const envArg = args.find(arg => arg.startsWith('--env='));
594
+ const profile = envArg ? envArg.split('=')[1] : 'development';
595
+
596
+ require('molex-env').load({
597
+ profile,
598
+ strict: true,
599
+ schema: {
600
+ PORT: 'number',
601
+ DATABASE_URL: { type: 'string', required: true }
602
+ }
603
+ });
604
+
605
+ console.log(`Started with profile: ${profile}`);
606
+ console.log(`PORT: ${process.menv.PORT}`);
607
+ ```
608
+
609
+ ### Development with Hot Reload
610
+
611
+ ```javascript
612
+ const { watch } = require('molex-env');
613
+
614
+ let currentConfig;
615
+
616
+ watch({
617
+ profile: 'dev',
618
+ schema: {
619
+ PORT: 'number',
620
+ DEBUG: 'boolean',
621
+ API_URL: 'string'
622
+ }
623
+ }, (err, result) => {
624
+ if (err) {
625
+ console.error('Config error:', err.message);
626
+ return;
627
+ }
628
+
629
+ const changed = [];
630
+ if (!currentConfig) {
631
+ console.log('Initial config loaded');
632
+ } else {
633
+ // Detect what changed
634
+ Object.keys(result.parsed).forEach(key => {
635
+ if (currentConfig[key] !== result.parsed[key]) {
636
+ changed.push(`${key}: ${currentConfig[key]} → ${result.parsed[key]}`);
637
+ }
638
+ });
639
+
640
+ if (changed.length > 0) {
641
+ console.log('Config updated:', changed.join(', '));
642
+ }
643
+ }
644
+
645
+ currentConfig = result.parsed;
646
+ });
647
+ ```
648
+
649
+ ### Validation and Error Handling
650
+
651
+ ```javascript
652
+ const { load } = require('molex-env');
653
+
654
+ try {
655
+ const config = load({
656
+ profile: 'prod',
657
+ strict: true,
658
+ schema: {
659
+ PORT: { type: 'number', required: true },
660
+ DATABASE_URL: { type: 'string', required: true },
661
+ REDIS_URL: { type: 'string', required: true }
662
+ }
663
+ });
664
+
665
+ // Validate ranges
666
+ if (config.parsed.PORT < 1024 || config.parsed.PORT > 65535) {
667
+ throw new Error(`Invalid PORT: ${config.parsed.PORT} (must be 1024-65535)`);
668
+ }
669
+
670
+ // Validate URLs
671
+ if (!config.parsed.DATABASE_URL.startsWith('postgres://')) {
672
+ throw new Error('DATABASE_URL must be a PostgreSQL connection string');
673
+ }
674
+
675
+ console.log('Configuration validated successfully');
676
+
677
+ } catch (err) {
678
+ console.error('Configuration error:', err.message);
679
+ process.exit(1);
680
+ }
681
+ ```
682
+
683
+ ---
684
+
685
+ ## Best Practices
686
+
687
+ ### Git Configuration
688
+
689
+ Add to `.gitignore`:
690
+ ```gitignore
691
+ # Keep base configs in git
692
+ # .menv
693
+ # .menv.dev
694
+ # .menv.prod
695
+
696
+ # Ignore local overrides (machine-specific, secrets)
697
+ .menv.local
698
+ .menv.*.local
699
+ ```
700
+
701
+ ### Environment Strategy
702
+
703
+ ```
704
+ Development: .menv + .menv.local
705
+ Staging: .menv + .menv.staging
706
+ Production: .menv + .menv.prod + .menv.prod.local (secrets)
707
+ ```
708
+
709
+ ### Security Tips
710
+
711
+ - ✅ **DO** use `.menv.local` for secrets and add to `.gitignore`
712
+ - ✅ **DO** use `strict: true` in production to catch misconfigurations
713
+ - ✅ **DO** validate sensitive values (URLs, ports, etc.) after loading
714
+ - ❌ **DON'T** commit production secrets to git
715
+ - ❌ **DON'T** use `exportEnv: true` if you need immutable config
716
+
717
+ ### Performance
718
+
719
+ - Config loading is synchronous and fast (~1-2ms for typical files)
720
+ - Frozen configs (default) prevent accidental mutations
721
+ - Use `watch()` only in development (slight memory overhead)
722
+
723
+ ---
724
+
725
+ ## Example Project
726
+
727
+ A complete example application is included in `examples/basic`.
175
728
 
176
- ## Example project
177
- An example app is included in examples/basic.
178
- Run it with:
179
729
  ```bash
180
730
  cd examples/basic
181
731
  npm install
182
732
  npm start
183
733
  ```
734
+
735
+ The example demonstrates:
736
+ - Profile switching (dev/prod)
737
+ - Schema validation
738
+ - Type casting
739
+ - Origin tracking
740
+ - Live reload with watch mode
741
+
742
+ ---
743
+
744
+ ## Troubleshooting
745
+
746
+ ### "Unknown key" error in strict mode
747
+
748
+ **Problem:** Getting errors about unknown keys when loading config.
749
+
750
+ **Solution:** Add all keys to your schema or disable strict mode:
751
+ ```javascript
752
+ load({ strict: false }); // Allow unknown keys
753
+ ```
754
+
755
+ ### Values are strings instead of typed
756
+
757
+ **Problem:** `PORT` is `"3000"` (string) instead of `3000` (number).
758
+
759
+ **Solution:** Enable casting or add schema:
760
+ ```javascript
761
+ load({
762
+ cast: true, // Ensure casting is enabled
763
+ schema: { PORT: 'number' }
764
+ });
765
+ ```
766
+
767
+ ### Changes to .menv not reflected
768
+
769
+ **Problem:** Modified .menv file but app still uses old values.
770
+
771
+ **Solution:**
772
+ - If using `attach: true` (default), restart the app
773
+ - Or use `watch()` for automatic reloading in development
774
+
775
+ ### Type casting fails
776
+
777
+ **Problem:** Getting parse errors for JSON or dates.
778
+
779
+ **Solution:** Verify the format in your .menv file:
780
+ ```env
781
+ # Valid JSON (use double quotes)
782
+ METADATA={"key":"value"}
783
+
784
+ # Valid date (ISO 8601)
785
+ START_DATE=2026-02-02
786
+ ```
787
+
788
+ ---
789
+
790
+ ## License
791
+
792
+ ISC License
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "molex-env",
3
- "version": "0.2.0",
3
+ "version": "0.2.3",
4
4
  "description": "Native .menv loader with profiles, typing, and origin tracking.",
5
5
  "main": "src/index.js",
6
6
  "files": [