@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
package/dist/zod.js
CHANGED
|
@@ -11,6 +11,130 @@ function coerceNumber(value) {
|
|
|
11
11
|
if (!/^-?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$/.test(value)) return void 0;
|
|
12
12
|
return Number(value);
|
|
13
13
|
}
|
|
14
|
+
var DURATION_UNITS = {
|
|
15
|
+
ns: 1e-6,
|
|
16
|
+
nanosecond: 1e-6,
|
|
17
|
+
nanoseconds: 1e-6,
|
|
18
|
+
us: 1e-3,
|
|
19
|
+
microsecond: 1e-3,
|
|
20
|
+
microseconds: 1e-3,
|
|
21
|
+
ms: 1,
|
|
22
|
+
millisecond: 1,
|
|
23
|
+
milliseconds: 1,
|
|
24
|
+
s: 1e3,
|
|
25
|
+
second: 1e3,
|
|
26
|
+
seconds: 1e3,
|
|
27
|
+
m: 6e4,
|
|
28
|
+
minute: 6e4,
|
|
29
|
+
minutes: 6e4,
|
|
30
|
+
h: 36e5,
|
|
31
|
+
hour: 36e5,
|
|
32
|
+
hours: 36e5,
|
|
33
|
+
d: 864e5,
|
|
34
|
+
day: 864e5,
|
|
35
|
+
days: 864e5
|
|
36
|
+
};
|
|
37
|
+
var OUTPUT_DURATION_UNITS = {
|
|
38
|
+
ns: 1e-6,
|
|
39
|
+
us: 1e-3,
|
|
40
|
+
ms: 1,
|
|
41
|
+
s: 1e3,
|
|
42
|
+
m: 6e4,
|
|
43
|
+
h: 36e5,
|
|
44
|
+
d: 864e5
|
|
45
|
+
};
|
|
46
|
+
function parseDuration(value, outputUnit = "ms") {
|
|
47
|
+
const trimmed = value.trim();
|
|
48
|
+
let i = 0;
|
|
49
|
+
while (i < trimmed.length) {
|
|
50
|
+
const ch = trimmed[i];
|
|
51
|
+
if (ch !== "-" && ch !== "." && (ch < "0" || ch > "9")) break;
|
|
52
|
+
i++;
|
|
53
|
+
}
|
|
54
|
+
if (i === 0) return NaN;
|
|
55
|
+
const num = Number(trimmed.slice(0, i));
|
|
56
|
+
if (Number.isNaN(num)) return NaN;
|
|
57
|
+
const unit = trimmed.slice(i).trim().toLowerCase();
|
|
58
|
+
const mult = DURATION_UNITS[unit];
|
|
59
|
+
if (mult === void 0) return NaN;
|
|
60
|
+
const ms = num * mult;
|
|
61
|
+
const divisor = OUTPUT_DURATION_UNITS[outputUnit];
|
|
62
|
+
if (divisor === void 0) return NaN;
|
|
63
|
+
return ms / divisor;
|
|
64
|
+
}
|
|
65
|
+
var BYTE_UNITS = {
|
|
66
|
+
B: 1,
|
|
67
|
+
byte: 1,
|
|
68
|
+
bytes: 1,
|
|
69
|
+
KB: 1e3,
|
|
70
|
+
kilobyte: 1e3,
|
|
71
|
+
kilobytes: 1e3,
|
|
72
|
+
KiB: 1024,
|
|
73
|
+
kibibyte: 1024,
|
|
74
|
+
kibibytes: 1024,
|
|
75
|
+
MB: 1e6,
|
|
76
|
+
megabyte: 1e6,
|
|
77
|
+
megabytes: 1e6,
|
|
78
|
+
MiB: 1048576,
|
|
79
|
+
mebibyte: 1048576,
|
|
80
|
+
mebibytes: 1048576,
|
|
81
|
+
GB: 1e9,
|
|
82
|
+
gigabyte: 1e9,
|
|
83
|
+
gigabytes: 1e9,
|
|
84
|
+
GiB: 1073741824,
|
|
85
|
+
gibibyte: 1073741824,
|
|
86
|
+
gibibytes: 1073741824,
|
|
87
|
+
TB: 1e12,
|
|
88
|
+
terabyte: 1e12,
|
|
89
|
+
terabytes: 1e12,
|
|
90
|
+
TiB: 1099511627776,
|
|
91
|
+
tebibyte: 1099511627776,
|
|
92
|
+
tebibytes: 1099511627776,
|
|
93
|
+
// lowercase short-form aliases
|
|
94
|
+
b: 1,
|
|
95
|
+
kb: 1e3,
|
|
96
|
+
kib: 1024,
|
|
97
|
+
mb: 1e6,
|
|
98
|
+
mib: 1048576,
|
|
99
|
+
gb: 1e9,
|
|
100
|
+
gib: 1073741824,
|
|
101
|
+
tb: 1e12,
|
|
102
|
+
tib: 1099511627776
|
|
103
|
+
};
|
|
104
|
+
var OUTPUT_BYTE_UNITS = {
|
|
105
|
+
B: 1,
|
|
106
|
+
KB: 1e3,
|
|
107
|
+
KiB: 1024,
|
|
108
|
+
MB: 1e6,
|
|
109
|
+
MiB: 1048576,
|
|
110
|
+
GB: 1e9,
|
|
111
|
+
GiB: 1073741824,
|
|
112
|
+
TB: 1e12,
|
|
113
|
+
TiB: 1099511627776
|
|
114
|
+
};
|
|
115
|
+
function parseBytes(value, outputUnit = "B") {
|
|
116
|
+
const trimmed = value.trim();
|
|
117
|
+
let i = 0;
|
|
118
|
+
while (i < trimmed.length) {
|
|
119
|
+
const ch = trimmed[i];
|
|
120
|
+
if (ch !== "-" && ch !== "." && (ch < "0" || ch > "9")) break;
|
|
121
|
+
i++;
|
|
122
|
+
}
|
|
123
|
+
if (i === 0) return NaN;
|
|
124
|
+
const num = Number(trimmed.slice(0, i));
|
|
125
|
+
if (Number.isNaN(num)) return NaN;
|
|
126
|
+
const unit = trimmed.slice(i).trim();
|
|
127
|
+
let mult = BYTE_UNITS[unit];
|
|
128
|
+
if (mult === void 0) {
|
|
129
|
+
mult = BYTE_UNITS[unit.toLowerCase()];
|
|
130
|
+
}
|
|
131
|
+
if (mult === void 0) return NaN;
|
|
132
|
+
const bytes = num * mult;
|
|
133
|
+
const divisor = OUTPUT_BYTE_UNITS[outputUnit];
|
|
134
|
+
if (divisor === void 0) return NaN;
|
|
135
|
+
const result = bytes / divisor;
|
|
136
|
+
return outputUnit === "B" ? Math.round(result) : result;
|
|
137
|
+
}
|
|
14
138
|
|
|
15
139
|
// src/errors.ts
|
|
16
140
|
var ParseError = class extends Error {
|
|
@@ -127,6 +251,7 @@ function tokenize(input) {
|
|
|
127
251
|
advance();
|
|
128
252
|
advance();
|
|
129
253
|
let value = "";
|
|
254
|
+
let closed = false;
|
|
130
255
|
while (pos < input.length) {
|
|
131
256
|
if (peek() === '"') {
|
|
132
257
|
let quoteCount = 0;
|
|
@@ -136,6 +261,7 @@ function tokenize(input) {
|
|
|
136
261
|
}
|
|
137
262
|
if (quoteCount >= 3) {
|
|
138
263
|
for (let i = 0; i < quoteCount - 3; i++) value += '"';
|
|
264
|
+
closed = true;
|
|
139
265
|
break;
|
|
140
266
|
}
|
|
141
267
|
for (let i = 0; i < quoteCount; i++) value += '"';
|
|
@@ -143,6 +269,9 @@ function tokenize(input) {
|
|
|
143
269
|
}
|
|
144
270
|
value += advance();
|
|
145
271
|
}
|
|
272
|
+
if (!closed) {
|
|
273
|
+
throw new ParseError("unterminated triple-quoted string", sl, sc);
|
|
274
|
+
}
|
|
146
275
|
if (value.startsWith("\n")) value = value.slice(1);
|
|
147
276
|
push("triple_string", value, sl, sc, true);
|
|
148
277
|
continue;
|
|
@@ -181,13 +310,19 @@ function tokenize(input) {
|
|
|
181
310
|
value += "\f";
|
|
182
311
|
break;
|
|
183
312
|
case "u": {
|
|
313
|
+
if (pos + 4 > input.length) {
|
|
314
|
+
throw new ParseError("Invalid unicode escape: not enough characters", line, col);
|
|
315
|
+
}
|
|
184
316
|
const hex = input.slice(pos, pos + 4);
|
|
317
|
+
if (!/^[0-9a-fA-F]{4}$/.test(hex)) {
|
|
318
|
+
throw new ParseError(`Invalid unicode escape: \\u${hex}`, line, col);
|
|
319
|
+
}
|
|
185
320
|
value += String.fromCharCode(parseInt(hex, 16));
|
|
186
321
|
for (let i = 0; i < 4; i++) advance();
|
|
187
322
|
break;
|
|
188
323
|
}
|
|
189
324
|
default:
|
|
190
|
-
|
|
325
|
+
throw new ParseError(`Unknown escape sequence: \\${esc}`, line, col);
|
|
191
326
|
}
|
|
192
327
|
} else {
|
|
193
328
|
value += advance();
|
|
@@ -212,10 +347,10 @@ function tokenize(input) {
|
|
|
212
347
|
return tokens;
|
|
213
348
|
}
|
|
214
349
|
function isUnquotedStart(ch) {
|
|
215
|
-
return ch !== "" && !'{}[],:=+#\n\r "
|
|
350
|
+
return ch !== "" && !'{}[],:=+#\n\r "$?!@*&^\\'.includes(ch);
|
|
216
351
|
}
|
|
217
352
|
function isUnquotedContinue(ch, nextFn) {
|
|
218
|
-
if (ch === "" || '{}[],:=\n\r #"
|
|
353
|
+
if (ch === "" || '{}[],:=\n\r #"$?!@*&^\\'.includes(ch) || ch === " ") return false;
|
|
219
354
|
if (ch === "+" && nextFn() === "=") return false;
|
|
220
355
|
if (ch === "/" && nextFn() === "/") return false;
|
|
221
356
|
return true;
|
|
@@ -317,20 +452,46 @@ function parseTokens(tokens) {
|
|
|
317
452
|
skip("newline");
|
|
318
453
|
const t2 = peek();
|
|
319
454
|
let path2;
|
|
320
|
-
|
|
455
|
+
let required = false;
|
|
456
|
+
if (t2.kind === "unquoted" && (t2.value === "required(" || t2.value === "required" || t2.value.startsWith("required("))) {
|
|
457
|
+
if (t2.value === "required") {
|
|
458
|
+
const next2 = tokens[pos + 1] ?? { kind: "eof", value: "" };
|
|
459
|
+
if (next2.kind !== "unquoted" || !next2.value.startsWith("(")) {
|
|
460
|
+
throw new ParseError("include required must be followed by (", t2.line, t2.col);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
required = true;
|
|
464
|
+
advance();
|
|
465
|
+
const innerPrefix = t2.value.startsWith("required(") ? t2.value.slice("required(".length) : "";
|
|
466
|
+
if (innerPrefix.startsWith("url") || innerPrefix.startsWith("classpath")) {
|
|
467
|
+
throw new ParseError("include url(...) and classpath(...) are not supported", t2.line, t2.col);
|
|
468
|
+
}
|
|
469
|
+
const next = peek();
|
|
470
|
+
if (next.kind === "unquoted" && (next.value === "url" || next.value.startsWith("url(") || next.value === "classpath" || next.value.startsWith("classpath("))) {
|
|
471
|
+
throw new ParseError("include url(...) and classpath(...) are not supported", next.line, next.col);
|
|
472
|
+
}
|
|
473
|
+
while (peek().kind !== "string" && peek().kind !== "eof") advance();
|
|
474
|
+
if (peek().kind === "eof") throw new ParseError("expected include path", t2.line, t2.col);
|
|
475
|
+
path2 = advance().value;
|
|
476
|
+
while (peek().kind !== "newline" && peek().kind !== "rbrace" && peek().kind !== "eof" && peek().kind !== "comma") advance();
|
|
477
|
+
} else if (t2.kind === "string") {
|
|
321
478
|
path2 = advance().value;
|
|
322
479
|
} else if (t2.kind === "unquoted" && (t2.value === "file(" || t2.value === "file")) {
|
|
323
480
|
advance();
|
|
324
481
|
while (peek().kind !== "string" && peek().kind !== "eof") advance();
|
|
325
482
|
if (peek().kind === "eof") throw new ParseError("expected include path", t2.line, t2.col);
|
|
326
483
|
path2 = advance().value;
|
|
327
|
-
while (peek().kind !== "newline" && peek().kind !== "rbrace" && peek().kind !== "eof") advance();
|
|
484
|
+
while (peek().kind !== "newline" && peek().kind !== "rbrace" && peek().kind !== "eof" && peek().kind !== "comma") advance();
|
|
485
|
+
} else if (t2.kind === "unquoted" && (t2.value === "url" || t2.value.startsWith("url("))) {
|
|
486
|
+
throw new ParseError("include url(...) is not supported", t2.line, t2.col);
|
|
487
|
+
} else if (t2.kind === "unquoted" && (t2.value === "classpath" || t2.value.startsWith("classpath("))) {
|
|
488
|
+
throw new ParseError("include classpath(...) is not supported", t2.line, t2.col);
|
|
328
489
|
} else {
|
|
329
490
|
throw new ParseError(`expected include path, got ${t2.kind}`, t2.line, t2.col);
|
|
330
491
|
}
|
|
331
492
|
return {
|
|
332
493
|
key: [],
|
|
333
|
-
value: { kind: "include", path: path2, pos: p },
|
|
494
|
+
value: { kind: "include", path: path2, required, pos: p },
|
|
334
495
|
append: false,
|
|
335
496
|
pos: p
|
|
336
497
|
};
|
|
@@ -365,7 +526,7 @@ function parseTokens(tokens) {
|
|
|
365
526
|
break;
|
|
366
527
|
}
|
|
367
528
|
if (hadSpace) {
|
|
368
|
-
parts.push({ kind: "scalar", value: " ", pos: { line: t2.line, col: t2.col } });
|
|
529
|
+
parts.push({ kind: "scalar", value: " ", pos: { line: t2.line, col: t2.col }, _separator: true });
|
|
369
530
|
}
|
|
370
531
|
parts.push(node);
|
|
371
532
|
}
|
|
@@ -401,13 +562,88 @@ function parseTokens(tokens) {
|
|
|
401
562
|
const t = peek();
|
|
402
563
|
if (t.kind === "lbrace") {
|
|
403
564
|
advance();
|
|
404
|
-
|
|
565
|
+
const first = parseObject(true);
|
|
566
|
+
const allFields = first.kind === "object" ? [...first.fields] : [];
|
|
567
|
+
while (true) {
|
|
568
|
+
skip("newline");
|
|
569
|
+
if (peek().kind === "eof") break;
|
|
570
|
+
if (peek().kind === "lbrace") {
|
|
571
|
+
advance();
|
|
572
|
+
const extra = parseObject(true);
|
|
573
|
+
if (extra.kind === "object") allFields.push(...extra.fields);
|
|
574
|
+
} else {
|
|
575
|
+
const rest = parseObject(false);
|
|
576
|
+
if (rest.kind === "object") allFields.push(...rest.fields);
|
|
577
|
+
break;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
skip("newline");
|
|
581
|
+
const remaining = peek();
|
|
582
|
+
if (remaining.kind !== "eof") {
|
|
583
|
+
throw new ParseError(`Unexpected token '${remaining.value}' after closing brace`, remaining.line, remaining.col);
|
|
584
|
+
}
|
|
585
|
+
return { kind: "object", fields: allFields, pos: first.pos };
|
|
405
586
|
}
|
|
406
587
|
return parseObject(false);
|
|
407
588
|
}
|
|
408
589
|
|
|
409
590
|
// src/internal/resolver/resolver.ts
|
|
410
591
|
import * as nodePath from "path";
|
|
592
|
+
|
|
593
|
+
// src/internal/properties/properties.ts
|
|
594
|
+
function parseProperties(input) {
|
|
595
|
+
const root = /* @__PURE__ */ Object.create(null);
|
|
596
|
+
for (const line of input.split("\n")) {
|
|
597
|
+
const trimmed = line.trim();
|
|
598
|
+
if (trimmed === "" || trimmed.startsWith("#") || trimmed.startsWith("!")) continue;
|
|
599
|
+
const sepIdx = findSeparator(trimmed);
|
|
600
|
+
if (sepIdx === -1) continue;
|
|
601
|
+
const key = trimmed.slice(0, sepIdx).trim();
|
|
602
|
+
const value = trimmed.slice(sepIdx + 1).trim();
|
|
603
|
+
if (key === "") continue;
|
|
604
|
+
setNested(root, key.split("."), value);
|
|
605
|
+
}
|
|
606
|
+
return root;
|
|
607
|
+
}
|
|
608
|
+
function findSeparator(line) {
|
|
609
|
+
for (let i = 0; i < line.length; i++) {
|
|
610
|
+
if (line[i] === "=" || line[i] === ":") return i;
|
|
611
|
+
}
|
|
612
|
+
return -1;
|
|
613
|
+
}
|
|
614
|
+
var DANGEROUS_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
615
|
+
function setNested(obj, segments, value) {
|
|
616
|
+
let current = obj;
|
|
617
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
618
|
+
const seg = segments[i];
|
|
619
|
+
if (seg === void 0 || DANGEROUS_KEYS.has(seg)) return;
|
|
620
|
+
if (!(seg in current) || typeof current[seg] !== "object" || current[seg] === null) {
|
|
621
|
+
current[seg] = /* @__PURE__ */ Object.create(null);
|
|
622
|
+
}
|
|
623
|
+
current = current[seg];
|
|
624
|
+
}
|
|
625
|
+
const last = segments[segments.length - 1];
|
|
626
|
+
if (last === void 0 || DANGEROUS_KEYS.has(last)) return;
|
|
627
|
+
current[last] = value;
|
|
628
|
+
}
|
|
629
|
+
function propertiesToHoconValue(input) {
|
|
630
|
+
const parsed = parseProperties(input);
|
|
631
|
+
return recordToHoconValue(parsed);
|
|
632
|
+
}
|
|
633
|
+
function recordToHoconValue(obj) {
|
|
634
|
+
const fields = /* @__PURE__ */ new Map();
|
|
635
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
636
|
+
if (typeof val === "string") {
|
|
637
|
+
fields.set(key, { kind: "scalar", value: val });
|
|
638
|
+
} else if (val !== null && typeof val === "object") {
|
|
639
|
+
fields.set(key, recordToHoconValue(val));
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
return { kind: "object", fields };
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// src/internal/resolver/resolver.ts
|
|
646
|
+
var separatorValues = /* @__PURE__ */ new WeakSet();
|
|
411
647
|
function isSubst(v) {
|
|
412
648
|
return v._kind === "subst-placeholder";
|
|
413
649
|
}
|
|
@@ -441,7 +677,7 @@ function buildResObj(ast, opts) {
|
|
|
441
677
|
}
|
|
442
678
|
function applyField(obj, field, opts) {
|
|
443
679
|
if (field.key.length === 0 && field.value.kind === "include") {
|
|
444
|
-
const included = loadInclude(field.value.path, opts);
|
|
680
|
+
const included = loadInclude(field.value.path, field.value.required, opts);
|
|
445
681
|
deepMergeResObjInto(obj, included);
|
|
446
682
|
return;
|
|
447
683
|
}
|
|
@@ -479,8 +715,11 @@ function applyField(obj, field, opts) {
|
|
|
479
715
|
}
|
|
480
716
|
function astToResolverValue(ast, opts) {
|
|
481
717
|
switch (ast.kind) {
|
|
482
|
-
case "scalar":
|
|
483
|
-
|
|
718
|
+
case "scalar": {
|
|
719
|
+
const sv = { kind: "scalar", value: ast.value };
|
|
720
|
+
if (ast._separator) separatorValues.add(sv);
|
|
721
|
+
return sv;
|
|
722
|
+
}
|
|
484
723
|
case "array":
|
|
485
724
|
return { kind: "array", items: ast.items.map((i) => astToResolverValue(i, opts)) };
|
|
486
725
|
case "object": {
|
|
@@ -495,6 +734,18 @@ function astToResolverValue(ast, opts) {
|
|
|
495
734
|
return { kind: "scalar", value: null };
|
|
496
735
|
}
|
|
497
736
|
}
|
|
737
|
+
function hoconValueToResObj(hv) {
|
|
738
|
+
const obj = makeResObj();
|
|
739
|
+
if (hv.kind !== "object") return obj;
|
|
740
|
+
for (const [key, val] of hv.fields) {
|
|
741
|
+
if (val.kind === "object") {
|
|
742
|
+
obj.fields.set(key, hoconValueToResObj(val));
|
|
743
|
+
} else {
|
|
744
|
+
obj.fields.set(key, val);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
return obj;
|
|
748
|
+
}
|
|
498
749
|
function deepMergeResObjInto(dst, src) {
|
|
499
750
|
for (const [k, srcVal] of src.fields) {
|
|
500
751
|
const dstVal = dst.fields.get(k);
|
|
@@ -582,12 +833,18 @@ function resolveConcat(nodes, scope, root, resolving, resolvedCache, opts) {
|
|
|
582
833
|
const resolved = nodes.map((n) => resolveVal(n, scope, root, resolving, resolvedCache, opts)).filter((v) => v !== void 0);
|
|
583
834
|
if (resolved.length === 0) return { kind: "scalar", value: null };
|
|
584
835
|
if (resolved.length === 1) return resolved[0];
|
|
585
|
-
|
|
836
|
+
const nonSep = resolved.filter((v) => !separatorValues.has(v));
|
|
837
|
+
if (nonSep.length > 0 && nonSep.every((v) => v.kind === "object")) {
|
|
586
838
|
const merged = /* @__PURE__ */ new Map();
|
|
587
|
-
for (const v of
|
|
839
|
+
for (const v of nonSep) {
|
|
588
840
|
if (v.kind === "object") {
|
|
589
841
|
for (const [k, val] of v.fields) {
|
|
590
|
-
merged.
|
|
842
|
+
const existing = merged.get(k);
|
|
843
|
+
if (existing?.kind === "object" && val.kind === "object") {
|
|
844
|
+
merged.set(k, deepMergeHoconValues(existing, val));
|
|
845
|
+
} else {
|
|
846
|
+
merged.set(k, val);
|
|
847
|
+
}
|
|
591
848
|
}
|
|
592
849
|
}
|
|
593
850
|
}
|
|
@@ -604,6 +861,18 @@ function resolveConcat(nodes, scope, root, resolving, resolvedCache, opts) {
|
|
|
604
861
|
const str = resolved.map((v) => v.kind === "scalar" ? String(v.value) : JSON.stringify(v)).join("");
|
|
605
862
|
return { kind: "scalar", value: str };
|
|
606
863
|
}
|
|
864
|
+
function deepMergeHoconValues(base, overlay) {
|
|
865
|
+
const merged = new Map(base.fields);
|
|
866
|
+
for (const [k, v] of overlay.fields) {
|
|
867
|
+
const existing = merged.get(k);
|
|
868
|
+
if (existing?.kind === "object" && v.kind === "object") {
|
|
869
|
+
merged.set(k, deepMergeHoconValues(existing, v));
|
|
870
|
+
} else {
|
|
871
|
+
merged.set(k, v);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
return { kind: "object", fields: merged };
|
|
875
|
+
}
|
|
607
876
|
function resolveAppend(a, scope, root, resolving, resolvedCache, opts) {
|
|
608
877
|
const existing = resolveVal(a.existing, scope, root, resolving, resolvedCache, opts) ?? { kind: "array", items: [] };
|
|
609
878
|
const elem = resolveVal(a.elem, scope, root, resolving, resolvedCache, opts);
|
|
@@ -661,35 +930,68 @@ function parseSubstPath(raw) {
|
|
|
661
930
|
}
|
|
662
931
|
return segments;
|
|
663
932
|
}
|
|
664
|
-
function
|
|
665
|
-
|
|
933
|
+
function isFileNotFoundError(e) {
|
|
934
|
+
if (!(e instanceof Error)) return false;
|
|
935
|
+
const code = e.code;
|
|
936
|
+
if (code === "ENOENT" || code === "MODULE_NOT_FOUND") return true;
|
|
937
|
+
const msg = e.message.toLowerCase();
|
|
938
|
+
return msg.includes("not found") || msg.includes("no such file") || msg.includes("enoent");
|
|
939
|
+
}
|
|
940
|
+
function loadSingleInclude(candidate, opts) {
|
|
941
|
+
const { readFileSync: readFileSync2, includeStack = [], env } = opts;
|
|
942
|
+
if (includeStack.includes(candidate)) {
|
|
943
|
+
throw new ResolveError(`circular include: ${candidate}`, candidate, 0, 0);
|
|
944
|
+
}
|
|
945
|
+
let content;
|
|
946
|
+
try {
|
|
947
|
+
content = readFileSync2(candidate);
|
|
948
|
+
} catch (e) {
|
|
949
|
+
if (isFileNotFoundError(e)) return void 0;
|
|
950
|
+
throw e;
|
|
951
|
+
}
|
|
952
|
+
if (candidate.endsWith(".properties")) {
|
|
953
|
+
return hoconValueToResObj(propertiesToHoconValue(content));
|
|
954
|
+
}
|
|
955
|
+
const ast = parseTokens(tokenize(content));
|
|
956
|
+
return buildResObj(ast, {
|
|
957
|
+
env,
|
|
958
|
+
baseDir: nodePath.dirname(candidate),
|
|
959
|
+
readFileSync: readFileSync2,
|
|
960
|
+
includeStack: [...includeStack, candidate]
|
|
961
|
+
});
|
|
962
|
+
}
|
|
963
|
+
function loadInclude(includePath, required, opts) {
|
|
964
|
+
const { baseDir, includeStack = [] } = opts;
|
|
666
965
|
const absPath = baseDir ? nodePath.resolve(baseDir, includePath) : nodePath.resolve(includePath);
|
|
667
966
|
if (includeStack.includes(absPath)) {
|
|
668
967
|
throw new ResolveError(`circular include: ${absPath}`, absPath, 0, 0);
|
|
669
968
|
}
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
candidates.push(absPath + ".conf", absPath + ".json");
|
|
969
|
+
if (includeStack.length >= 50) {
|
|
970
|
+
throw new ResolveError(`include depth limit exceeded (max 50)`, includePath, 0, 0);
|
|
673
971
|
}
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
972
|
+
const hasExplicitExt = absPath.endsWith(".conf") || absPath.endsWith(".json") || absPath.endsWith(".properties");
|
|
973
|
+
if (hasExplicitExt) {
|
|
974
|
+
const result = loadSingleInclude(absPath, opts);
|
|
975
|
+
if (result !== void 0) return result;
|
|
976
|
+
if (required) {
|
|
977
|
+
throw new ResolveError(`required include file not found: ${includePath}`, includePath, 0, 0);
|
|
680
978
|
}
|
|
681
|
-
|
|
682
|
-
|
|
979
|
+
return makeResObj();
|
|
980
|
+
}
|
|
981
|
+
const merged = makeResObj();
|
|
982
|
+
let foundAny = false;
|
|
983
|
+
const probeExts = [".properties", ".json", ".conf"];
|
|
984
|
+
for (const ext of probeExts) {
|
|
985
|
+
const obj = loadSingleInclude(`${absPath}${ext}`, opts);
|
|
986
|
+
if (obj !== void 0) {
|
|
987
|
+
deepMergeResObjInto(merged, obj);
|
|
988
|
+
foundAny = true;
|
|
683
989
|
}
|
|
684
|
-
const ast = parseTokens(tokenize(content));
|
|
685
|
-
return buildResObj(ast, {
|
|
686
|
-
env,
|
|
687
|
-
baseDir: nodePath.dirname(candidate),
|
|
688
|
-
readFileSync: readFileSync2,
|
|
689
|
-
includeStack: [...includeStack, candidate]
|
|
690
|
-
});
|
|
691
990
|
}
|
|
692
|
-
|
|
991
|
+
if (!foundAny && required) {
|
|
992
|
+
throw new ResolveError(`required include file not found: ${includePath}`, includePath, 0, 0);
|
|
993
|
+
}
|
|
994
|
+
return merged;
|
|
693
995
|
}
|
|
694
996
|
|
|
695
997
|
// src/config.ts
|
|
@@ -725,6 +1027,20 @@ var Config = class _Config {
|
|
|
725
1027
|
}
|
|
726
1028
|
throw new ConfigError(`expected boolean at ${path2}, got ${typeof v}`, path2);
|
|
727
1029
|
}
|
|
1030
|
+
getDuration(path2, unit) {
|
|
1031
|
+
const v = this.requireScalar(path2);
|
|
1032
|
+
if (typeof v !== "string") throw new ConfigError(`expected duration string at ${path2}, got ${typeof v}`, path2);
|
|
1033
|
+
const result = parseDuration(v, unit);
|
|
1034
|
+
if (Number.isNaN(result)) throw new ConfigError(`invalid duration at ${path2}: ${JSON.stringify(v)}`, path2);
|
|
1035
|
+
return result;
|
|
1036
|
+
}
|
|
1037
|
+
getBytes(path2, unit) {
|
|
1038
|
+
const v = this.requireScalar(path2);
|
|
1039
|
+
if (typeof v !== "string") throw new ConfigError(`expected byte size string at ${path2}, got ${typeof v}`, path2);
|
|
1040
|
+
const result = parseBytes(v, unit);
|
|
1041
|
+
if (Number.isNaN(result)) throw new ConfigError(`invalid byte size at ${path2}: ${JSON.stringify(v)}`, path2);
|
|
1042
|
+
return result;
|
|
1043
|
+
}
|
|
728
1044
|
getConfig(path2) {
|
|
729
1045
|
const v = this.lookupNode(path2);
|
|
730
1046
|
if (v === void 0) throw new ConfigError(`path not found: ${path2}`, path2);
|
|
@@ -751,7 +1067,7 @@ var Config = class _Config {
|
|
|
751
1067
|
return hoconToJs(this.root);
|
|
752
1068
|
}
|
|
753
1069
|
lookupNode(path2) {
|
|
754
|
-
const segments = path2
|
|
1070
|
+
const segments = splitConfigPath(path2);
|
|
755
1071
|
let current = this.root;
|
|
756
1072
|
for (const seg of segments) {
|
|
757
1073
|
if (current.kind !== "object") return void 0;
|
|
@@ -768,6 +1084,45 @@ var Config = class _Config {
|
|
|
768
1084
|
return v.value;
|
|
769
1085
|
}
|
|
770
1086
|
};
|
|
1087
|
+
function splitConfigPath(path2) {
|
|
1088
|
+
const segments = [];
|
|
1089
|
+
let i = 0;
|
|
1090
|
+
while (i < path2.length) {
|
|
1091
|
+
if (path2[i] === '"') {
|
|
1092
|
+
i++;
|
|
1093
|
+
let segment = "";
|
|
1094
|
+
let closed = false;
|
|
1095
|
+
while (i < path2.length) {
|
|
1096
|
+
const ch = path2[i];
|
|
1097
|
+
if (ch === "\\" && i + 1 < path2.length) {
|
|
1098
|
+
const next = path2[i + 1];
|
|
1099
|
+
segment += next;
|
|
1100
|
+
i += 2;
|
|
1101
|
+
continue;
|
|
1102
|
+
}
|
|
1103
|
+
if (ch === '"') {
|
|
1104
|
+
closed = true;
|
|
1105
|
+
i++;
|
|
1106
|
+
break;
|
|
1107
|
+
}
|
|
1108
|
+
segment += ch;
|
|
1109
|
+
i++;
|
|
1110
|
+
}
|
|
1111
|
+
if (!closed) throw new ConfigError(`unterminated quoted path segment: ${path2}`, path2);
|
|
1112
|
+
segments.push(segment);
|
|
1113
|
+
if (i < path2.length && path2[i] === ".") i++;
|
|
1114
|
+
} else {
|
|
1115
|
+
const dot = path2.indexOf(".", i);
|
|
1116
|
+
if (dot === -1) {
|
|
1117
|
+
segments.push(path2.slice(i));
|
|
1118
|
+
break;
|
|
1119
|
+
}
|
|
1120
|
+
segments.push(path2.slice(i, dot));
|
|
1121
|
+
i = dot + 1;
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
return segments;
|
|
1125
|
+
}
|
|
771
1126
|
function hoconToJs(v) {
|
|
772
1127
|
switch (v.kind) {
|
|
773
1128
|
case "scalar":
|
|
@@ -805,14 +1160,19 @@ function getEnv(opts) {
|
|
|
805
1160
|
function defaultReadFileSync(filePath) {
|
|
806
1161
|
return fs.readFileSync(filePath, "utf-8");
|
|
807
1162
|
}
|
|
808
|
-
function
|
|
1163
|
+
function buildResolveContext(input, opts) {
|
|
809
1164
|
const tokens = tokenize(input);
|
|
810
1165
|
const ast = parseTokens(tokens);
|
|
811
1166
|
const resolveOpts = {
|
|
812
1167
|
env: getEnv(opts),
|
|
813
1168
|
baseDir: opts.baseDir,
|
|
814
|
-
readFileSync: opts.readFileSync ?? defaultReadFileSync
|
|
1169
|
+
readFileSync: opts.readFileSync ?? defaultReadFileSync,
|
|
1170
|
+
readFile: opts.readFile
|
|
815
1171
|
};
|
|
1172
|
+
return { ast, resolveOpts };
|
|
1173
|
+
}
|
|
1174
|
+
function parse(input, opts = {}) {
|
|
1175
|
+
const { ast, resolveOpts } = buildResolveContext(input, opts);
|
|
816
1176
|
const value = resolve2(ast, resolveOpts);
|
|
817
1177
|
if (value.kind !== "object") throw new Error("resolved value is not an object");
|
|
818
1178
|
return new Config(value);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@o3co/ts.hocon",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Full Lightbend HOCON spec-compliant parser and config library for TypeScript",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"hocon",
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"@types/node": "^25.5.0",
|
|
42
42
|
"@vitest/coverage-v8": "^4.1.2",
|
|
43
43
|
"tsup": "^8.5.1",
|
|
44
|
+
"typedoc": "^0.28.18",
|
|
44
45
|
"typescript": "^5.9.3",
|
|
45
46
|
"vitest": "^4.1.2",
|
|
46
47
|
"zod": "^4.3.6"
|
|
@@ -49,12 +50,19 @@
|
|
|
49
50
|
"type": "git",
|
|
50
51
|
"url": "https://github.com/o3co/ts.hocon"
|
|
51
52
|
},
|
|
53
|
+
"license": "Apache-2.0",
|
|
54
|
+
"engines": {
|
|
55
|
+
"node": ">=18"
|
|
56
|
+
},
|
|
57
|
+
"homepage": "https://o3co.github.io/ts.hocon/",
|
|
58
|
+
"bugs": "https://github.com/o3co/ts.hocon/issues",
|
|
52
59
|
"scripts": {
|
|
53
60
|
"build": "tsup",
|
|
54
61
|
"test": "vitest run",
|
|
55
62
|
"test:watch": "vitest",
|
|
56
63
|
"typecheck": "tsc --noEmit",
|
|
57
64
|
"coverage": "vitest run --coverage",
|
|
58
|
-
"bench": "vitest bench"
|
|
65
|
+
"bench": "vitest bench",
|
|
66
|
+
"docs": "typedoc"
|
|
59
67
|
}
|
|
60
68
|
}
|