@o3co/ts.hocon 0.1.5 → 1.0.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/README.md +45 -0
- package/dist/index.cjs +544 -39
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +544 -39
- package/dist/{parse-C1NDMzD5.d.cts → parse-C4VOYuEk.d.cts} +8 -5
- package/dist/{parse-C1NDMzD5.d.ts → parse-C4VOYuEk.d.ts} +8 -5
- package/dist/zod.cjs +398 -38
- package/dist/zod.d.cts +1 -1
- package/dist/zod.d.ts +1 -1
- package/dist/zod.js +398 -38
- package/package.json +10 -2
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
type DurationUnit = 'ns' | 'us' | 'ms' | 's' | 'm' | 'h' | 'd';
|
|
2
|
+
type ByteUnit = 'B' | 'KB' | 'KiB' | 'MB' | 'MiB' | 'GB' | 'GiB' | 'TB' | 'TiB';
|
|
3
|
+
|
|
1
4
|
type HoconValue = {
|
|
2
5
|
kind: 'object';
|
|
3
6
|
fields: Map<string, HoconValue>;
|
|
@@ -18,6 +21,8 @@ declare class Config {
|
|
|
18
21
|
getString(path: string): string;
|
|
19
22
|
getNumber(path: string): number;
|
|
20
23
|
getBoolean(path: string): boolean;
|
|
24
|
+
getDuration(path: string, unit?: DurationUnit): number;
|
|
25
|
+
getBytes(path: string, unit?: ByteUnit): number;
|
|
21
26
|
getConfig(path: string): Config;
|
|
22
27
|
getList(path: string): unknown[];
|
|
23
28
|
has(path: string): boolean;
|
|
@@ -36,13 +41,11 @@ type ParseOptions = {
|
|
|
36
41
|
};
|
|
37
42
|
declare function parse(input: string, opts?: ParseOptions): Config;
|
|
38
43
|
/**
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* Note: `include` directives in string input are resolved synchronously
|
|
42
|
-
* via `readFileSync`; `readFile` is not used by this function.
|
|
44
|
+
* Truly async version of `parse()`. Include directives are resolved
|
|
45
|
+
* asynchronously via `readFile` when provided.
|
|
43
46
|
*/
|
|
44
47
|
declare function parseAsync(input: string, opts?: ParseOptions): Promise<Config>;
|
|
45
48
|
declare function parseFile(filePath: string, opts?: ParseOptions): Config;
|
|
46
49
|
declare function parseFileAsync(filePath: string, opts?: ParseOptions): Promise<Config>;
|
|
47
50
|
|
|
48
|
-
export { Config as C, type HoconValue as H, type ParseOptions as P, parseAsync as a, parseFile as b, parseFileAsync as c, parse as p };
|
|
51
|
+
export { type ByteUnit as B, Config as C, type DurationUnit as D, type HoconValue as H, type ParseOptions as P, parseAsync as a, parseFile as b, parseFileAsync as c, parse as p };
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
type DurationUnit = 'ns' | 'us' | 'ms' | 's' | 'm' | 'h' | 'd';
|
|
2
|
+
type ByteUnit = 'B' | 'KB' | 'KiB' | 'MB' | 'MiB' | 'GB' | 'GiB' | 'TB' | 'TiB';
|
|
3
|
+
|
|
1
4
|
type HoconValue = {
|
|
2
5
|
kind: 'object';
|
|
3
6
|
fields: Map<string, HoconValue>;
|
|
@@ -18,6 +21,8 @@ declare class Config {
|
|
|
18
21
|
getString(path: string): string;
|
|
19
22
|
getNumber(path: string): number;
|
|
20
23
|
getBoolean(path: string): boolean;
|
|
24
|
+
getDuration(path: string, unit?: DurationUnit): number;
|
|
25
|
+
getBytes(path: string, unit?: ByteUnit): number;
|
|
21
26
|
getConfig(path: string): Config;
|
|
22
27
|
getList(path: string): unknown[];
|
|
23
28
|
has(path: string): boolean;
|
|
@@ -36,13 +41,11 @@ type ParseOptions = {
|
|
|
36
41
|
};
|
|
37
42
|
declare function parse(input: string, opts?: ParseOptions): Config;
|
|
38
43
|
/**
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* Note: `include` directives in string input are resolved synchronously
|
|
42
|
-
* via `readFileSync`; `readFile` is not used by this function.
|
|
44
|
+
* Truly async version of `parse()`. Include directives are resolved
|
|
45
|
+
* asynchronously via `readFile` when provided.
|
|
43
46
|
*/
|
|
44
47
|
declare function parseAsync(input: string, opts?: ParseOptions): Promise<Config>;
|
|
45
48
|
declare function parseFile(filePath: string, opts?: ParseOptions): Config;
|
|
46
49
|
declare function parseFileAsync(filePath: string, opts?: ParseOptions): Promise<Config>;
|
|
47
50
|
|
|
48
|
-
export { Config as C, type HoconValue as H, type ParseOptions as P, parseAsync as a, parseFile as b, parseFileAsync as c, parse as p };
|
|
51
|
+
export { type ByteUnit as B, Config as C, type DurationUnit as D, type HoconValue as H, type ParseOptions as P, parseAsync as a, parseFile as b, parseFileAsync as c, parse as p };
|
package/dist/zod.cjs
CHANGED
|
@@ -50,6 +50,130 @@ function coerceNumber(value) {
|
|
|
50
50
|
if (!/^-?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$/.test(value)) return void 0;
|
|
51
51
|
return Number(value);
|
|
52
52
|
}
|
|
53
|
+
var DURATION_UNITS = {
|
|
54
|
+
ns: 1e-6,
|
|
55
|
+
nanosecond: 1e-6,
|
|
56
|
+
nanoseconds: 1e-6,
|
|
57
|
+
us: 1e-3,
|
|
58
|
+
microsecond: 1e-3,
|
|
59
|
+
microseconds: 1e-3,
|
|
60
|
+
ms: 1,
|
|
61
|
+
millisecond: 1,
|
|
62
|
+
milliseconds: 1,
|
|
63
|
+
s: 1e3,
|
|
64
|
+
second: 1e3,
|
|
65
|
+
seconds: 1e3,
|
|
66
|
+
m: 6e4,
|
|
67
|
+
minute: 6e4,
|
|
68
|
+
minutes: 6e4,
|
|
69
|
+
h: 36e5,
|
|
70
|
+
hour: 36e5,
|
|
71
|
+
hours: 36e5,
|
|
72
|
+
d: 864e5,
|
|
73
|
+
day: 864e5,
|
|
74
|
+
days: 864e5
|
|
75
|
+
};
|
|
76
|
+
var OUTPUT_DURATION_UNITS = {
|
|
77
|
+
ns: 1e-6,
|
|
78
|
+
us: 1e-3,
|
|
79
|
+
ms: 1,
|
|
80
|
+
s: 1e3,
|
|
81
|
+
m: 6e4,
|
|
82
|
+
h: 36e5,
|
|
83
|
+
d: 864e5
|
|
84
|
+
};
|
|
85
|
+
function parseDuration(value, outputUnit = "ms") {
|
|
86
|
+
const trimmed = value.trim();
|
|
87
|
+
let i = 0;
|
|
88
|
+
while (i < trimmed.length) {
|
|
89
|
+
const ch = trimmed[i];
|
|
90
|
+
if (ch !== "-" && ch !== "." && (ch < "0" || ch > "9")) break;
|
|
91
|
+
i++;
|
|
92
|
+
}
|
|
93
|
+
if (i === 0) return NaN;
|
|
94
|
+
const num = Number(trimmed.slice(0, i));
|
|
95
|
+
if (Number.isNaN(num)) return NaN;
|
|
96
|
+
const unit = trimmed.slice(i).trim().toLowerCase();
|
|
97
|
+
const mult = DURATION_UNITS[unit];
|
|
98
|
+
if (mult === void 0) return NaN;
|
|
99
|
+
const ms = num * mult;
|
|
100
|
+
const divisor = OUTPUT_DURATION_UNITS[outputUnit];
|
|
101
|
+
if (divisor === void 0) return NaN;
|
|
102
|
+
return ms / divisor;
|
|
103
|
+
}
|
|
104
|
+
var BYTE_UNITS = {
|
|
105
|
+
B: 1,
|
|
106
|
+
byte: 1,
|
|
107
|
+
bytes: 1,
|
|
108
|
+
KB: 1e3,
|
|
109
|
+
kilobyte: 1e3,
|
|
110
|
+
kilobytes: 1e3,
|
|
111
|
+
KiB: 1024,
|
|
112
|
+
kibibyte: 1024,
|
|
113
|
+
kibibytes: 1024,
|
|
114
|
+
MB: 1e6,
|
|
115
|
+
megabyte: 1e6,
|
|
116
|
+
megabytes: 1e6,
|
|
117
|
+
MiB: 1048576,
|
|
118
|
+
mebibyte: 1048576,
|
|
119
|
+
mebibytes: 1048576,
|
|
120
|
+
GB: 1e9,
|
|
121
|
+
gigabyte: 1e9,
|
|
122
|
+
gigabytes: 1e9,
|
|
123
|
+
GiB: 1073741824,
|
|
124
|
+
gibibyte: 1073741824,
|
|
125
|
+
gibibytes: 1073741824,
|
|
126
|
+
TB: 1e12,
|
|
127
|
+
terabyte: 1e12,
|
|
128
|
+
terabytes: 1e12,
|
|
129
|
+
TiB: 1099511627776,
|
|
130
|
+
tebibyte: 1099511627776,
|
|
131
|
+
tebibytes: 1099511627776,
|
|
132
|
+
// lowercase short-form aliases
|
|
133
|
+
b: 1,
|
|
134
|
+
kb: 1e3,
|
|
135
|
+
kib: 1024,
|
|
136
|
+
mb: 1e6,
|
|
137
|
+
mib: 1048576,
|
|
138
|
+
gb: 1e9,
|
|
139
|
+
gib: 1073741824,
|
|
140
|
+
tb: 1e12,
|
|
141
|
+
tib: 1099511627776
|
|
142
|
+
};
|
|
143
|
+
var OUTPUT_BYTE_UNITS = {
|
|
144
|
+
B: 1,
|
|
145
|
+
KB: 1e3,
|
|
146
|
+
KiB: 1024,
|
|
147
|
+
MB: 1e6,
|
|
148
|
+
MiB: 1048576,
|
|
149
|
+
GB: 1e9,
|
|
150
|
+
GiB: 1073741824,
|
|
151
|
+
TB: 1e12,
|
|
152
|
+
TiB: 1099511627776
|
|
153
|
+
};
|
|
154
|
+
function parseBytes(value, outputUnit = "B") {
|
|
155
|
+
const trimmed = value.trim();
|
|
156
|
+
let i = 0;
|
|
157
|
+
while (i < trimmed.length) {
|
|
158
|
+
const ch = trimmed[i];
|
|
159
|
+
if (ch !== "-" && ch !== "." && (ch < "0" || ch > "9")) break;
|
|
160
|
+
i++;
|
|
161
|
+
}
|
|
162
|
+
if (i === 0) return NaN;
|
|
163
|
+
const num = Number(trimmed.slice(0, i));
|
|
164
|
+
if (Number.isNaN(num)) return NaN;
|
|
165
|
+
const unit = trimmed.slice(i).trim();
|
|
166
|
+
let mult = BYTE_UNITS[unit];
|
|
167
|
+
if (mult === void 0) {
|
|
168
|
+
mult = BYTE_UNITS[unit.toLowerCase()];
|
|
169
|
+
}
|
|
170
|
+
if (mult === void 0) return NaN;
|
|
171
|
+
const bytes = num * mult;
|
|
172
|
+
const divisor = OUTPUT_BYTE_UNITS[outputUnit];
|
|
173
|
+
if (divisor === void 0) return NaN;
|
|
174
|
+
const result = bytes / divisor;
|
|
175
|
+
return outputUnit === "B" ? Math.round(result) : result;
|
|
176
|
+
}
|
|
53
177
|
|
|
54
178
|
// src/errors.ts
|
|
55
179
|
var ParseError = class extends Error {
|
|
@@ -166,6 +290,7 @@ function tokenize(input) {
|
|
|
166
290
|
advance();
|
|
167
291
|
advance();
|
|
168
292
|
let value = "";
|
|
293
|
+
let closed = false;
|
|
169
294
|
while (pos < input.length) {
|
|
170
295
|
if (peek() === '"') {
|
|
171
296
|
let quoteCount = 0;
|
|
@@ -175,6 +300,7 @@ function tokenize(input) {
|
|
|
175
300
|
}
|
|
176
301
|
if (quoteCount >= 3) {
|
|
177
302
|
for (let i = 0; i < quoteCount - 3; i++) value += '"';
|
|
303
|
+
closed = true;
|
|
178
304
|
break;
|
|
179
305
|
}
|
|
180
306
|
for (let i = 0; i < quoteCount; i++) value += '"';
|
|
@@ -182,6 +308,9 @@ function tokenize(input) {
|
|
|
182
308
|
}
|
|
183
309
|
value += advance();
|
|
184
310
|
}
|
|
311
|
+
if (!closed) {
|
|
312
|
+
throw new ParseError("unterminated triple-quoted string", sl, sc);
|
|
313
|
+
}
|
|
185
314
|
if (value.startsWith("\n")) value = value.slice(1);
|
|
186
315
|
push("triple_string", value, sl, sc, true);
|
|
187
316
|
continue;
|
|
@@ -220,13 +349,19 @@ function tokenize(input) {
|
|
|
220
349
|
value += "\f";
|
|
221
350
|
break;
|
|
222
351
|
case "u": {
|
|
352
|
+
if (pos + 4 > input.length) {
|
|
353
|
+
throw new ParseError("Invalid unicode escape: not enough characters", line, col);
|
|
354
|
+
}
|
|
223
355
|
const hex = input.slice(pos, pos + 4);
|
|
356
|
+
if (!/^[0-9a-fA-F]{4}$/.test(hex)) {
|
|
357
|
+
throw new ParseError(`Invalid unicode escape: \\u${hex}`, line, col);
|
|
358
|
+
}
|
|
224
359
|
value += String.fromCharCode(parseInt(hex, 16));
|
|
225
360
|
for (let i = 0; i < 4; i++) advance();
|
|
226
361
|
break;
|
|
227
362
|
}
|
|
228
363
|
default:
|
|
229
|
-
|
|
364
|
+
throw new ParseError(`Unknown escape sequence: \\${esc}`, line, col);
|
|
230
365
|
}
|
|
231
366
|
} else {
|
|
232
367
|
value += advance();
|
|
@@ -251,10 +386,10 @@ function tokenize(input) {
|
|
|
251
386
|
return tokens;
|
|
252
387
|
}
|
|
253
388
|
function isUnquotedStart(ch) {
|
|
254
|
-
return ch !== "" && !'{}[],:=+#\n\r "
|
|
389
|
+
return ch !== "" && !'{}[],:=+#\n\r "$?!@*&^\\'.includes(ch);
|
|
255
390
|
}
|
|
256
391
|
function isUnquotedContinue(ch, nextFn) {
|
|
257
|
-
if (ch === "" || '{}[],:=\n\r #"
|
|
392
|
+
if (ch === "" || '{}[],:=\n\r #"$?!@*&^\\'.includes(ch) || ch === " ") return false;
|
|
258
393
|
if (ch === "+" && nextFn() === "=") return false;
|
|
259
394
|
if (ch === "/" && nextFn() === "/") return false;
|
|
260
395
|
return true;
|
|
@@ -356,20 +491,46 @@ function parseTokens(tokens) {
|
|
|
356
491
|
skip("newline");
|
|
357
492
|
const t2 = peek();
|
|
358
493
|
let path2;
|
|
359
|
-
|
|
494
|
+
let required = false;
|
|
495
|
+
if (t2.kind === "unquoted" && (t2.value === "required(" || t2.value === "required" || t2.value.startsWith("required("))) {
|
|
496
|
+
if (t2.value === "required") {
|
|
497
|
+
const next2 = tokens[pos + 1] ?? { kind: "eof", value: "" };
|
|
498
|
+
if (next2.kind !== "unquoted" || !next2.value.startsWith("(")) {
|
|
499
|
+
throw new ParseError("include required must be followed by (", t2.line, t2.col);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
required = true;
|
|
503
|
+
advance();
|
|
504
|
+
const innerPrefix = t2.value.startsWith("required(") ? t2.value.slice("required(".length) : "";
|
|
505
|
+
if (innerPrefix.startsWith("url") || innerPrefix.startsWith("classpath")) {
|
|
506
|
+
throw new ParseError("include url(...) and classpath(...) are not supported", t2.line, t2.col);
|
|
507
|
+
}
|
|
508
|
+
const next = peek();
|
|
509
|
+
if (next.kind === "unquoted" && (next.value === "url" || next.value.startsWith("url(") || next.value === "classpath" || next.value.startsWith("classpath("))) {
|
|
510
|
+
throw new ParseError("include url(...) and classpath(...) are not supported", next.line, next.col);
|
|
511
|
+
}
|
|
512
|
+
while (peek().kind !== "string" && peek().kind !== "eof") advance();
|
|
513
|
+
if (peek().kind === "eof") throw new ParseError("expected include path", t2.line, t2.col);
|
|
514
|
+
path2 = advance().value;
|
|
515
|
+
while (peek().kind !== "newline" && peek().kind !== "rbrace" && peek().kind !== "eof" && peek().kind !== "comma") advance();
|
|
516
|
+
} else if (t2.kind === "string") {
|
|
360
517
|
path2 = advance().value;
|
|
361
518
|
} else if (t2.kind === "unquoted" && (t2.value === "file(" || t2.value === "file")) {
|
|
362
519
|
advance();
|
|
363
520
|
while (peek().kind !== "string" && peek().kind !== "eof") advance();
|
|
364
521
|
if (peek().kind === "eof") throw new ParseError("expected include path", t2.line, t2.col);
|
|
365
522
|
path2 = advance().value;
|
|
366
|
-
while (peek().kind !== "newline" && peek().kind !== "rbrace" && peek().kind !== "eof") advance();
|
|
523
|
+
while (peek().kind !== "newline" && peek().kind !== "rbrace" && peek().kind !== "eof" && peek().kind !== "comma") advance();
|
|
524
|
+
} else if (t2.kind === "unquoted" && (t2.value === "url" || t2.value.startsWith("url("))) {
|
|
525
|
+
throw new ParseError("include url(...) is not supported", t2.line, t2.col);
|
|
526
|
+
} else if (t2.kind === "unquoted" && (t2.value === "classpath" || t2.value.startsWith("classpath("))) {
|
|
527
|
+
throw new ParseError("include classpath(...) is not supported", t2.line, t2.col);
|
|
367
528
|
} else {
|
|
368
529
|
throw new ParseError(`expected include path, got ${t2.kind}`, t2.line, t2.col);
|
|
369
530
|
}
|
|
370
531
|
return {
|
|
371
532
|
key: [],
|
|
372
|
-
value: { kind: "include", path: path2, pos: p },
|
|
533
|
+
value: { kind: "include", path: path2, required, pos: p },
|
|
373
534
|
append: false,
|
|
374
535
|
pos: p
|
|
375
536
|
};
|
|
@@ -404,7 +565,7 @@ function parseTokens(tokens) {
|
|
|
404
565
|
break;
|
|
405
566
|
}
|
|
406
567
|
if (hadSpace) {
|
|
407
|
-
parts.push({ kind: "scalar", value: " ", pos: { line: t2.line, col: t2.col } });
|
|
568
|
+
parts.push({ kind: "scalar", value: " ", pos: { line: t2.line, col: t2.col }, _separator: true });
|
|
408
569
|
}
|
|
409
570
|
parts.push(node);
|
|
410
571
|
}
|
|
@@ -440,13 +601,88 @@ function parseTokens(tokens) {
|
|
|
440
601
|
const t = peek();
|
|
441
602
|
if (t.kind === "lbrace") {
|
|
442
603
|
advance();
|
|
443
|
-
|
|
604
|
+
const first = parseObject(true);
|
|
605
|
+
const allFields = first.kind === "object" ? [...first.fields] : [];
|
|
606
|
+
while (true) {
|
|
607
|
+
skip("newline");
|
|
608
|
+
if (peek().kind === "eof") break;
|
|
609
|
+
if (peek().kind === "lbrace") {
|
|
610
|
+
advance();
|
|
611
|
+
const extra = parseObject(true);
|
|
612
|
+
if (extra.kind === "object") allFields.push(...extra.fields);
|
|
613
|
+
} else {
|
|
614
|
+
const rest = parseObject(false);
|
|
615
|
+
if (rest.kind === "object") allFields.push(...rest.fields);
|
|
616
|
+
break;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
skip("newline");
|
|
620
|
+
const remaining = peek();
|
|
621
|
+
if (remaining.kind !== "eof") {
|
|
622
|
+
throw new ParseError(`Unexpected token '${remaining.value}' after closing brace`, remaining.line, remaining.col);
|
|
623
|
+
}
|
|
624
|
+
return { kind: "object", fields: allFields, pos: first.pos };
|
|
444
625
|
}
|
|
445
626
|
return parseObject(false);
|
|
446
627
|
}
|
|
447
628
|
|
|
448
629
|
// src/internal/resolver/resolver.ts
|
|
449
630
|
var nodePath = __toESM(require("path"), 1);
|
|
631
|
+
|
|
632
|
+
// src/internal/properties/properties.ts
|
|
633
|
+
function parseProperties(input) {
|
|
634
|
+
const root = /* @__PURE__ */ Object.create(null);
|
|
635
|
+
for (const line of input.split("\n")) {
|
|
636
|
+
const trimmed = line.trim();
|
|
637
|
+
if (trimmed === "" || trimmed.startsWith("#") || trimmed.startsWith("!")) continue;
|
|
638
|
+
const sepIdx = findSeparator(trimmed);
|
|
639
|
+
if (sepIdx === -1) continue;
|
|
640
|
+
const key = trimmed.slice(0, sepIdx).trim();
|
|
641
|
+
const value = trimmed.slice(sepIdx + 1).trim();
|
|
642
|
+
if (key === "") continue;
|
|
643
|
+
setNested(root, key.split("."), value);
|
|
644
|
+
}
|
|
645
|
+
return root;
|
|
646
|
+
}
|
|
647
|
+
function findSeparator(line) {
|
|
648
|
+
for (let i = 0; i < line.length; i++) {
|
|
649
|
+
if (line[i] === "=" || line[i] === ":") return i;
|
|
650
|
+
}
|
|
651
|
+
return -1;
|
|
652
|
+
}
|
|
653
|
+
var DANGEROUS_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
654
|
+
function setNested(obj, segments, value) {
|
|
655
|
+
let current = obj;
|
|
656
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
657
|
+
const seg = segments[i];
|
|
658
|
+
if (seg === void 0 || DANGEROUS_KEYS.has(seg)) return;
|
|
659
|
+
if (!(seg in current) || typeof current[seg] !== "object" || current[seg] === null) {
|
|
660
|
+
current[seg] = /* @__PURE__ */ Object.create(null);
|
|
661
|
+
}
|
|
662
|
+
current = current[seg];
|
|
663
|
+
}
|
|
664
|
+
const last = segments[segments.length - 1];
|
|
665
|
+
if (last === void 0 || DANGEROUS_KEYS.has(last)) return;
|
|
666
|
+
current[last] = value;
|
|
667
|
+
}
|
|
668
|
+
function propertiesToHoconValue(input) {
|
|
669
|
+
const parsed = parseProperties(input);
|
|
670
|
+
return recordToHoconValue(parsed);
|
|
671
|
+
}
|
|
672
|
+
function recordToHoconValue(obj) {
|
|
673
|
+
const fields = /* @__PURE__ */ new Map();
|
|
674
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
675
|
+
if (typeof val === "string") {
|
|
676
|
+
fields.set(key, { kind: "scalar", value: val });
|
|
677
|
+
} else if (val !== null && typeof val === "object") {
|
|
678
|
+
fields.set(key, recordToHoconValue(val));
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
return { kind: "object", fields };
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// src/internal/resolver/resolver.ts
|
|
685
|
+
var separatorValues = /* @__PURE__ */ new WeakSet();
|
|
450
686
|
function isSubst(v) {
|
|
451
687
|
return v._kind === "subst-placeholder";
|
|
452
688
|
}
|
|
@@ -480,7 +716,7 @@ function buildResObj(ast, opts) {
|
|
|
480
716
|
}
|
|
481
717
|
function applyField(obj, field, opts) {
|
|
482
718
|
if (field.key.length === 0 && field.value.kind === "include") {
|
|
483
|
-
const included = loadInclude(field.value.path, opts);
|
|
719
|
+
const included = loadInclude(field.value.path, field.value.required, opts);
|
|
484
720
|
deepMergeResObjInto(obj, included);
|
|
485
721
|
return;
|
|
486
722
|
}
|
|
@@ -518,8 +754,11 @@ function applyField(obj, field, opts) {
|
|
|
518
754
|
}
|
|
519
755
|
function astToResolverValue(ast, opts) {
|
|
520
756
|
switch (ast.kind) {
|
|
521
|
-
case "scalar":
|
|
522
|
-
|
|
757
|
+
case "scalar": {
|
|
758
|
+
const sv = { kind: "scalar", value: ast.value };
|
|
759
|
+
if (ast._separator) separatorValues.add(sv);
|
|
760
|
+
return sv;
|
|
761
|
+
}
|
|
523
762
|
case "array":
|
|
524
763
|
return { kind: "array", items: ast.items.map((i) => astToResolverValue(i, opts)) };
|
|
525
764
|
case "object": {
|
|
@@ -534,6 +773,18 @@ function astToResolverValue(ast, opts) {
|
|
|
534
773
|
return { kind: "scalar", value: null };
|
|
535
774
|
}
|
|
536
775
|
}
|
|
776
|
+
function hoconValueToResObj(hv) {
|
|
777
|
+
const obj = makeResObj();
|
|
778
|
+
if (hv.kind !== "object") return obj;
|
|
779
|
+
for (const [key, val] of hv.fields) {
|
|
780
|
+
if (val.kind === "object") {
|
|
781
|
+
obj.fields.set(key, hoconValueToResObj(val));
|
|
782
|
+
} else {
|
|
783
|
+
obj.fields.set(key, val);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
return obj;
|
|
787
|
+
}
|
|
537
788
|
function deepMergeResObjInto(dst, src) {
|
|
538
789
|
for (const [k, srcVal] of src.fields) {
|
|
539
790
|
const dstVal = dst.fields.get(k);
|
|
@@ -621,12 +872,18 @@ function resolveConcat(nodes, scope, root, resolving, resolvedCache, opts) {
|
|
|
621
872
|
const resolved = nodes.map((n) => resolveVal(n, scope, root, resolving, resolvedCache, opts)).filter((v) => v !== void 0);
|
|
622
873
|
if (resolved.length === 0) return { kind: "scalar", value: null };
|
|
623
874
|
if (resolved.length === 1) return resolved[0];
|
|
624
|
-
|
|
875
|
+
const nonSep = resolved.filter((v) => !separatorValues.has(v));
|
|
876
|
+
if (nonSep.length > 0 && nonSep.every((v) => v.kind === "object")) {
|
|
625
877
|
const merged = /* @__PURE__ */ new Map();
|
|
626
|
-
for (const v of
|
|
878
|
+
for (const v of nonSep) {
|
|
627
879
|
if (v.kind === "object") {
|
|
628
880
|
for (const [k, val] of v.fields) {
|
|
629
|
-
merged.
|
|
881
|
+
const existing = merged.get(k);
|
|
882
|
+
if (existing?.kind === "object" && val.kind === "object") {
|
|
883
|
+
merged.set(k, deepMergeHoconValues(existing, val));
|
|
884
|
+
} else {
|
|
885
|
+
merged.set(k, val);
|
|
886
|
+
}
|
|
630
887
|
}
|
|
631
888
|
}
|
|
632
889
|
}
|
|
@@ -643,6 +900,18 @@ function resolveConcat(nodes, scope, root, resolving, resolvedCache, opts) {
|
|
|
643
900
|
const str = resolved.map((v) => v.kind === "scalar" ? String(v.value) : JSON.stringify(v)).join("");
|
|
644
901
|
return { kind: "scalar", value: str };
|
|
645
902
|
}
|
|
903
|
+
function deepMergeHoconValues(base, overlay) {
|
|
904
|
+
const merged = new Map(base.fields);
|
|
905
|
+
for (const [k, v] of overlay.fields) {
|
|
906
|
+
const existing = merged.get(k);
|
|
907
|
+
if (existing?.kind === "object" && v.kind === "object") {
|
|
908
|
+
merged.set(k, deepMergeHoconValues(existing, v));
|
|
909
|
+
} else {
|
|
910
|
+
merged.set(k, v);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
return { kind: "object", fields: merged };
|
|
914
|
+
}
|
|
646
915
|
function resolveAppend(a, scope, root, resolving, resolvedCache, opts) {
|
|
647
916
|
const existing = resolveVal(a.existing, scope, root, resolving, resolvedCache, opts) ?? { kind: "array", items: [] };
|
|
648
917
|
const elem = resolveVal(a.elem, scope, root, resolving, resolvedCache, opts);
|
|
@@ -700,35 +969,68 @@ function parseSubstPath(raw) {
|
|
|
700
969
|
}
|
|
701
970
|
return segments;
|
|
702
971
|
}
|
|
703
|
-
function
|
|
704
|
-
|
|
972
|
+
function isFileNotFoundError(e) {
|
|
973
|
+
if (!(e instanceof Error)) return false;
|
|
974
|
+
const code = e.code;
|
|
975
|
+
if (code === "ENOENT" || code === "MODULE_NOT_FOUND") return true;
|
|
976
|
+
const msg = e.message.toLowerCase();
|
|
977
|
+
return msg.includes("not found") || msg.includes("no such file") || msg.includes("enoent");
|
|
978
|
+
}
|
|
979
|
+
function loadSingleInclude(candidate, opts) {
|
|
980
|
+
const { readFileSync: readFileSync2, includeStack = [], env } = opts;
|
|
981
|
+
if (includeStack.includes(candidate)) {
|
|
982
|
+
throw new ResolveError(`circular include: ${candidate}`, candidate, 0, 0);
|
|
983
|
+
}
|
|
984
|
+
let content;
|
|
985
|
+
try {
|
|
986
|
+
content = readFileSync2(candidate);
|
|
987
|
+
} catch (e) {
|
|
988
|
+
if (isFileNotFoundError(e)) return void 0;
|
|
989
|
+
throw e;
|
|
990
|
+
}
|
|
991
|
+
if (candidate.endsWith(".properties")) {
|
|
992
|
+
return hoconValueToResObj(propertiesToHoconValue(content));
|
|
993
|
+
}
|
|
994
|
+
const ast = parseTokens(tokenize(content));
|
|
995
|
+
return buildResObj(ast, {
|
|
996
|
+
env,
|
|
997
|
+
baseDir: nodePath.dirname(candidate),
|
|
998
|
+
readFileSync: readFileSync2,
|
|
999
|
+
includeStack: [...includeStack, candidate]
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
function loadInclude(includePath, required, opts) {
|
|
1003
|
+
const { baseDir, includeStack = [] } = opts;
|
|
705
1004
|
const absPath = baseDir ? nodePath.resolve(baseDir, includePath) : nodePath.resolve(includePath);
|
|
706
1005
|
if (includeStack.includes(absPath)) {
|
|
707
1006
|
throw new ResolveError(`circular include: ${absPath}`, absPath, 0, 0);
|
|
708
1007
|
}
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
candidates.push(absPath + ".conf", absPath + ".json");
|
|
1008
|
+
if (includeStack.length >= 50) {
|
|
1009
|
+
throw new ResolveError(`include depth limit exceeded (max 50)`, includePath, 0, 0);
|
|
712
1010
|
}
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
1011
|
+
const hasExplicitExt = absPath.endsWith(".conf") || absPath.endsWith(".json") || absPath.endsWith(".properties");
|
|
1012
|
+
if (hasExplicitExt) {
|
|
1013
|
+
const result = loadSingleInclude(absPath, opts);
|
|
1014
|
+
if (result !== void 0) return result;
|
|
1015
|
+
if (required) {
|
|
1016
|
+
throw new ResolveError(`required include file not found: ${includePath}`, includePath, 0, 0);
|
|
719
1017
|
}
|
|
720
|
-
|
|
721
|
-
|
|
1018
|
+
return makeResObj();
|
|
1019
|
+
}
|
|
1020
|
+
const merged = makeResObj();
|
|
1021
|
+
let foundAny = false;
|
|
1022
|
+
const probeExts = [".properties", ".json", ".conf"];
|
|
1023
|
+
for (const ext of probeExts) {
|
|
1024
|
+
const obj = loadSingleInclude(`${absPath}${ext}`, opts);
|
|
1025
|
+
if (obj !== void 0) {
|
|
1026
|
+
deepMergeResObjInto(merged, obj);
|
|
1027
|
+
foundAny = true;
|
|
722
1028
|
}
|
|
723
|
-
const ast = parseTokens(tokenize(content));
|
|
724
|
-
return buildResObj(ast, {
|
|
725
|
-
env,
|
|
726
|
-
baseDir: nodePath.dirname(candidate),
|
|
727
|
-
readFileSync: readFileSync2,
|
|
728
|
-
includeStack: [...includeStack, candidate]
|
|
729
|
-
});
|
|
730
1029
|
}
|
|
731
|
-
|
|
1030
|
+
if (!foundAny && required) {
|
|
1031
|
+
throw new ResolveError(`required include file not found: ${includePath}`, includePath, 0, 0);
|
|
1032
|
+
}
|
|
1033
|
+
return merged;
|
|
732
1034
|
}
|
|
733
1035
|
|
|
734
1036
|
// src/config.ts
|
|
@@ -764,6 +1066,20 @@ var Config = class _Config {
|
|
|
764
1066
|
}
|
|
765
1067
|
throw new ConfigError(`expected boolean at ${path2}, got ${typeof v}`, path2);
|
|
766
1068
|
}
|
|
1069
|
+
getDuration(path2, unit) {
|
|
1070
|
+
const v = this.requireScalar(path2);
|
|
1071
|
+
if (typeof v !== "string") throw new ConfigError(`expected duration string at ${path2}, got ${typeof v}`, path2);
|
|
1072
|
+
const result = parseDuration(v, unit);
|
|
1073
|
+
if (Number.isNaN(result)) throw new ConfigError(`invalid duration at ${path2}: ${JSON.stringify(v)}`, path2);
|
|
1074
|
+
return result;
|
|
1075
|
+
}
|
|
1076
|
+
getBytes(path2, unit) {
|
|
1077
|
+
const v = this.requireScalar(path2);
|
|
1078
|
+
if (typeof v !== "string") throw new ConfigError(`expected byte size string at ${path2}, got ${typeof v}`, path2);
|
|
1079
|
+
const result = parseBytes(v, unit);
|
|
1080
|
+
if (Number.isNaN(result)) throw new ConfigError(`invalid byte size at ${path2}: ${JSON.stringify(v)}`, path2);
|
|
1081
|
+
return result;
|
|
1082
|
+
}
|
|
767
1083
|
getConfig(path2) {
|
|
768
1084
|
const v = this.lookupNode(path2);
|
|
769
1085
|
if (v === void 0) throw new ConfigError(`path not found: ${path2}`, path2);
|
|
@@ -790,7 +1106,7 @@ var Config = class _Config {
|
|
|
790
1106
|
return hoconToJs(this.root);
|
|
791
1107
|
}
|
|
792
1108
|
lookupNode(path2) {
|
|
793
|
-
const segments = path2
|
|
1109
|
+
const segments = splitConfigPath(path2);
|
|
794
1110
|
let current = this.root;
|
|
795
1111
|
for (const seg of segments) {
|
|
796
1112
|
if (current.kind !== "object") return void 0;
|
|
@@ -807,6 +1123,45 @@ var Config = class _Config {
|
|
|
807
1123
|
return v.value;
|
|
808
1124
|
}
|
|
809
1125
|
};
|
|
1126
|
+
function splitConfigPath(path2) {
|
|
1127
|
+
const segments = [];
|
|
1128
|
+
let i = 0;
|
|
1129
|
+
while (i < path2.length) {
|
|
1130
|
+
if (path2[i] === '"') {
|
|
1131
|
+
i++;
|
|
1132
|
+
let segment = "";
|
|
1133
|
+
let closed = false;
|
|
1134
|
+
while (i < path2.length) {
|
|
1135
|
+
const ch = path2[i];
|
|
1136
|
+
if (ch === "\\" && i + 1 < path2.length) {
|
|
1137
|
+
const next = path2[i + 1];
|
|
1138
|
+
segment += next;
|
|
1139
|
+
i += 2;
|
|
1140
|
+
continue;
|
|
1141
|
+
}
|
|
1142
|
+
if (ch === '"') {
|
|
1143
|
+
closed = true;
|
|
1144
|
+
i++;
|
|
1145
|
+
break;
|
|
1146
|
+
}
|
|
1147
|
+
segment += ch;
|
|
1148
|
+
i++;
|
|
1149
|
+
}
|
|
1150
|
+
if (!closed) throw new ConfigError(`unterminated quoted path segment: ${path2}`, path2);
|
|
1151
|
+
segments.push(segment);
|
|
1152
|
+
if (i < path2.length && path2[i] === ".") i++;
|
|
1153
|
+
} else {
|
|
1154
|
+
const dot = path2.indexOf(".", i);
|
|
1155
|
+
if (dot === -1) {
|
|
1156
|
+
segments.push(path2.slice(i));
|
|
1157
|
+
break;
|
|
1158
|
+
}
|
|
1159
|
+
segments.push(path2.slice(i, dot));
|
|
1160
|
+
i = dot + 1;
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
return segments;
|
|
1164
|
+
}
|
|
810
1165
|
function hoconToJs(v) {
|
|
811
1166
|
switch (v.kind) {
|
|
812
1167
|
case "scalar":
|
|
@@ -844,14 +1199,19 @@ function getEnv(opts) {
|
|
|
844
1199
|
function defaultReadFileSync(filePath) {
|
|
845
1200
|
return fs.readFileSync(filePath, "utf-8");
|
|
846
1201
|
}
|
|
847
|
-
function
|
|
1202
|
+
function buildResolveContext(input, opts) {
|
|
848
1203
|
const tokens = tokenize(input);
|
|
849
1204
|
const ast = parseTokens(tokens);
|
|
850
1205
|
const resolveOpts = {
|
|
851
1206
|
env: getEnv(opts),
|
|
852
1207
|
baseDir: opts.baseDir,
|
|
853
|
-
readFileSync: opts.readFileSync ?? defaultReadFileSync
|
|
1208
|
+
readFileSync: opts.readFileSync ?? defaultReadFileSync,
|
|
1209
|
+
readFile: opts.readFile
|
|
854
1210
|
};
|
|
1211
|
+
return { ast, resolveOpts };
|
|
1212
|
+
}
|
|
1213
|
+
function parse(input, opts = {}) {
|
|
1214
|
+
const { ast, resolveOpts } = buildResolveContext(input, opts);
|
|
855
1215
|
const value = resolve2(ast, resolveOpts);
|
|
856
1216
|
if (value.kind !== "object") throw new Error("resolved value is not an object");
|
|
857
1217
|
return new Config(value);
|
package/dist/zod.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ZodType } from 'zod';
|
|
2
|
-
import { C as Config, P as ParseOptions } from './parse-
|
|
2
|
+
import { C as Config, P as ParseOptions } from './parse-C4VOYuEk.cjs';
|
|
3
3
|
|
|
4
4
|
declare function validate<T>(config: Config, schema: ZodType<T>): T;
|
|
5
5
|
declare function getValidated<T>(config: Config, path: string, schema: ZodType<T>): T;
|
package/dist/zod.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ZodType } from 'zod';
|
|
2
|
-
import { C as Config, P as ParseOptions } from './parse-
|
|
2
|
+
import { C as Config, P as ParseOptions } from './parse-C4VOYuEk.js';
|
|
3
3
|
|
|
4
4
|
declare function validate<T>(config: Config, schema: ZodType<T>): T;
|
|
5
5
|
declare function getValidated<T>(config: Config, path: string, schema: ZodType<T>): T;
|