@zero-server/env 0.9.1 → 0.9.2
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/LICENSE +21 -21
- package/index.js +2 -5
- package/lib/env/index.js +465 -0
- package/package.json +9 -3
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Tony Wiedman
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Tony Wiedman
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/index.js
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
// AUTO-GENERATED by .tools/generate-package-stubs.js — edit .tools/scope-manifest.js and re-run `npm run packages:generate`.
|
|
2
2
|
'use strict';
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
module.exports = {
|
|
6
|
-
env: sdk.env,
|
|
7
|
-
};
|
|
3
|
+
const env = require('./lib/env');
|
|
4
|
+
module.exports = { env };
|
package/lib/env/index.js
ADDED
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module env
|
|
3
|
+
* @description Zero-dependency typed environment variable system.
|
|
4
|
+
* Loads `.env` files, validates against a typed schema, and
|
|
5
|
+
* exposes a fast accessor with built-in type coercion.
|
|
6
|
+
*
|
|
7
|
+
* Supports: string, number, boolean, integer, array, json, url, port, enum.
|
|
8
|
+
* Multi-environment: `.env`, `.env.local`, `.env.{NODE_ENV}`, `.env.{NODE_ENV}.local`.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* const { env } = require('@zero-server/sdk');
|
|
12
|
+
*
|
|
13
|
+
* env.load({
|
|
14
|
+
* PORT: { type: 'port', default: 3000 },
|
|
15
|
+
* DATABASE_URL: { type: 'string', required: true },
|
|
16
|
+
* DEBUG: { type: 'boolean', default: false },
|
|
17
|
+
* ALLOWED_ORIGINS: { type: 'array', separator: ',' },
|
|
18
|
+
* LOG_LEVEL: { type: 'enum', values: ['debug','info','warn','error'], default: 'info' },
|
|
19
|
+
* });
|
|
20
|
+
*
|
|
21
|
+
* env.PORT // => 3000 (number)
|
|
22
|
+
* env('PORT') // => 3000
|
|
23
|
+
* env.DEBUG // => false (boolean)
|
|
24
|
+
* env.require('DATABASE_URL') // throws if missing
|
|
25
|
+
*/
|
|
26
|
+
const fs = require('fs');
|
|
27
|
+
const path = require('path');
|
|
28
|
+
|
|
29
|
+
// ===========================================================
|
|
30
|
+
// .env file parser
|
|
31
|
+
// ===========================================================
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Parse a `.env` file string into key-value pairs.
|
|
35
|
+
* Supports `#` comments, single/double/backtick quotes, multiline values,
|
|
36
|
+
* inline comments, interpolation `${VAR}`, and `export` prefix.
|
|
37
|
+
*
|
|
38
|
+
* @param {string} src - Raw file contents.
|
|
39
|
+
* @returns {Object<string, string>} Parsed key-value pairs.
|
|
40
|
+
*/
|
|
41
|
+
function parse(src)
|
|
42
|
+
{
|
|
43
|
+
const result = {};
|
|
44
|
+
const lines = src.replace(/\r\n?/g, '\n').split('\n');
|
|
45
|
+
|
|
46
|
+
let i = 0;
|
|
47
|
+
while (i < lines.length)
|
|
48
|
+
{
|
|
49
|
+
let line = lines[i].trim();
|
|
50
|
+
i++;
|
|
51
|
+
|
|
52
|
+
// Skip comments and blank lines
|
|
53
|
+
if (!line || line.startsWith('#')) continue;
|
|
54
|
+
|
|
55
|
+
// Strip optional `export ` prefix
|
|
56
|
+
if (line.startsWith('export ')) line = line.slice(7).trim();
|
|
57
|
+
|
|
58
|
+
const eqIdx = line.indexOf('=');
|
|
59
|
+
if (eqIdx === -1) continue;
|
|
60
|
+
|
|
61
|
+
const key = line.slice(0, eqIdx).trim();
|
|
62
|
+
let value = line.slice(eqIdx + 1).trim();
|
|
63
|
+
|
|
64
|
+
// Validate key name — only word chars + dots
|
|
65
|
+
if (!/^[\w.]+$/.test(key)) continue;
|
|
66
|
+
|
|
67
|
+
// Quoted values
|
|
68
|
+
const q = value[0];
|
|
69
|
+
if ((q === '"' || q === "'" || q === '`') && value.endsWith(q) && value.length >= 2)
|
|
70
|
+
{
|
|
71
|
+
value = value.slice(1, -1);
|
|
72
|
+
}
|
|
73
|
+
else if (q === '"' || q === "'" || q === '`')
|
|
74
|
+
{
|
|
75
|
+
// Multiline — read until closing quote
|
|
76
|
+
let multiline = value.slice(1);
|
|
77
|
+
while (i < lines.length)
|
|
78
|
+
{
|
|
79
|
+
const nextLine = lines[i];
|
|
80
|
+
i++;
|
|
81
|
+
if (nextLine.trimEnd().endsWith(q))
|
|
82
|
+
{
|
|
83
|
+
multiline += '\n' + nextLine.trimEnd().slice(0, -1);
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
multiline += '\n' + nextLine;
|
|
87
|
+
}
|
|
88
|
+
value = multiline;
|
|
89
|
+
}
|
|
90
|
+
else
|
|
91
|
+
{
|
|
92
|
+
// Unquoted — strip inline comment
|
|
93
|
+
const hashIdx = value.indexOf(' #');
|
|
94
|
+
if (hashIdx !== -1) value = value.slice(0, hashIdx).trim();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Variable interpolation: ${VAR} → process.env.VAR or already-parsed value
|
|
98
|
+
value = value.replace(/\$\{([^}]+)\}/g, (_, name) =>
|
|
99
|
+
{
|
|
100
|
+
return result[name] || process.env[name] || '';
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
result[key] = value;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ===========================================================
|
|
110
|
+
// Type coercion
|
|
111
|
+
// ===========================================================
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Coerce a string value to the specified type.
|
|
115
|
+
*
|
|
116
|
+
* @private
|
|
117
|
+
* @param {string} raw - Raw string value.
|
|
118
|
+
* @param {object} fieldDef - Schema field definition.
|
|
119
|
+
* @param {string} key - Variable name (for error messages).
|
|
120
|
+
* @returns {*} Coerced value.
|
|
121
|
+
* @throws {Error} When the value cannot be coerced or fails validation.
|
|
122
|
+
*/
|
|
123
|
+
function coerce(raw, fieldDef, key)
|
|
124
|
+
{
|
|
125
|
+
const type = fieldDef.type || 'string';
|
|
126
|
+
|
|
127
|
+
switch (type)
|
|
128
|
+
{
|
|
129
|
+
case 'string':
|
|
130
|
+
{
|
|
131
|
+
const val = String(raw);
|
|
132
|
+
if (fieldDef.min !== undefined && val.length < fieldDef.min)
|
|
133
|
+
throw new Error(`env "${key}" must be at least ${fieldDef.min} characters`);
|
|
134
|
+
if (fieldDef.max !== undefined && val.length > fieldDef.max)
|
|
135
|
+
throw new Error(`env "${key}" must be at most ${fieldDef.max} characters`);
|
|
136
|
+
if (fieldDef.match && !fieldDef.match.test(val))
|
|
137
|
+
throw new Error(`env "${key}" does not match pattern ${fieldDef.match}`);
|
|
138
|
+
return val;
|
|
139
|
+
}
|
|
140
|
+
case 'number':
|
|
141
|
+
{
|
|
142
|
+
const val = Number(raw);
|
|
143
|
+
if (isNaN(val)) throw new Error(`env "${key}" must be a number, got "${raw}"`);
|
|
144
|
+
if (fieldDef.min !== undefined && val < fieldDef.min)
|
|
145
|
+
throw new Error(`env "${key}" must be >= ${fieldDef.min}`);
|
|
146
|
+
if (fieldDef.max !== undefined && val > fieldDef.max)
|
|
147
|
+
throw new Error(`env "${key}" must be <= ${fieldDef.max}`);
|
|
148
|
+
return val;
|
|
149
|
+
}
|
|
150
|
+
case 'integer':
|
|
151
|
+
{
|
|
152
|
+
const val = parseInt(raw, 10);
|
|
153
|
+
if (isNaN(val)) throw new Error(`env "${key}" must be an integer, got "${raw}"`);
|
|
154
|
+
if (fieldDef.min !== undefined && val < fieldDef.min)
|
|
155
|
+
throw new Error(`env "${key}" must be >= ${fieldDef.min}`);
|
|
156
|
+
if (fieldDef.max !== undefined && val > fieldDef.max)
|
|
157
|
+
throw new Error(`env "${key}" must be <= ${fieldDef.max}`);
|
|
158
|
+
return val;
|
|
159
|
+
}
|
|
160
|
+
case 'port':
|
|
161
|
+
{
|
|
162
|
+
const val = parseInt(raw, 10);
|
|
163
|
+
if (isNaN(val) || val < 0 || val > 65535)
|
|
164
|
+
throw new Error(`env "${key}" must be a valid port (0-65535), got "${raw}"`);
|
|
165
|
+
return val;
|
|
166
|
+
}
|
|
167
|
+
case 'boolean':
|
|
168
|
+
{
|
|
169
|
+
const lower = String(raw).toLowerCase().trim();
|
|
170
|
+
if (['true', '1', 'yes', 'on'].includes(lower)) return true;
|
|
171
|
+
if (['false', '0', 'no', 'off', ''].includes(lower)) return false;
|
|
172
|
+
throw new Error(`env "${key}" must be a boolean, got "${raw}"`);
|
|
173
|
+
}
|
|
174
|
+
case 'array':
|
|
175
|
+
{
|
|
176
|
+
const sep = fieldDef.separator || ',';
|
|
177
|
+
return String(raw).split(sep).map(s => s.trim()).filter(Boolean);
|
|
178
|
+
}
|
|
179
|
+
case 'json':
|
|
180
|
+
{
|
|
181
|
+
try { return JSON.parse(raw); }
|
|
182
|
+
catch (e) { throw new Error(`env "${key}" must be valid JSON: ${e.message}`); }
|
|
183
|
+
}
|
|
184
|
+
case 'url':
|
|
185
|
+
{
|
|
186
|
+
try { new URL(raw); return raw; }
|
|
187
|
+
catch (e) { throw new Error(`env "${key}" must be a valid URL, got "${raw}"`); }
|
|
188
|
+
}
|
|
189
|
+
case 'enum':
|
|
190
|
+
{
|
|
191
|
+
const values = fieldDef.values || [];
|
|
192
|
+
if (!values.includes(raw))
|
|
193
|
+
throw new Error(`env "${key}" must be one of [${values.join(', ')}], got "${raw}"`);
|
|
194
|
+
return raw;
|
|
195
|
+
}
|
|
196
|
+
default:
|
|
197
|
+
return raw;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ===========================================================
|
|
202
|
+
// Env store
|
|
203
|
+
// ===========================================================
|
|
204
|
+
|
|
205
|
+
/** @type {Object<string, *>} Typed/validated values store */
|
|
206
|
+
const _store = {};
|
|
207
|
+
|
|
208
|
+
/** @type {Object<string, object>} Schema definitions */
|
|
209
|
+
let _schema = null;
|
|
210
|
+
|
|
211
|
+
/** @type {boolean} */
|
|
212
|
+
let _loaded = false;
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Load environment variables from `.env` files and validate against a typed schema.
|
|
216
|
+
*
|
|
217
|
+
* Files are loaded in precedence order (later overrides earlier):
|
|
218
|
+
* 1. `.env` — shared defaults
|
|
219
|
+
* 2. `.env.local` — local overrides (gitignored)
|
|
220
|
+
* 3. `.env.{NODE_ENV}` — environment-specific (e.g. `.env.production`)
|
|
221
|
+
* 4. `.env.{NODE_ENV}.local` — env-specific local overrides
|
|
222
|
+
*
|
|
223
|
+
* Process environment variables (`process.env`) always take precedence.
|
|
224
|
+
*
|
|
225
|
+
* @param {Object<string, object>} [schema] - Typed schema definition.
|
|
226
|
+
* @param {object} [options] - Configuration options.
|
|
227
|
+
* @param {string} [options.path] - Custom directory to load from (default: `process.cwd()`).
|
|
228
|
+
* @param {boolean} [options.override=false] - When true, overwrite existing `process.env` values with file values.
|
|
229
|
+
* When false (default), file values are written to `process.env` only for keys not already set.
|
|
230
|
+
* Set to `false` explicitly to disable all `process.env` syncing.
|
|
231
|
+
* @returns {Object<string, *>} The validated env store.
|
|
232
|
+
*
|
|
233
|
+
* @throws {Error} On validation failures (missing required vars, bad types, etc.).
|
|
234
|
+
*/
|
|
235
|
+
function load(schema, options = {})
|
|
236
|
+
{
|
|
237
|
+
/* Allow load('/path/to/dir') or load('/path/to/.env') as shorthand */
|
|
238
|
+
if (typeof schema === 'string')
|
|
239
|
+
{
|
|
240
|
+
const p = schema;
|
|
241
|
+
schema = undefined;
|
|
242
|
+
options = typeof options === 'object' ? options : {};
|
|
243
|
+
/* If it points to a file, use its parent directory */
|
|
244
|
+
options.path = (p.endsWith('.env') || p.includes('.env.')) ? path.dirname(p) : p;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const dir = options.path || process.cwd();
|
|
248
|
+
const nodeEnv = process.env.NODE_ENV || 'development';
|
|
249
|
+
|
|
250
|
+
// Load files in precedence order
|
|
251
|
+
const files = [
|
|
252
|
+
'.env',
|
|
253
|
+
'.env.local',
|
|
254
|
+
`.env.${nodeEnv}`,
|
|
255
|
+
`.env.${nodeEnv}.local`,
|
|
256
|
+
];
|
|
257
|
+
|
|
258
|
+
const raw = {};
|
|
259
|
+
|
|
260
|
+
for (const file of files)
|
|
261
|
+
{
|
|
262
|
+
const filePath = path.resolve(dir, file);
|
|
263
|
+
try
|
|
264
|
+
{
|
|
265
|
+
if (fs.existsSync(filePath))
|
|
266
|
+
{
|
|
267
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
268
|
+
Object.assign(raw, parse(content));
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
catch (e) { /* silently skip unreadable files */ }
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Process.env always wins
|
|
275
|
+
const merged = {};
|
|
276
|
+
if (schema)
|
|
277
|
+
{
|
|
278
|
+
_schema = schema;
|
|
279
|
+
for (const key of Object.keys(schema))
|
|
280
|
+
{
|
|
281
|
+
if (process.env[key] !== undefined) merged[key] = process.env[key];
|
|
282
|
+
else if (raw[key] !== undefined) merged[key] = raw[key];
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
else
|
|
286
|
+
{
|
|
287
|
+
// No schema — load everything
|
|
288
|
+
Object.assign(merged, raw);
|
|
289
|
+
for (const key of Object.keys(raw))
|
|
290
|
+
{
|
|
291
|
+
if (process.env[key] !== undefined) merged[key] = process.env[key];
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Write file values into process.env unless explicitly disabled
|
|
296
|
+
if (options.override !== false)
|
|
297
|
+
{
|
|
298
|
+
for (const [k, v] of Object.entries(raw))
|
|
299
|
+
{
|
|
300
|
+
if (options.override || process.env[k] === undefined) process.env[k] = v;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Validate and coerce
|
|
305
|
+
const errors = [];
|
|
306
|
+
|
|
307
|
+
if (schema)
|
|
308
|
+
{
|
|
309
|
+
for (const [key, def] of Object.entries(schema))
|
|
310
|
+
{
|
|
311
|
+
const rawVal = merged[key];
|
|
312
|
+
|
|
313
|
+
if (rawVal === undefined || rawVal === '')
|
|
314
|
+
{
|
|
315
|
+
if (def.required)
|
|
316
|
+
{
|
|
317
|
+
errors.push(`env "${key}" is required but not set`);
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
if (def.default !== undefined)
|
|
321
|
+
{
|
|
322
|
+
_store[key] = typeof def.default === 'function' ? def.default() : def.default;
|
|
323
|
+
}
|
|
324
|
+
else
|
|
325
|
+
{
|
|
326
|
+
_store[key] = undefined;
|
|
327
|
+
}
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
try
|
|
332
|
+
{
|
|
333
|
+
_store[key] = coerce(rawVal, def, key);
|
|
334
|
+
}
|
|
335
|
+
catch (e)
|
|
336
|
+
{
|
|
337
|
+
errors.push(e.message);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
else
|
|
342
|
+
{
|
|
343
|
+
// No schema — store raw strings
|
|
344
|
+
Object.assign(_store, merged);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (errors.length > 0)
|
|
348
|
+
{
|
|
349
|
+
throw new Error('Environment validation failed:\n • ' + errors.join('\n • '));
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Sync coerced values back to process.env so other modules see them
|
|
353
|
+
if (options.override !== false)
|
|
354
|
+
{
|
|
355
|
+
for (const [k, v] of Object.entries(_store))
|
|
356
|
+
{
|
|
357
|
+
if (v !== undefined)
|
|
358
|
+
{
|
|
359
|
+
process.env[k] = typeof v === 'object' ? JSON.stringify(v) : String(v);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
_loaded = true;
|
|
365
|
+
return _store;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Get a typed environment variable.
|
|
370
|
+
* Can also be called as `env(key)`.
|
|
371
|
+
*
|
|
372
|
+
* @param {string} key - Variable name.
|
|
373
|
+
* @returns {*} The typed value.
|
|
374
|
+
*/
|
|
375
|
+
function get(key)
|
|
376
|
+
{
|
|
377
|
+
if (_store.hasOwnProperty(key)) return _store[key];
|
|
378
|
+
return process.env[key];
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Get a required environment variable. Throws if missing.
|
|
383
|
+
*
|
|
384
|
+
* @param {string} key - Variable name.
|
|
385
|
+
* @returns {*} The typed value.
|
|
386
|
+
* @throws {Error} If the variable is not set.
|
|
387
|
+
*/
|
|
388
|
+
function require_(key)
|
|
389
|
+
{
|
|
390
|
+
const val = get(key);
|
|
391
|
+
if (val === undefined || val === null || val === '')
|
|
392
|
+
{
|
|
393
|
+
throw new Error(`Required environment variable "${key}" is not set`);
|
|
394
|
+
}
|
|
395
|
+
return val;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Check if a variable is set (not undefined).
|
|
400
|
+
*
|
|
401
|
+
* @param {string} key - Variable name.
|
|
402
|
+
* @returns {boolean} Boolean result.
|
|
403
|
+
*/
|
|
404
|
+
function has(key)
|
|
405
|
+
{
|
|
406
|
+
return _store.hasOwnProperty(key) || process.env.hasOwnProperty(key);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Get all loaded values as a plain object.
|
|
411
|
+
*
|
|
412
|
+
* @returns {Object<string, *>} Result value.
|
|
413
|
+
*/
|
|
414
|
+
function all()
|
|
415
|
+
{
|
|
416
|
+
return { ..._store };
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Reset the env store (useful for testing).
|
|
421
|
+
*/
|
|
422
|
+
function reset()
|
|
423
|
+
{
|
|
424
|
+
for (const k of Object.keys(_store)) delete _store[k];
|
|
425
|
+
_schema = null;
|
|
426
|
+
_loaded = false;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// ===========================================================
|
|
430
|
+
// Proxy-based accessor — env.PORT, env('PORT'), env.get('PORT')
|
|
431
|
+
// ===========================================================
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* The env function — callable as `env(key)` or `env.key`.
|
|
435
|
+
*
|
|
436
|
+
* @param {string} key - Environment variable name.
|
|
437
|
+
* @returns {*} Result value.
|
|
438
|
+
*/
|
|
439
|
+
function envFn(key)
|
|
440
|
+
{
|
|
441
|
+
return get(key);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Attach methods
|
|
445
|
+
envFn.load = load;
|
|
446
|
+
envFn.get = get;
|
|
447
|
+
envFn.require = require_;
|
|
448
|
+
envFn.has = has;
|
|
449
|
+
envFn.all = all;
|
|
450
|
+
envFn.reset = reset;
|
|
451
|
+
envFn.parse = parse;
|
|
452
|
+
|
|
453
|
+
// Proxy for dotenv.PORT style access
|
|
454
|
+
const envProxy = new Proxy(envFn, {
|
|
455
|
+
get(target, prop)
|
|
456
|
+
{
|
|
457
|
+
// Return own methods first
|
|
458
|
+
if (prop in target) return target[prop];
|
|
459
|
+
// Then check the store
|
|
460
|
+
if (typeof prop === 'string') return get(prop);
|
|
461
|
+
return undefined;
|
|
462
|
+
},
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
module.exports = envProxy;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zero-server/env",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.2",
|
|
4
4
|
"description": "Typed .env loader with schema validation.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"zero-server",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"./package.json": "./package.json"
|
|
21
21
|
},
|
|
22
22
|
"files": [
|
|
23
|
+
"lib",
|
|
23
24
|
"index.js",
|
|
24
25
|
"index.d.ts",
|
|
25
26
|
"README.md",
|
|
@@ -42,7 +43,12 @@
|
|
|
42
43
|
"access": "public"
|
|
43
44
|
},
|
|
44
45
|
"sideEffects": false,
|
|
45
|
-
"
|
|
46
|
-
"@zero-server/sdk": "0.9.
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"@zero-server/sdk": ">=0.9.2"
|
|
48
|
+
},
|
|
49
|
+
"peerDependenciesMeta": {
|
|
50
|
+
"@zero-server/sdk": {
|
|
51
|
+
"optional": true
|
|
52
|
+
}
|
|
47
53
|
}
|
|
48
54
|
}
|