@jondotsoy/configs 0.1.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.
@@ -0,0 +1,847 @@
1
+ // src/utils/store.ts
2
+ class Store {
3
+ value;
4
+ subscribers = new Set;
5
+ cleanups = new Map;
6
+ mountListeners = new Set;
7
+ unmountListeners = new Set;
8
+ constructor(initial) {
9
+ this.value = initial;
10
+ }
11
+ get() {
12
+ return this.value;
13
+ }
14
+ set(next) {
15
+ this.value = next;
16
+ for (const subscriber of this.subscribers) {
17
+ this.runSubscriber(subscriber, this.value);
18
+ }
19
+ }
20
+ subscribe(subscriber) {
21
+ this.addSubscriber(subscriber);
22
+ this.runSubscriber(subscriber, this.value);
23
+ return () => this.removeSubscriber(subscriber);
24
+ }
25
+ listen(subscriber) {
26
+ this.addSubscriber(subscriber);
27
+ return () => this.removeSubscriber(subscriber);
28
+ }
29
+ runSubscriber(subscriber, value) {
30
+ const cleanup = subscriber(value);
31
+ if (typeof cleanup === "function")
32
+ this.cleanups.set(subscriber, cleanup);
33
+ }
34
+ onMount(listener) {
35
+ this.mountListeners.add(listener);
36
+ return () => {
37
+ this.mountListeners.delete(listener);
38
+ };
39
+ }
40
+ onUnmount(listener) {
41
+ this.unmountListeners.add(listener);
42
+ return () => {
43
+ this.unmountListeners.delete(listener);
44
+ };
45
+ }
46
+ addSubscriber(subscriber) {
47
+ const wasEmpty = this.subscribers.size === 0;
48
+ if (wasEmpty) {
49
+ for (const listener of this.mountListeners)
50
+ listener();
51
+ }
52
+ this.subscribers.add(subscriber);
53
+ }
54
+ removeSubscriber(subscriber) {
55
+ if (!this.subscribers.delete(subscriber))
56
+ return;
57
+ const cleanup = this.cleanups.get(subscriber);
58
+ this.cleanups.delete(subscriber);
59
+ cleanup?.();
60
+ if (this.subscribers.size === 0) {
61
+ for (const listener of this.unmountListeners)
62
+ listener();
63
+ }
64
+ }
65
+ static onMount(store, callback) {
66
+ let cleanup;
67
+ const unsubMount = store.onMount(() => {
68
+ cleanup = callback();
69
+ });
70
+ const unsubUnmount = store.onUnmount(() => {
71
+ cleanup?.();
72
+ cleanup = undefined;
73
+ });
74
+ return () => {
75
+ cleanup?.();
76
+ cleanup = undefined;
77
+ unsubMount();
78
+ unsubUnmount();
79
+ };
80
+ }
81
+ }
82
+ function create(initial) {
83
+ return new Store(initial);
84
+ }
85
+ function computed(source, selector) {
86
+ const result = new Store(selector(source.get()));
87
+ Store.onMount(result, () => {
88
+ return source.subscribe((value) => {
89
+ result.set(selector(value));
90
+ });
91
+ });
92
+ return result;
93
+ }
94
+ var store = { create, computed, onMount: Store.onMount };
95
+
96
+ // src/sources/source.ts
97
+ class Source {
98
+ underlying;
99
+ store = new Store(null);
100
+ closed = false;
101
+ closePromise;
102
+ startPromise;
103
+ constructor(underlying) {
104
+ this.underlying = underlying;
105
+ const control = {
106
+ set: (value) => {
107
+ if (this.closed)
108
+ return;
109
+ const next = this.underlying.reduce ? this.underlying.reduce(value, this.store.get()) : value;
110
+ this.store.set(next);
111
+ },
112
+ close: () => {
113
+ this.close();
114
+ }
115
+ };
116
+ this.startPromise = Promise.resolve().then(() => underlying.start(control)).then(() => {
117
+ return;
118
+ });
119
+ this.startPromise.catch(() => {});
120
+ }
121
+ async open() {
122
+ await this.startPromise;
123
+ return this.store;
124
+ }
125
+ close() {
126
+ if (!this.closePromise) {
127
+ this.closed = true;
128
+ this.closePromise = Promise.resolve(this.underlying.close?.()).then(() => {
129
+ return;
130
+ }, () => {
131
+ return;
132
+ });
133
+ }
134
+ return this.closePromise;
135
+ }
136
+ }
137
+
138
+ // src/sources/env.ts
139
+ var mapKey = {
140
+ snakeCase(options = {}) {
141
+ const separator = options.separator ?? "_";
142
+ return (key) => key.toLowerCase().split(separator);
143
+ },
144
+ identity() {
145
+ return (key) => [key];
146
+ },
147
+ camelCase() {
148
+ return (key) => [key.toLowerCase().replace(/_([a-z0-9])/g, (_, char) => char.toUpperCase())];
149
+ },
150
+ lookup(table, fallback = (key) => [key]) {
151
+ return (key) => table[key] ?? fallback(key);
152
+ }
153
+ };
154
+ function setPath(target, path, value) {
155
+ let node = target;
156
+ for (const segment of path.slice(0, -1)) {
157
+ const next = node[segment];
158
+ if (typeof next !== "object" || next === null) {
159
+ node[segment] = {};
160
+ }
161
+ node = node[segment];
162
+ }
163
+ node[path[path.length - 1]] = value;
164
+ }
165
+ function envSource(options = {}) {
166
+ const env = options.env ?? process.env;
167
+ const { prefix, suffix } = options;
168
+ const mapKey2 = options.mapKey ?? ((key) => [key]);
169
+ return new Source({
170
+ start(control) {
171
+ const tree = {};
172
+ for (const [key, value] of Object.entries(env)) {
173
+ if (value === undefined)
174
+ continue;
175
+ if (prefix !== undefined && !key.startsWith(prefix))
176
+ continue;
177
+ if (suffix !== undefined && !key.endsWith(suffix))
178
+ continue;
179
+ const trimmedKey = key.slice(prefix !== undefined ? prefix.length : 0, suffix !== undefined ? key.length - suffix.length : key.length);
180
+ setPath(tree, mapKey2(trimmedKey), value);
181
+ }
182
+ control.set(tree);
183
+ control.close();
184
+ }
185
+ });
186
+ }
187
+
188
+ // src/sources/fetch.ts
189
+ async function download(url, init) {
190
+ const response = await fetch(url, init);
191
+ if (!response.ok) {
192
+ throw new Error(`fetchSource: received ${response.status} ${response.statusText} from "${url}"`);
193
+ }
194
+ return response;
195
+ }
196
+ function fetchSource(options) {
197
+ const { url, method = "GET", headers, attempts = 1 } = options;
198
+ return new Source({
199
+ async start(control) {
200
+ let response;
201
+ let lastError;
202
+ for (let attempt = 0;attempt < Math.max(1, attempts); attempt++) {
203
+ try {
204
+ response = await download(url, { method, headers });
205
+ break;
206
+ } catch (error) {
207
+ lastError = error;
208
+ }
209
+ }
210
+ if (!response) {
211
+ console.error(`fetchSource: failed to fetch "${url}" after ${attempts} attempt(s)`, lastError);
212
+ control.close();
213
+ return;
214
+ }
215
+ const contentType = response.headers.get("content-type") ?? "";
216
+ const text = await response.text();
217
+ let data;
218
+ try {
219
+ data = JSON.parse(text);
220
+ } catch (error) {
221
+ console.error(`fetchSource: response body from "${url}" is not valid JSON (content-type: "${contentType}")`, error);
222
+ control.close();
223
+ return;
224
+ }
225
+ control.set(data);
226
+ control.close();
227
+ }
228
+ });
229
+ }
230
+
231
+ // src/sources/sse.ts
232
+ function isPatch(value) {
233
+ return typeof value === "object" && value !== null && !Array.isArray(value);
234
+ }
235
+ function extractData(rawEvent) {
236
+ const dataLines = rawEvent.split(`
237
+ `).filter((line) => line.startsWith("data:")).map((line) => line.slice("data:".length).replace(/^ /, ""));
238
+ return dataLines.length > 0 ? dataLines.join(`
239
+ `) : null;
240
+ }
241
+ async function readEvents(body, onEvent) {
242
+ const reader = body.getReader();
243
+ const decoder = new TextDecoder;
244
+ let buffer = "";
245
+ try {
246
+ while (true) {
247
+ const { done, value } = await reader.read();
248
+ if (done)
249
+ break;
250
+ buffer += decoder.decode(value, { stream: true });
251
+ let boundary;
252
+ while ((boundary = buffer.indexOf(`
253
+
254
+ `)) !== -1) {
255
+ onEvent(buffer.slice(0, boundary));
256
+ buffer = buffer.slice(boundary + 2);
257
+ }
258
+ }
259
+ } finally {
260
+ reader.releaseLock();
261
+ }
262
+ }
263
+ function sseSource(options) {
264
+ const { url, method = "GET", headers } = options;
265
+ const abortController = new AbortController;
266
+ return new Source({
267
+ async start(control) {
268
+ let response;
269
+ try {
270
+ response = await fetch(url, { method, headers, signal: abortController.signal });
271
+ } catch (error) {
272
+ if (!abortController.signal.aborted) {
273
+ console.error(`sseSource: failed to connect to "${url}"`, error);
274
+ }
275
+ control.close();
276
+ return;
277
+ }
278
+ if (!response.ok || !response.body) {
279
+ console.error(`sseSource: received ${response.status} ${response.statusText} from "${url}"`);
280
+ control.close();
281
+ return;
282
+ }
283
+ await new Promise((resolveFirstMessage) => {
284
+ let settled = false;
285
+ const settle = () => {
286
+ if (settled)
287
+ return;
288
+ settled = true;
289
+ resolveFirstMessage();
290
+ };
291
+ readEvents(response.body, (rawEvent) => {
292
+ const raw = extractData(rawEvent);
293
+ if (raw === null || raw.trim() === "")
294
+ return;
295
+ let parsed;
296
+ try {
297
+ parsed = JSON.parse(raw);
298
+ } catch (error) {
299
+ console.error(`sseSource: message from "${url}" is not valid JSON`, error);
300
+ return;
301
+ }
302
+ if (!isPatch(parsed)) {
303
+ console.error(`sseSource: message from "${url}" did not parse to a JSON object`, parsed);
304
+ return;
305
+ }
306
+ control.set(parsed);
307
+ settle();
308
+ }).catch((error) => {
309
+ if (!abortController.signal.aborted) {
310
+ console.error(`sseSource: connection to "${url}" ended with an error`, error);
311
+ }
312
+ }).finally(() => {
313
+ control.close();
314
+ settle();
315
+ });
316
+ });
317
+ },
318
+ reduce: (patch, previous) => ({
319
+ ...previous ?? {},
320
+ ...patch
321
+ }),
322
+ close() {
323
+ abortController.abort();
324
+ }
325
+ });
326
+ }
327
+
328
+ // src/sources/file.ts
329
+ import { watch } from "node:fs";
330
+ import { readFile } from "node:fs/promises";
331
+
332
+ // src/utils/dotenv.ts
333
+ var QUOTES = ['"', "'", "`"];
334
+ function extractQuoted(value, quote) {
335
+ let i = 1;
336
+ while (i < value.length) {
337
+ const char = value[i];
338
+ if (char === "\\" && i + 1 < value.length) {
339
+ i += 2;
340
+ continue;
341
+ }
342
+ if (char === quote)
343
+ return value.slice(1, i);
344
+ i++;
345
+ }
346
+ return;
347
+ }
348
+ function unescapeQuoted(content, quote) {
349
+ if (quote !== '"')
350
+ return content;
351
+ return content.replace(/\\n/g, `
352
+ `).replace(/\\r/g, "\r");
353
+ }
354
+ function stripInlineComment(value) {
355
+ const match = value.match(/(^|\s)#/);
356
+ if (!match)
357
+ return value;
358
+ return value.slice(0, match.index).trimEnd();
359
+ }
360
+ function parseLine(line) {
361
+ const eq = line.indexOf("=");
362
+ if (eq === -1)
363
+ return;
364
+ const key = line.slice(0, eq).trim();
365
+ const rawValue = line.slice(eq + 1).trim();
366
+ if (rawValue.length >= 2 && QUOTES.includes(rawValue[0])) {
367
+ const quote = rawValue[0];
368
+ const quoted = extractQuoted(rawValue, quote);
369
+ if (quoted !== undefined)
370
+ return [key, unescapeQuoted(quoted, quote)];
371
+ }
372
+ return [key, stripInlineComment(rawValue)];
373
+ }
374
+ function parse(text) {
375
+ const result = {};
376
+ for (const rawLine of text.split(/\r?\n/)) {
377
+ const line = rawLine.trim();
378
+ if (line === "" || line.startsWith("#"))
379
+ continue;
380
+ const parsed = parseLine(line);
381
+ if (!parsed)
382
+ continue;
383
+ const [key, value] = parsed;
384
+ result[key] = value;
385
+ }
386
+ return result;
387
+ }
388
+ var DotEnv = { parse };
389
+
390
+ // src/sources/file.ts
391
+ function detectFormat(path) {
392
+ const pathname = path instanceof URL ? path.pathname : path;
393
+ if (pathname.endsWith(".json"))
394
+ return "json";
395
+ if (pathname.endsWith(".env"))
396
+ return "env";
397
+ return;
398
+ }
399
+ function parseFile(format, text) {
400
+ switch (format) {
401
+ case "json":
402
+ return JSON.parse(text);
403
+ case "env":
404
+ return DotEnv.parse(text);
405
+ }
406
+ }
407
+ function selectTreePath(data, treePath) {
408
+ let node = data;
409
+ for (const key of treePath) {
410
+ if (typeof node !== "object" || node === null)
411
+ return;
412
+ node = node[key];
413
+ }
414
+ return node;
415
+ }
416
+ function fileSource(path, options = {}) {
417
+ const shouldWatch = options.watch ?? true;
418
+ const treePath = options.treePath ?? [];
419
+ let watcher;
420
+ return new Source({
421
+ async start(control) {
422
+ const format = detectFormat(path);
423
+ if (!format) {
424
+ console.error(`fileSource: unrecognized file extension for "${path}"`);
425
+ control.close();
426
+ return;
427
+ }
428
+ async function readOnce() {
429
+ let text;
430
+ try {
431
+ text = await readFile(path, "utf8");
432
+ } catch (error) {
433
+ console.error(`fileSource: failed to read "${path}"`, error);
434
+ return false;
435
+ }
436
+ let parsed;
437
+ try {
438
+ parsed = parseFile(format, text);
439
+ } catch (error) {
440
+ console.error(`fileSource: failed to parse "${path}" as ${format}`, error);
441
+ return false;
442
+ }
443
+ const selected = selectTreePath(parsed, treePath);
444
+ if (selected === undefined) {
445
+ console.error(`fileSource: treePath [${treePath.map((k) => JSON.stringify(k)).join(", ")}] did not resolve to anything in "${path}"`);
446
+ control.set({});
447
+ return true;
448
+ }
449
+ control.set(selected);
450
+ return true;
451
+ }
452
+ await readOnce();
453
+ if (!shouldWatch) {
454
+ control.close();
455
+ return;
456
+ }
457
+ watcher = watch(path, { persistent: false }, () => {
458
+ readOnce();
459
+ });
460
+ },
461
+ close() {
462
+ watcher?.close();
463
+ }
464
+ });
465
+ }
466
+
467
+ // src/sources/literal.ts
468
+ function literalSource(value) {
469
+ return new Source({
470
+ start(control) {
471
+ control.set(value);
472
+ control.close();
473
+ }
474
+ });
475
+ }
476
+
477
+ // src/errors.ts
478
+ class ConfigError extends Error {
479
+ }
480
+
481
+ // src/config.types.ts
482
+ function readNestedValue(data, path) {
483
+ let node = data;
484
+ for (const key of path) {
485
+ if (node === null || typeof node !== "object")
486
+ return;
487
+ node = node[key];
488
+ }
489
+ return node;
490
+ }
491
+ function typeMismatch(field, value, path) {
492
+ throw new ConfigError(`Expected ${field.type} at "${path.join(".")}", got ${JSON.stringify(value)}`);
493
+ }
494
+ function validate(field, value, path) {
495
+ if (typeof value !== field.type) {
496
+ typeMismatch(field, value, path);
497
+ }
498
+ if (field.type === "string" && field.pattern && !field.pattern.test(value)) {
499
+ throw new ConfigError(`Value at "${path.join(".")}" does not match pattern ${field.pattern}`);
500
+ }
501
+ }
502
+ function coerce(field, raw, path) {
503
+ let value = raw;
504
+ if (field.type === "number" && typeof value !== "number") {
505
+ const num = Number(value);
506
+ if (typeof value !== "string" || value.trim() === "" || Number.isNaN(num)) {
507
+ typeMismatch(field, raw, path);
508
+ }
509
+ value = num;
510
+ } else if (field.type === "boolean" && typeof value !== "boolean") {
511
+ if (value === "true" || value === "1")
512
+ value = true;
513
+ else if (value === "false" || value === "0")
514
+ value = false;
515
+ else
516
+ typeMismatch(field, raw, path);
517
+ } else if (field.type === "string" && typeof value !== "string") {
518
+ typeMismatch(field, raw, path);
519
+ }
520
+ validate(field, value, path);
521
+ return value;
522
+ }
523
+ function deepEqual(a, b) {
524
+ if (a === b)
525
+ return true;
526
+ if (typeof a !== "object" || typeof b !== "object" || a === null || b === null)
527
+ return false;
528
+ const aKeys = Object.keys(a);
529
+ const bKeys = Object.keys(b);
530
+ if (aKeys.length !== bKeys.length)
531
+ return false;
532
+ for (const key of aKeys) {
533
+ if (!deepEqual(a[key], b[key]))
534
+ return false;
535
+ }
536
+ return true;
537
+ }
538
+
539
+ class ConfigField extends Store {
540
+ set() {
541
+ throw new ConfigError("Cannot set a config field: config values are read-only");
542
+ }
543
+ _update(value) {
544
+ super.set(value);
545
+ }
546
+ }
547
+ var stateOf = new WeakMap;
548
+ function isEmbeddedNode(value) {
549
+ return typeof value === "object" && value !== null && stateOf.has(value);
550
+ }
551
+ function collectEmbeddedStates(shape) {
552
+ const result = [];
553
+ for (const key of Object.keys(shape)) {
554
+ const state = stateOf.get(shape[key]);
555
+ if (state)
556
+ result.push(state);
557
+ }
558
+ return result;
559
+ }
560
+
561
+ class ConfigNodeState {
562
+ shape;
563
+ basePath;
564
+ ownSources;
565
+ ownsResolution;
566
+ closableSources;
567
+ rootStores;
568
+ fields = new Map;
569
+ children = new Map;
570
+ snapshotStore;
571
+ readyPromise;
572
+ constructor(shape, rootStores, basePath, ownSources, ownsResolution, closableSources) {
573
+ this.shape = shape;
574
+ this.basePath = basePath;
575
+ this.ownSources = ownSources;
576
+ this.ownsResolution = ownsResolution;
577
+ this.closableSources = closableSources;
578
+ this.rootStores = rootStores;
579
+ const embeddedReady = collectEmbeddedStates(shape).map((state) => state.readyPromise);
580
+ if (ownsResolution) {
581
+ const opened = Promise.all(ownSources.map((source) => source.open())).then((stores) => {
582
+ this.rootStores = stores;
583
+ this.wireLiveUpdates();
584
+ });
585
+ this.readyPromise = Promise.all([opened, ...embeddedReady]).then(() => {
586
+ return;
587
+ });
588
+ } else {
589
+ this.wireLiveUpdates();
590
+ this.readyPromise = Promise.all(embeddedReady).then(() => {
591
+ return;
592
+ });
593
+ }
594
+ }
595
+ wireLiveUpdates() {
596
+ for (const rootStore of this.rootStores) {
597
+ rootStore.listen(() => this.refreshFields());
598
+ }
599
+ }
600
+ resolveField(field, path) {
601
+ for (const rootStore of this.rootStores) {
602
+ const raw = readNestedValue(rootStore.get(), path);
603
+ if (raw !== undefined && raw !== null) {
604
+ return coerce(field, raw, path);
605
+ }
606
+ }
607
+ return field.default !== undefined ? field.default : null;
608
+ }
609
+ refreshFields() {
610
+ for (const [key, field] of this.fields) {
611
+ const schema = this.shape[key];
612
+ if (schema.readonly)
613
+ continue;
614
+ const path = [...this.basePath, key];
615
+ const next = this.resolveField(schema, path);
616
+ if (next !== field.get())
617
+ field._update(next);
618
+ }
619
+ this.refreshSnapshot();
620
+ }
621
+ refreshSnapshot() {
622
+ if (!this.snapshotStore)
623
+ return;
624
+ const next = this.get();
625
+ if (!deepEqual(next, this.snapshotStore.get()))
626
+ this.snapshotStore.set(next);
627
+ }
628
+ fieldFor(key) {
629
+ let field = this.fields.get(key);
630
+ if (!field) {
631
+ const schema = this.shape[key];
632
+ const path = [...this.basePath, key];
633
+ field = new ConfigField(this.resolveField(schema, path));
634
+ this.fields.set(key, field);
635
+ }
636
+ return field;
637
+ }
638
+ childNode(key) {
639
+ const cached = this.children.get(key);
640
+ if (cached)
641
+ return cached;
642
+ const node = this.shape[key];
643
+ const embeddedState = stateOf.get(node);
644
+ if (!embeddedState) {
645
+ throw new ConfigError(`"${key}" is not a nested config group`);
646
+ }
647
+ let result;
648
+ if (embeddedState.ownsResolution) {
649
+ result = node;
650
+ } else {
651
+ const childState = new ConfigNodeState(embeddedState.shape, this.rootStores, [...this.basePath, key], [], false, this.closableSources);
652
+ result = wrapNode(new ConfigNode(childState));
653
+ }
654
+ this.children.set(key, result);
655
+ return result;
656
+ }
657
+ get() {
658
+ const out = {};
659
+ for (const key of Object.keys(this.shape)) {
660
+ const node = this.shape[key];
661
+ if (isEmbeddedNode(node)) {
662
+ out[key] = this.childNode(key).get();
663
+ } else {
664
+ out[key] = this.fieldFor(key).get();
665
+ }
666
+ }
667
+ return out;
668
+ }
669
+ set(key) {
670
+ const node = this.shape[key];
671
+ if (!node)
672
+ throw new ConfigError(`Unknown field "${key}"`);
673
+ throw new ConfigError(`Cannot set "${[...this.basePath, key].join(".")}": config values are read-only`);
674
+ }
675
+ ensureSnapshotStore() {
676
+ if (!this.snapshotStore)
677
+ this.snapshotStore = new Store(this.get());
678
+ return this.snapshotStore;
679
+ }
680
+ subscribe(subscriber) {
681
+ return this.ensureSnapshotStore().subscribe(subscriber);
682
+ }
683
+ listen(subscriber) {
684
+ return this.ensureSnapshotStore().listen(subscriber);
685
+ }
686
+ async close() {
687
+ await Promise.all([
688
+ ...this.closableSources.map((source) => source.close()),
689
+ ...collectEmbeddedStates(this.shape).map((state) => state.close())
690
+ ]);
691
+ }
692
+ }
693
+ function wrapNode(instance) {
694
+ const state = instance._state;
695
+ const proxy = new Proxy(instance, {
696
+ get(target, prop, receiver) {
697
+ if (typeof prop === "string" && Object.prototype.hasOwnProperty.call(state.shape, prop)) {
698
+ const node = state.shape[prop];
699
+ return isEmbeddedNode(node) ? state.childNode(prop) : state.fieldFor(prop);
700
+ }
701
+ return Reflect.get(target, prop, receiver);
702
+ }
703
+ });
704
+ stateOf.set(proxy, state);
705
+ return proxy;
706
+ }
707
+
708
+ class ConfigNodeCore {
709
+ _state;
710
+ constructor(_state) {
711
+ this._state = _state;
712
+ }
713
+ get shape() {
714
+ return this._state.shape;
715
+ }
716
+ get() {
717
+ return this._state.get();
718
+ }
719
+ set(key, _value) {
720
+ this._state.set(key);
721
+ }
722
+ subscribe(subscriber) {
723
+ return this._state.subscribe(subscriber);
724
+ }
725
+ listen(subscriber) {
726
+ return this._state.listen(subscriber);
727
+ }
728
+ close() {
729
+ return this._state.close();
730
+ }
731
+ [Symbol.asyncDispose]() {
732
+ return this.close();
733
+ }
734
+ }
735
+
736
+ class ConfigNodeResolved extends ConfigNodeCore {
737
+ }
738
+
739
+ class ConfigNode extends ConfigNodeCore {
740
+ static [Symbol.hasInstance](instance) {
741
+ return instance instanceof ConfigNodeCore;
742
+ }
743
+ then(onfulfilled, onrejected) {
744
+ return this._state.readyPromise.then(() => wrapNode(new ConfigNodeResolved(this._state))).then(onfulfilled, onrejected);
745
+ }
746
+ }
747
+ function createConfigNode(shape, options) {
748
+ const ownSources = options?.sources ?? [];
749
+ const ownsResolution = options !== undefined;
750
+ const state = new ConfigNodeState(shape, [], [], ownSources, ownsResolution, ownSources);
751
+ return wrapNode(new ConfigNode(state));
752
+ }
753
+ // src/utils/data-types.ts
754
+ function assertPrimitive(value, target) {
755
+ if (typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean") {
756
+ throw new ConfigError(`DataTypes.${target}.from: cannot convert ${typeof value} to ${target}`);
757
+ }
758
+ }
759
+ var numberType = {
760
+ from(value) {
761
+ assertPrimitive(value, "number");
762
+ if (typeof value === "number")
763
+ return value;
764
+ if (typeof value === "boolean")
765
+ return value ? 1 : 0;
766
+ const num = Number(value);
767
+ if (value.trim() === "" || Number.isNaN(num)) {
768
+ throw new ConfigError(`DataTypes.number.from: cannot convert ${JSON.stringify(value)} to number`);
769
+ }
770
+ return num;
771
+ }
772
+ };
773
+ var stringType = {
774
+ from(value) {
775
+ if (value instanceof URL)
776
+ return value.toString();
777
+ assertPrimitive(value, "string");
778
+ return String(value);
779
+ }
780
+ };
781
+ var booleanType = {
782
+ from(value) {
783
+ assertPrimitive(value, "boolean");
784
+ if (typeof value === "boolean")
785
+ return value;
786
+ if (typeof value === "number") {
787
+ if (value === 1)
788
+ return true;
789
+ if (value === 0)
790
+ return false;
791
+ throw new ConfigError(`DataTypes.boolean.from: cannot convert ${value} to boolean`);
792
+ }
793
+ if (value === "true" || value === "1")
794
+ return true;
795
+ if (value === "false" || value === "0")
796
+ return false;
797
+ throw new ConfigError(`DataTypes.boolean.from: cannot convert ${JSON.stringify(value)} to boolean`);
798
+ }
799
+ };
800
+ var listType = {
801
+ from(value) {
802
+ if (Array.isArray(value))
803
+ return value.map((item) => stringType.from(item));
804
+ if (typeof value === "string")
805
+ return value.split(",").map((item) => item.trim());
806
+ throw new ConfigError(`DataTypes.list.from: cannot convert ${typeof value} to list`);
807
+ }
808
+ };
809
+ var dataTypesRegistry = {
810
+ number: numberType,
811
+ string: stringType,
812
+ boolean: booleanType,
813
+ list: listType
814
+ };
815
+ function factory(type) {
816
+ if (type === "number" || type === "string" || type === "boolean" || type === "list") {
817
+ return dataTypesRegistry[type];
818
+ }
819
+ throw new ConfigError(`DataTypes.factory: unknown type "${type}"`);
820
+ }
821
+ var DataTypes = {
822
+ ...dataTypesRegistry,
823
+ factory
824
+ };
825
+
826
+ // src/configs.ts
827
+ var configs = {
828
+ create: createConfigNode
829
+ };
830
+ var configs_default = configs;
831
+ export {
832
+ sseSource,
833
+ mapKey,
834
+ literalSource,
835
+ fileSource,
836
+ fetchSource,
837
+ envSource,
838
+ configs_default as default,
839
+ configs,
840
+ Store,
841
+ Source,
842
+ DotEnv,
843
+ DataTypes,
844
+ ConfigNodeResolved,
845
+ ConfigNode,
846
+ ConfigError
847
+ };