@ayepi/env 0.2.0

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/dist/index.js ADDED
@@ -0,0 +1,722 @@
1
+ import { t as parseDotenv } from "./dotenv.js";
2
+ //#region src/coerce.ts
3
+ /** Read a schema's internal `_zod.def` (zod v4 stores its definition there). */
4
+ function def(schema) {
5
+ return schema._zod.def;
6
+ }
7
+ /** Wrapper types that hold an `innerType` we look through to find the effective leaf type. */
8
+ const WRAPPERS = new Set([
9
+ "optional",
10
+ "nullable",
11
+ "default",
12
+ "prefault",
13
+ "catch",
14
+ "readonly",
15
+ "nonoptional"
16
+ ]);
17
+ /** The effective (unwrapped) zod type name of a field — e.g. `optional(number)` → `'number'`. */
18
+ function effectiveType(schema) {
19
+ let d = def(schema);
20
+ while (WRAPPERS.has(d.type)) d = def(d.innerType);
21
+ return d.type;
22
+ }
23
+ /** The default strings parsed as `true` / `false` (matched case-insensitively, trimmed). */
24
+ const DEFAULT_TRUE = new Set([
25
+ "true",
26
+ "1",
27
+ "yes",
28
+ "y",
29
+ "on"
30
+ ]);
31
+ const DEFAULT_FALSE = new Set([
32
+ "false",
33
+ "0",
34
+ "no",
35
+ "n",
36
+ "off"
37
+ ]);
38
+ /** Parse a boolean-ish string; `undefined` when it isn't recognizably boolean. */
39
+ function toBool(value, words) {
40
+ const v = value.trim().toLowerCase();
41
+ if ((words?.true ?? DEFAULT_TRUE).has(v)) return true;
42
+ if ((words?.false ?? DEFAULT_FALSE).has(v)) return false;
43
+ }
44
+ /** `JSON.parse` the string, or return it unchanged if it isn't valid JSON. */
45
+ function tryJson(value) {
46
+ try {
47
+ return JSON.parse(value);
48
+ } catch {
49
+ return value;
50
+ }
51
+ }
52
+ /** Complex zod types whose string form is JSON. */
53
+ const JSONISH = new Set([
54
+ "object",
55
+ "array",
56
+ "tuple",
57
+ "record",
58
+ "map",
59
+ "set",
60
+ "union",
61
+ "intersection",
62
+ "json"
63
+ ]);
64
+ /**
65
+ * Coerce `value` toward what `schema` expects. Non-string values pass through; strings are
66
+ * converted per the field's effective type — so a plain `z.number()` / `z.boolean()` / `z.date()`
67
+ * / `z.object()` works straight from env strings, **no `z.coerce` needed**. Returns the original
68
+ * string when a conversion is ambiguous or fails, so zod produces the final, authoritative error.
69
+ * Pass `words` to customize which strings count as boolean `true`/`false`.
70
+ */
71
+ function coerce(schema, value, words) {
72
+ if (typeof value !== "string") return value;
73
+ const t = effectiveType(schema);
74
+ switch (t) {
75
+ case "number": {
76
+ const n = Number(value);
77
+ return value.trim() === "" || Number.isNaN(n) ? value : n;
78
+ }
79
+ case "bigint": try {
80
+ return BigInt(value);
81
+ } catch {
82
+ return value;
83
+ }
84
+ case "boolean": {
85
+ const b = toBool(value, words);
86
+ return b === void 0 ? value : b;
87
+ }
88
+ case "date": {
89
+ const d = new Date(value);
90
+ return Number.isNaN(d.getTime()) ? value : d;
91
+ }
92
+ default: return JSONISH.has(t) ? tryJson(value) : value;
93
+ }
94
+ }
95
+ //#endregion
96
+ //#region src/meta.ts
97
+ /** Read a schema's `.meta()` (zod v4), narrowed to the aliasing keys we understand. */
98
+ function readMeta(schema) {
99
+ return schema.meta?.();
100
+ }
101
+ /** The source keys a field reads, in precedence order — its `vars`/`var` meta, else `[key]`. */
102
+ function varsOf(schema, key) {
103
+ const meta = readMeta(schema);
104
+ if (meta?.vars && meta.vars.length > 0) return meta.vars;
105
+ if (meta?.var) return [meta.var];
106
+ return [key];
107
+ }
108
+ /** Attach aliasing metadata: read `schema` from the first present of `keys`. */
109
+ function alias(schema, ...keys) {
110
+ return schema.meta({ vars: keys });
111
+ }
112
+ //#endregion
113
+ //#region src/source.ts
114
+ /** `process.env` when available (Node), else an empty record (so the core stays browser-safe). */
115
+ function defaultSource() {
116
+ return globalThis.process?.env ?? {};
117
+ }
118
+ /** Flatten one or more sources into a single record; later sources win. */
119
+ function mergeSources(source) {
120
+ const list = source === void 0 ? [defaultSource()] : Array.isArray(source) ? source : [source];
121
+ return Object.assign({}, ...list);
122
+ }
123
+ /** Resolve a value from the first present (defined) of several source `keys` (**aliasing**). */
124
+ function resolveRaw(source, keys) {
125
+ for (const k of keys) {
126
+ const v = source[k];
127
+ if (v !== void 0) return v;
128
+ }
129
+ }
130
+ /** A readable, aggregated environment validation error built from zod issues. */
131
+ var EnvError = class extends Error {
132
+ /** The individual zod issues that caused the failure. */
133
+ issues;
134
+ constructor(issues) {
135
+ const lines = issues.map((i) => ` ${i.path.join(".") || "(root)"}: ${i.message}`);
136
+ super(`Invalid environment:\n${lines.join("\n")}`);
137
+ this.name = "EnvError";
138
+ this.issues = issues;
139
+ }
140
+ };
141
+ /** Prefix a field key onto each issue's path (for aggregated, keyed error messages). */
142
+ function keyed(key, issues) {
143
+ return issues.map((i) => ({
144
+ ...i,
145
+ path: [key, ...i.path]
146
+ }));
147
+ }
148
+ /** A custom single-issue for a field (a thrown factory, or a value with no schema that failed). */
149
+ function customIssue(key, message) {
150
+ return {
151
+ code: "custom",
152
+ path: [key],
153
+ message
154
+ };
155
+ }
156
+ /** Message text for a thrown value (Error or otherwise). */
157
+ function errMessage(err) {
158
+ return err instanceof Error ? err.message : String(err);
159
+ }
160
+ //#endregion
161
+ //#region src/util.ts
162
+ /** True if `x` is a zod schema (zod v4 stores its definition under `_zod` and exposes `safeParse`). */
163
+ function isZodType(x) {
164
+ return typeof x === "object" && x !== null && "_zod" in x && typeof x.safeParse === "function";
165
+ }
166
+ /** Structural deep equality (for `deep` change detection): handles arrays, plain objects, dates, NaN. */
167
+ function deepEqual(a, b) {
168
+ if (Object.is(a, b)) return true;
169
+ if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
170
+ if (a instanceof Date || b instanceof Date) return a instanceof Date && b instanceof Date && a.getTime() === b.getTime();
171
+ const aArr = Array.isArray(a);
172
+ if (aArr !== Array.isArray(b)) return false;
173
+ if (aArr) {
174
+ const x = a;
175
+ const y = b;
176
+ return x.length === y.length && x.every((v, i) => deepEqual(v, y[i]));
177
+ }
178
+ const ak = Object.keys(a);
179
+ const bk = Object.keys(b);
180
+ return ak.length === bk.length && ak.every((k) => Object.prototype.hasOwnProperty.call(b, k) && deepEqual(a[k], b[k]));
181
+ }
182
+ /** Equality used for change detection: structural when `deep`, else identity (`Object.is`). */
183
+ function changed(prev, next, deep) {
184
+ return deep ? !deepEqual(prev, next) : !Object.is(prev, next);
185
+ }
186
+ //#endregion
187
+ //#region src/env.ts
188
+ /** Normalize {@link EnvOptions} into the lowercased {@link BooleanWords} the coercer consumes. */
189
+ function toWords(options) {
190
+ const b = options?.booleans;
191
+ if (!b) return;
192
+ const lower = (xs) => xs ? new Set(xs.map((s) => s.trim().toLowerCase())) : void 0;
193
+ return {
194
+ true: lower(b.true),
195
+ false: lower(b.false)
196
+ };
197
+ }
198
+ /** The synchronous engine behind {@link env} (cast to the typed {@link Env} surface by `env`/`add`). */
199
+ var EnvImpl = class {
200
+ defs = /* @__PURE__ */ new Map();
201
+ overrides = {};
202
+ cache = /* @__PURE__ */ new Map();
203
+ deps = /* @__PURE__ */ new Map();
204
+ keyLs = /* @__PURE__ */ new Map();
205
+ globalLs = /* @__PURE__ */ new Set();
206
+ prev = /* @__PURE__ */ new Map();
207
+ words;
208
+ groupCount = 0;
209
+ constructor(input, options) {
210
+ this.words = toWords(options);
211
+ this.define(input);
212
+ }
213
+ /** Register a group of fields under the next group index. */
214
+ define(input) {
215
+ const group = this.groupCount++;
216
+ for (const [key, val] of Object.entries(input)) if (isZodType(val)) this.defs.set(key, {
217
+ kind: "schema",
218
+ key,
219
+ group,
220
+ schema: val
221
+ });
222
+ else this.defs.set(key, {
223
+ kind: "factory",
224
+ key,
225
+ group,
226
+ factory: val
227
+ });
228
+ }
229
+ /** The live source: `process.env` (or `{}`), with `set(...)` overrides on top. */
230
+ source() {
231
+ return Object.assign({}, defaultSource(), this.overrides);
232
+ }
233
+ /** A proxy over fields from groups *earlier* than `group`, recording which keys a factory reads. */
234
+ inheritedFor(group) {
235
+ const reads = /* @__PURE__ */ new Set();
236
+ const self = this;
237
+ const visible = (prop) => typeof prop === "string" && (self.defs.get(prop)?.group ?? Infinity) < group;
238
+ return {
239
+ proxy: new Proxy(Object.create(null), {
240
+ get: (_t, prop) => visible(prop) ? (reads.add(prop), self.resolve(prop)) : void 0,
241
+ has: (_t, prop) => visible(prop),
242
+ ownKeys: () => [...self.defs.values()].filter((f) => f.group < group).map((f) => f.key),
243
+ getOwnPropertyDescriptor: () => ({
244
+ enumerable: true,
245
+ configurable: true
246
+ })
247
+ }),
248
+ reads
249
+ };
250
+ }
251
+ /** Resolve `key` to its value (cached). Throws an {@link EnvError} (scoped to `key`) on failure. */
252
+ resolve(key) {
253
+ if (this.cache.has(key)) return this.cache.get(key);
254
+ const def = this.defs.get(key);
255
+ let schema;
256
+ let computed;
257
+ let isComputed = false;
258
+ if (def.kind === "schema") schema = def.schema;
259
+ else {
260
+ const { proxy, reads } = this.inheritedFor(def.group);
261
+ let produced;
262
+ try {
263
+ produced = def.factory(proxy);
264
+ } catch (err) {
265
+ this.deps.set(key, reads);
266
+ throw new EnvError([customIssue(key, errMessage(err))]);
267
+ }
268
+ this.deps.set(key, reads);
269
+ if (isZodType(produced)) schema = produced;
270
+ else {
271
+ computed = produced;
272
+ isComputed = true;
273
+ }
274
+ }
275
+ let result;
276
+ if (isComputed) result = computed;
277
+ else {
278
+ const raw = resolveRaw(this.source(), varsOf(schema, key));
279
+ const res = schema.safeParse(raw !== void 0 ? coerce(schema, raw, this.words) : void 0);
280
+ if (!res.success) throw new EnvError(keyed(key, res.error.issues));
281
+ result = res.data;
282
+ }
283
+ this.cache.set(key, result);
284
+ return result;
285
+ }
286
+ get(key) {
287
+ return this.resolve(key);
288
+ }
289
+ parse() {
290
+ const out = {};
291
+ const issues = [];
292
+ for (const key of this.defs.keys()) try {
293
+ out[key] = this.resolve(key);
294
+ } catch (err) {
295
+ issues.push(...err.issues);
296
+ }
297
+ if (issues.length > 0) throw new EnvError(issues);
298
+ return out;
299
+ }
300
+ set(keyOrUpdates, value) {
301
+ const updates = typeof keyOrUpdates === "string" ? { [keyOrUpdates]: value } : keyOrUpdates;
302
+ Object.assign(this.overrides, updates);
303
+ this.invalidate();
304
+ }
305
+ add(input) {
306
+ this.define(input);
307
+ this.cache.clear();
308
+ this.deps.clear();
309
+ return this;
310
+ }
311
+ with(fn) {
312
+ fn(this);
313
+ }
314
+ map(fn) {
315
+ return fn(this);
316
+ }
317
+ on(arg1, arg2, arg3) {
318
+ if (typeof arg1 === "function") return this.onGlobal(arg1, arg2);
319
+ const keys = Array.isArray(arg1) ? arg1 : [arg1];
320
+ return this.onKeys(keys, arg2, arg3);
321
+ }
322
+ /** Call a subscriber, swallowing any throw so one bad listener can't break the engine. */
323
+ fire(thunk) {
324
+ try {
325
+ thunk();
326
+ } catch {}
327
+ }
328
+ /** Ensure `prev` holds a baseline for `key` (so the next change can be detected). */
329
+ baseline(key) {
330
+ if (this.prev.has(key)) return;
331
+ try {
332
+ this.prev.set(key, this.resolve(key));
333
+ } catch {}
334
+ }
335
+ removeKeyListener(l) {
336
+ for (const k of l.keys) {
337
+ const set = this.keyLs.get(k);
338
+ if (set) {
339
+ set.delete(l);
340
+ if (set.size === 0) this.keyLs.delete(k);
341
+ }
342
+ }
343
+ }
344
+ invokeKey(l, value, key) {
345
+ this.fire(() => l.fn(value, key));
346
+ if (l.opts.once) this.removeKeyListener(l);
347
+ }
348
+ invokeGlobal(l, key, value) {
349
+ this.fire(() => l.fn(key, value));
350
+ if (l.opts.once) this.globalLs.delete(l);
351
+ }
352
+ onGlobal(fn, opts) {
353
+ const l = {
354
+ fn,
355
+ opts: opts ?? {}
356
+ };
357
+ this.globalLs.add(l);
358
+ for (const key of this.defs.keys()) this.baseline(key);
359
+ if (l.opts.immediate) for (const key of this.defs.keys()) {
360
+ if (!this.globalLs.has(l)) break;
361
+ try {
362
+ const v = this.resolve(key);
363
+ this.invokeGlobal(l, key, v);
364
+ } catch {}
365
+ }
366
+ return () => this.globalLs.delete(l);
367
+ }
368
+ onKeys(keys, fn, opts) {
369
+ const l = {
370
+ fn,
371
+ opts: opts ?? {},
372
+ keys
373
+ };
374
+ for (const key of keys) {
375
+ let set = this.keyLs.get(key);
376
+ if (!set) this.keyLs.set(key, set = /* @__PURE__ */ new Set());
377
+ set.add(l);
378
+ this.baseline(key);
379
+ }
380
+ if (l.opts.immediate) for (const key of keys) {
381
+ if (l.opts.once && !this.keyLs.get(key)?.has(l)) break;
382
+ try {
383
+ const v = this.resolve(key);
384
+ this.invokeKey(l, v, key);
385
+ } catch {}
386
+ }
387
+ return () => this.removeKeyListener(l);
388
+ }
389
+ /** After a `set(...)`: drop caches and notify listeners whose watched values changed. */
390
+ invalidate() {
391
+ const hasGlobal = this.globalLs.size > 0;
392
+ const watched = new Set(this.keyLs.keys());
393
+ if (hasGlobal) for (const k of this.defs.keys()) watched.add(k);
394
+ this.cache.clear();
395
+ this.deps.clear();
396
+ for (const key of watched) {
397
+ let next;
398
+ try {
399
+ next = this.resolve(key);
400
+ } catch {
401
+ continue;
402
+ }
403
+ const hadPrev = this.prev.has(key);
404
+ const old = this.prev.get(key);
405
+ this.prev.set(key, next);
406
+ if (!hadPrev) continue;
407
+ const keyLs = this.keyLs.get(key);
408
+ if (keyLs) {
409
+ for (const l of [...keyLs]) if (changed(old, next, l.opts.deep)) this.invokeKey(l, next, key);
410
+ }
411
+ for (const g of [...this.globalLs]) if (changed(old, next, g.opts.deep)) this.invokeGlobal(g, key, next);
412
+ }
413
+ }
414
+ };
415
+ /** Build a typed, lazy, reactive config from a record of fields. Reads `process.env` by default. */
416
+ function env(input, options) {
417
+ return new EnvImpl(input, options);
418
+ }
419
+ //#endregion
420
+ //#region src/provider.ts
421
+ /** Bind a field to a live {@link EnvProvider}; values are validated against `schema`. */
422
+ function dynamic(provider, schema) {
423
+ return {
424
+ kind: "dynamic",
425
+ provider,
426
+ schema
427
+ };
428
+ }
429
+ /** True if `x` is a {@link DynamicBinding}. */
430
+ function isDynamic(x) {
431
+ return typeof x === "object" && x !== null && x.kind === "dynamic";
432
+ }
433
+ /** A provider that re-fetches on an interval — the simplest way to make any source "dynamic". */
434
+ function pollProvider(fetch, intervalMs) {
435
+ return {
436
+ load: fetch,
437
+ watch(emit) {
438
+ const id = setInterval(() => {
439
+ (async () => {
440
+ try {
441
+ emit(await fetch());
442
+ } catch {}
443
+ })();
444
+ }, intervalMs);
445
+ return () => clearInterval(id);
446
+ }
447
+ };
448
+ }
449
+ /** A fixed-value provider (no updates) — handy for tests and constants behind the provider interface. */
450
+ function staticProvider(value) {
451
+ return { load: () => value };
452
+ }
453
+ //#endregion
454
+ //#region src/async.ts
455
+ /** The asynchronous engine behind {@link asyncEnv}. */
456
+ var AsyncEnvImpl = class {
457
+ defs = /* @__PURE__ */ new Map();
458
+ overrides = {};
459
+ cache = /* @__PURE__ */ new Map();
460
+ pending = /* @__PURE__ */ new Map();
461
+ deps = /* @__PURE__ */ new Map();
462
+ keyLs = /* @__PURE__ */ new Map();
463
+ globalLs = /* @__PURE__ */ new Set();
464
+ prev = /* @__PURE__ */ new Map();
465
+ dynamicRaw = /* @__PURE__ */ new Map();
466
+ started = /* @__PURE__ */ new Set();
467
+ watchers = /* @__PURE__ */ new Map();
468
+ words;
469
+ groupCount = 0;
470
+ constructor(input, options) {
471
+ this.words = toWords(options);
472
+ this.define(input);
473
+ }
474
+ define(input) {
475
+ const group = this.groupCount++;
476
+ for (const [key, val] of Object.entries(input)) if (isDynamic(val)) this.defs.set(key, {
477
+ kind: "dynamic",
478
+ key,
479
+ group,
480
+ provider: val.provider,
481
+ schema: val.schema
482
+ });
483
+ else if (isZodType(val)) this.defs.set(key, {
484
+ kind: "schema",
485
+ key,
486
+ group,
487
+ schema: val
488
+ });
489
+ else this.defs.set(key, {
490
+ kind: "factory",
491
+ key,
492
+ group,
493
+ factory: val
494
+ });
495
+ }
496
+ source() {
497
+ return Object.assign({}, defaultSource(), this.overrides);
498
+ }
499
+ /** Validate a raw string against `schema` (coercing first); throws a keyed {@link EnvError} on failure. */
500
+ validate(schema, key, raw) {
501
+ const res = schema.safeParse(raw !== void 0 ? coerce(schema, raw, this.words) : void 0);
502
+ if (!res.success) throw new EnvError(keyed(key, res.error.issues));
503
+ return res.data;
504
+ }
505
+ /** Ensure a dynamic field's provider has loaded once and (if able) is watching for live updates. */
506
+ async ensureStarted(def) {
507
+ if (this.started.has(def.key)) return;
508
+ this.started.add(def.key);
509
+ this.dynamicRaw.set(def.key, await def.provider.load());
510
+ if (def.provider.watch) this.watchers.set(def.key, def.provider.watch((raw) => this.onDynamic(def, raw)));
511
+ }
512
+ /** A live push from a provider: keep the last good value on a bad update, else apply + notify. */
513
+ onDynamic(def, raw) {
514
+ if (!def.schema.safeParse(raw !== void 0 ? coerce(def.schema, raw, this.words) : void 0).success) return;
515
+ this.dynamicRaw.set(def.key, raw);
516
+ this.emitChange();
517
+ }
518
+ /** Resolve every field in groups *earlier* than `group` into a plain snapshot object. */
519
+ async earlierSnapshot(group) {
520
+ const earlier = [...this.defs.values()].filter((f) => f.group < group);
521
+ const entries = await Promise.all(earlier.map(async (f) => [f.key, await this.resolve(f.key)]));
522
+ return Object.fromEntries(entries);
523
+ }
524
+ /** Do the work of resolving one field (no memoization — see {@link resolve}). */
525
+ async compute(key) {
526
+ const def = this.defs.get(key);
527
+ if (def.kind === "schema") return this.validate(def.schema, key, resolveRaw(this.source(), varsOf(def.schema, key)));
528
+ if (def.kind === "dynamic") {
529
+ await this.ensureStarted(def);
530
+ return this.validate(def.schema, key, this.dynamicRaw.get(def.key));
531
+ }
532
+ const snap = await this.earlierSnapshot(def.group);
533
+ const reads = /* @__PURE__ */ new Set();
534
+ const proxy = new Proxy(snap, {
535
+ get: (t, p) => typeof p === "string" && p in t ? (reads.add(p), t[p]) : void 0,
536
+ has: (t, p) => typeof p === "string" && p in t
537
+ });
538
+ let produced;
539
+ try {
540
+ produced = await def.factory(proxy);
541
+ } catch (err) {
542
+ this.deps.set(key, reads);
543
+ throw new EnvError([customIssue(key, errMessage(err))]);
544
+ }
545
+ this.deps.set(key, reads);
546
+ if (isZodType(produced)) return this.validate(produced, key, resolveRaw(this.source(), varsOf(produced, key)));
547
+ return produced;
548
+ }
549
+ /** Resolve `key` (memoized per snapshot). Cleared by `set`/provider updates. */
550
+ resolve(key) {
551
+ if (this.cache.has(key)) return Promise.resolve(this.cache.get(key));
552
+ let p = this.pending.get(key);
553
+ if (!p) {
554
+ p = this.compute(key).then((v) => {
555
+ this.cache.set(key, v);
556
+ this.pending.delete(key);
557
+ return v;
558
+ }, (err) => {
559
+ this.pending.delete(key);
560
+ throw err;
561
+ });
562
+ this.pending.set(key, p);
563
+ }
564
+ return p;
565
+ }
566
+ get(key) {
567
+ return this.resolve(key);
568
+ }
569
+ async parse() {
570
+ const out = {};
571
+ const issues = [];
572
+ await Promise.all([...this.defs.keys()].map(async (key) => {
573
+ try {
574
+ out[key] = await this.resolve(key);
575
+ } catch (err) {
576
+ issues.push(...err.issues);
577
+ }
578
+ }));
579
+ if (issues.length > 0) throw new EnvError(issues);
580
+ return out;
581
+ }
582
+ set(keyOrUpdates, value) {
583
+ const updates = typeof keyOrUpdates === "string" ? { [keyOrUpdates]: value } : keyOrUpdates;
584
+ Object.assign(this.overrides, updates);
585
+ this.emitChange();
586
+ }
587
+ add(input) {
588
+ this.define(input);
589
+ this.cache.clear();
590
+ this.pending.clear();
591
+ this.deps.clear();
592
+ return this;
593
+ }
594
+ with(fn) {
595
+ fn(this);
596
+ }
597
+ map(fn) {
598
+ return fn(this);
599
+ }
600
+ async refresh(key) {
601
+ const keys = (key !== void 0 ? [key] : [...this.defs.keys()]).filter((k) => this.defs.get(k)?.kind === "dynamic");
602
+ let any = false;
603
+ for (const k of keys) {
604
+ const def = this.defs.get(k);
605
+ await this.ensureStarted(def);
606
+ const raw = await def.provider.load();
607
+ if (def.schema.safeParse(raw !== void 0 ? coerce(def.schema, raw, this.words) : void 0).success) {
608
+ this.dynamicRaw.set(k, raw);
609
+ any = true;
610
+ }
611
+ }
612
+ if (any) await this.emitChange();
613
+ }
614
+ close() {
615
+ for (const un of this.watchers.values()) un();
616
+ this.watchers.clear();
617
+ }
618
+ on(arg1, arg2, arg3) {
619
+ if (typeof arg1 === "function") return this.onGlobal(arg1, arg2);
620
+ const keys = Array.isArray(arg1) ? arg1 : [arg1];
621
+ return this.onKeys(keys, arg2, arg3);
622
+ }
623
+ fire(thunk) {
624
+ try {
625
+ thunk();
626
+ } catch {}
627
+ }
628
+ async baseline(key) {
629
+ if (this.prev.has(key)) return;
630
+ try {
631
+ this.prev.set(key, await this.resolve(key));
632
+ } catch {}
633
+ }
634
+ removeKeyListener(l) {
635
+ for (const k of l.keys) {
636
+ const set = this.keyLs.get(k);
637
+ if (set) {
638
+ set.delete(l);
639
+ if (set.size === 0) this.keyLs.delete(k);
640
+ }
641
+ }
642
+ }
643
+ invokeKey(l, value, key) {
644
+ this.fire(() => l.fn(value, key));
645
+ if (l.opts.once) this.removeKeyListener(l);
646
+ }
647
+ invokeGlobal(l, key, value) {
648
+ this.fire(() => l.fn(key, value));
649
+ if (l.opts.once) this.globalLs.delete(l);
650
+ }
651
+ onGlobal(fn, opts) {
652
+ const l = {
653
+ fn,
654
+ opts: opts ?? {}
655
+ };
656
+ this.globalLs.add(l);
657
+ (async () => {
658
+ for (const key of this.defs.keys()) await this.baseline(key);
659
+ if (l.opts.immediate) for (const key of this.defs.keys()) {
660
+ if (!this.globalLs.has(l)) break;
661
+ try {
662
+ this.invokeGlobal(l, key, await this.resolve(key));
663
+ } catch {}
664
+ }
665
+ })();
666
+ return () => this.globalLs.delete(l);
667
+ }
668
+ onKeys(keys, fn, opts) {
669
+ const l = {
670
+ fn,
671
+ opts: opts ?? {},
672
+ keys
673
+ };
674
+ for (const key of keys) {
675
+ let set = this.keyLs.get(key);
676
+ if (!set) this.keyLs.set(key, set = /* @__PURE__ */ new Set());
677
+ set.add(l);
678
+ }
679
+ (async () => {
680
+ for (const key of keys) await this.baseline(key);
681
+ if (l.opts.immediate) for (const key of keys) {
682
+ if (l.opts.once && !this.keyLs.get(key)?.has(l)) break;
683
+ try {
684
+ this.invokeKey(l, await this.resolve(key), key);
685
+ } catch {}
686
+ }
687
+ })();
688
+ return () => this.removeKeyListener(l);
689
+ }
690
+ /** After a change: drop caches and notify listeners whose watched values changed. */
691
+ async emitChange() {
692
+ const hasGlobal = this.globalLs.size > 0;
693
+ const watched = new Set(this.keyLs.keys());
694
+ if (hasGlobal) for (const k of this.defs.keys()) watched.add(k);
695
+ this.cache.clear();
696
+ this.pending.clear();
697
+ this.deps.clear();
698
+ for (const key of watched) {
699
+ let next;
700
+ try {
701
+ next = await this.resolve(key);
702
+ } catch {
703
+ continue;
704
+ }
705
+ const hadPrev = this.prev.has(key);
706
+ const old = this.prev.get(key);
707
+ this.prev.set(key, next);
708
+ if (!hadPrev) continue;
709
+ const keyLs = this.keyLs.get(key);
710
+ if (keyLs) {
711
+ for (const l of [...keyLs]) if (changed(old, next, l.opts.deep)) this.invokeKey(l, next, key);
712
+ }
713
+ for (const g of [...this.globalLs]) if (changed(old, next, g.opts.deep)) this.invokeGlobal(g, key, next);
714
+ }
715
+ }
716
+ };
717
+ /** Build a typed, lazy, reactive **async** config — supports async factories and live providers. */
718
+ function asyncEnv(input, options) {
719
+ return new AsyncEnvImpl(input, options);
720
+ }
721
+ //#endregion
722
+ export { DEFAULT_FALSE, DEFAULT_TRUE, EnvError, alias, asyncEnv, coerce, defaultSource, dynamic, effectiveType, env, isDynamic, mergeSources, parseDotenv, pollProvider, resolveRaw, staticProvider, varsOf };