@o3co/ts.hocon 1.9.0 → 1.10.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 +33 -1
- package/dist/adapters/env.cjs +1668 -0
- package/dist/adapters/env.d.cts +41 -0
- package/dist/adapters/env.d.ts +41 -0
- package/dist/adapters/env.js +1629 -0
- package/dist/adapters/jsonc.cjs +1613 -0
- package/dist/adapters/jsonc.d.cts +25 -0
- package/dist/adapters/jsonc.d.ts +25 -0
- package/dist/adapters/jsonc.js +1574 -0
- package/dist/adapters/properties.cjs +1681 -0
- package/dist/adapters/properties.d.cts +27 -0
- package/dist/adapters/properties.d.ts +27 -0
- package/dist/adapters/properties.js +1643 -0
- package/dist/adapters/toml.cjs +1570 -0
- package/dist/adapters/toml.d.cts +18 -0
- package/dist/adapters/toml.d.ts +18 -0
- package/dist/adapters/toml.js +1534 -0
- package/dist/adapters/yaml.cjs +1610 -0
- package/dist/adapters/yaml.d.cts +43 -0
- package/dist/adapters/yaml.d.ts +43 -0
- package/dist/adapters/yaml.js +1573 -0
- package/dist/index.cjs +107 -17
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +107 -17
- package/dist/zod.cjs +107 -17
- package/dist/zod.d.cts +1 -1
- package/dist/zod.d.ts +1 -1
- package/dist/zod.js +107 -17
- package/package.json +47 -13
- package/dist/{config-D27s3_aG.d.cts → config-PdhVgeLS.d.cts} +3 -3
- package/dist/{config-D27s3_aG.d.ts → config-PdhVgeLS.d.ts} +3 -3
|
@@ -0,0 +1,1629 @@
|
|
|
1
|
+
// src/coerce.ts
|
|
2
|
+
var DECIMAL_NUMBER_RE = /^-?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$/;
|
|
3
|
+
function isHoconWs(cp) {
|
|
4
|
+
if (cp === 9 || cp === 10 || cp === 11 || cp === 12 || cp === 13) return true;
|
|
5
|
+
if (cp >= 28 && cp <= 31) return true;
|
|
6
|
+
if (cp === 32 || cp === 160 || cp === 65279) return true;
|
|
7
|
+
if (cp === 5760) return true;
|
|
8
|
+
if (cp >= 8192 && cp <= 8202) return true;
|
|
9
|
+
if (cp === 8232 || cp === 8233 || cp === 8239 || cp === 8287) return true;
|
|
10
|
+
if (cp === 12288) return true;
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
function trimHoconWs(s) {
|
|
14
|
+
let start = 0;
|
|
15
|
+
let end = s.length;
|
|
16
|
+
while (start < end && isHoconWs(s.charCodeAt(start))) {
|
|
17
|
+
start++;
|
|
18
|
+
}
|
|
19
|
+
while (end > start && isHoconWs(s.charCodeAt(end - 1))) {
|
|
20
|
+
end--;
|
|
21
|
+
}
|
|
22
|
+
return s.slice(start, end);
|
|
23
|
+
}
|
|
24
|
+
var TRUTHY = /* @__PURE__ */ new Set(["true", "yes", "on"]);
|
|
25
|
+
var FALSY = /* @__PURE__ */ new Set(["false", "no", "off"]);
|
|
26
|
+
function coerceBoolean(value) {
|
|
27
|
+
const lower = value.toLowerCase();
|
|
28
|
+
if (TRUTHY.has(lower)) return true;
|
|
29
|
+
if (FALSY.has(lower)) return false;
|
|
30
|
+
return void 0;
|
|
31
|
+
}
|
|
32
|
+
function coerceNumber(value) {
|
|
33
|
+
if (!DECIMAL_NUMBER_RE.test(value)) return void 0;
|
|
34
|
+
return Number(value);
|
|
35
|
+
}
|
|
36
|
+
var DURATION_UNITS = {
|
|
37
|
+
ns: 1e-6,
|
|
38
|
+
nanosecond: 1e-6,
|
|
39
|
+
nanoseconds: 1e-6,
|
|
40
|
+
us: 1e-3,
|
|
41
|
+
microsecond: 1e-3,
|
|
42
|
+
microseconds: 1e-3,
|
|
43
|
+
ms: 1,
|
|
44
|
+
millisecond: 1,
|
|
45
|
+
milliseconds: 1,
|
|
46
|
+
s: 1e3,
|
|
47
|
+
second: 1e3,
|
|
48
|
+
seconds: 1e3,
|
|
49
|
+
m: 6e4,
|
|
50
|
+
minute: 6e4,
|
|
51
|
+
minutes: 6e4,
|
|
52
|
+
h: 36e5,
|
|
53
|
+
hour: 36e5,
|
|
54
|
+
hours: 36e5,
|
|
55
|
+
d: 864e5,
|
|
56
|
+
day: 864e5,
|
|
57
|
+
days: 864e5
|
|
58
|
+
};
|
|
59
|
+
var OUTPUT_DURATION_UNITS = {
|
|
60
|
+
ns: 1e-6,
|
|
61
|
+
us: 1e-3,
|
|
62
|
+
ms: 1,
|
|
63
|
+
s: 1e3,
|
|
64
|
+
m: 6e4,
|
|
65
|
+
h: 36e5,
|
|
66
|
+
d: 864e5
|
|
67
|
+
};
|
|
68
|
+
function parseDuration(value, outputUnit = "ms") {
|
|
69
|
+
const trimmed = trimHoconWs(value);
|
|
70
|
+
let i = 0;
|
|
71
|
+
while (i < trimmed.length) {
|
|
72
|
+
const ch = trimmed[i];
|
|
73
|
+
if (i === 0 && (ch === "-" || ch === "+")) {
|
|
74
|
+
i++;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (ch !== "." && (ch < "0" || ch > "9")) break;
|
|
78
|
+
i++;
|
|
79
|
+
}
|
|
80
|
+
if (i === 0) return NaN;
|
|
81
|
+
const num = Number(trimmed.slice(0, i));
|
|
82
|
+
if (Number.isNaN(num)) return NaN;
|
|
83
|
+
const unit = trimHoconWs(trimmed.slice(i));
|
|
84
|
+
const divisor = OUTPUT_DURATION_UNITS[outputUnit];
|
|
85
|
+
if (divisor === void 0) return NaN;
|
|
86
|
+
if (unit === "") {
|
|
87
|
+
return num / divisor;
|
|
88
|
+
}
|
|
89
|
+
const mult = DURATION_UNITS[unit];
|
|
90
|
+
if (mult === void 0) return NaN;
|
|
91
|
+
const ms = num * mult;
|
|
92
|
+
return ms / divisor;
|
|
93
|
+
}
|
|
94
|
+
var BYTE_UNITS = {
|
|
95
|
+
B: 1,
|
|
96
|
+
byte: 1,
|
|
97
|
+
bytes: 1,
|
|
98
|
+
KB: 1e3,
|
|
99
|
+
kilobyte: 1e3,
|
|
100
|
+
kilobytes: 1e3,
|
|
101
|
+
KiB: 1024,
|
|
102
|
+
kibibyte: 1024,
|
|
103
|
+
kibibytes: 1024,
|
|
104
|
+
MB: 1e6,
|
|
105
|
+
megabyte: 1e6,
|
|
106
|
+
megabytes: 1e6,
|
|
107
|
+
MiB: 1048576,
|
|
108
|
+
mebibyte: 1048576,
|
|
109
|
+
mebibytes: 1048576,
|
|
110
|
+
GB: 1e9,
|
|
111
|
+
gigabyte: 1e9,
|
|
112
|
+
gigabytes: 1e9,
|
|
113
|
+
GiB: 1073741824,
|
|
114
|
+
gibibyte: 1073741824,
|
|
115
|
+
gibibytes: 1073741824,
|
|
116
|
+
TB: 1e12,
|
|
117
|
+
terabyte: 1e12,
|
|
118
|
+
terabytes: 1e12,
|
|
119
|
+
TiB: 1099511627776,
|
|
120
|
+
tebibyte: 1099511627776,
|
|
121
|
+
tebibytes: 1099511627776,
|
|
122
|
+
// S21.4 — HOCON.md L1385: single-letter abbreviations → powers of two (java -Xmx convention).
|
|
123
|
+
// Lightbend typesafe-config 1.4.3 verified: 1K=1024, 1M=1048576, etc.
|
|
124
|
+
// Both upper- and lowercase accepted per Lightbend behaviour.
|
|
125
|
+
// Z/Y (2^70/2^80) are deferred — they overflow MAX_SAFE_INTEGER and require BigInt accessor.
|
|
126
|
+
K: 1024,
|
|
127
|
+
k: 1024,
|
|
128
|
+
M: 1024 ** 2,
|
|
129
|
+
m: 1024 ** 2,
|
|
130
|
+
G: 1024 ** 3,
|
|
131
|
+
g: 1024 ** 3,
|
|
132
|
+
T: 1024 ** 4,
|
|
133
|
+
t: 1024 ** 4,
|
|
134
|
+
P: 1024 ** 5,
|
|
135
|
+
p: 1024 ** 5,
|
|
136
|
+
E: 1024 ** 6,
|
|
137
|
+
e: 1024 ** 6,
|
|
138
|
+
// lowercase short-form aliases
|
|
139
|
+
b: 1,
|
|
140
|
+
kb: 1e3,
|
|
141
|
+
kib: 1024,
|
|
142
|
+
mb: 1e6,
|
|
143
|
+
mib: 1048576,
|
|
144
|
+
gb: 1e9,
|
|
145
|
+
gib: 1073741824,
|
|
146
|
+
tb: 1e12,
|
|
147
|
+
tib: 1099511627776
|
|
148
|
+
};
|
|
149
|
+
var OUTPUT_BYTE_UNITS = {
|
|
150
|
+
B: 1,
|
|
151
|
+
KB: 1e3,
|
|
152
|
+
KiB: 1024,
|
|
153
|
+
MB: 1e6,
|
|
154
|
+
MiB: 1048576,
|
|
155
|
+
GB: 1e9,
|
|
156
|
+
GiB: 1073741824,
|
|
157
|
+
TB: 1e12,
|
|
158
|
+
TiB: 1099511627776
|
|
159
|
+
};
|
|
160
|
+
function parseBytes(value, outputUnit = "B") {
|
|
161
|
+
const trimmed = trimHoconWs(value);
|
|
162
|
+
let i = 0;
|
|
163
|
+
while (i < trimmed.length) {
|
|
164
|
+
const ch = trimmed[i];
|
|
165
|
+
if (i === 0 && (ch === "-" || ch === "+")) {
|
|
166
|
+
i++;
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (ch !== "." && (ch < "0" || ch > "9")) break;
|
|
170
|
+
i++;
|
|
171
|
+
}
|
|
172
|
+
if (i === 0) return NaN;
|
|
173
|
+
const num = Number(trimmed.slice(0, i));
|
|
174
|
+
if (Number.isNaN(num)) return NaN;
|
|
175
|
+
const unit = trimHoconWs(trimmed.slice(i));
|
|
176
|
+
const divisor = OUTPUT_BYTE_UNITS[outputUnit];
|
|
177
|
+
if (divisor === void 0) return NaN;
|
|
178
|
+
if (unit === "") {
|
|
179
|
+
const bytes2 = Math.trunc(num);
|
|
180
|
+
if (Math.abs(bytes2) > Number.MAX_SAFE_INTEGER) {
|
|
181
|
+
throw new RangeError("byte size overflows representable range (max 2^53-1 bytes)");
|
|
182
|
+
}
|
|
183
|
+
return bytes2 / divisor;
|
|
184
|
+
}
|
|
185
|
+
let mult = BYTE_UNITS[unit];
|
|
186
|
+
if (mult === void 0) {
|
|
187
|
+
mult = BYTE_UNITS[unit.toLowerCase()];
|
|
188
|
+
}
|
|
189
|
+
if (mult === void 0) return NaN;
|
|
190
|
+
const bytes = num * mult;
|
|
191
|
+
if (Math.abs(bytes) > Number.MAX_SAFE_INTEGER) {
|
|
192
|
+
throw new RangeError("byte size overflows representable range (max 2^53-1 bytes)");
|
|
193
|
+
}
|
|
194
|
+
const result = bytes / divisor;
|
|
195
|
+
return outputUnit === "B" ? Math.round(result) : result;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// src/errors.ts
|
|
199
|
+
var ResolveError = class extends Error {
|
|
200
|
+
constructor(message, path, line, col, file) {
|
|
201
|
+
super(message);
|
|
202
|
+
this.path = path;
|
|
203
|
+
this.line = line;
|
|
204
|
+
this.col = col;
|
|
205
|
+
this.file = file;
|
|
206
|
+
this.name = "ResolveError";
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
var ConfigError = class extends Error {
|
|
210
|
+
constructor(message, path) {
|
|
211
|
+
super(message);
|
|
212
|
+
this.path = path;
|
|
213
|
+
this.name = "ConfigError";
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
var NotResolvedError = class extends ConfigError {
|
|
217
|
+
constructor(path) {
|
|
218
|
+
super(`value at path "${path}" is not resolved (call resolve() first)`, path);
|
|
219
|
+
this.name = "NotResolvedError";
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
// src/internal/resolver/fold-self-ref.ts
|
|
224
|
+
function substFullKey(s) {
|
|
225
|
+
return stringSegmentsToKey(s.segments.map((seg) => seg.text));
|
|
226
|
+
}
|
|
227
|
+
function stringSegmentsToKey(segments) {
|
|
228
|
+
return segments.map((t) => {
|
|
229
|
+
if (t === "" || /[^a-zA-Z0-9\-_]/.test(t)) {
|
|
230
|
+
return `"${t.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
231
|
+
}
|
|
232
|
+
return t;
|
|
233
|
+
}).join(".");
|
|
234
|
+
}
|
|
235
|
+
function containsSelfRef(v, fullKey) {
|
|
236
|
+
if (isSubst(v)) return !v.knownAbsent && substFullKey(v) === fullKey;
|
|
237
|
+
if (isConcat(v)) return v.nodes.some((n) => containsSelfRef(n, fullKey));
|
|
238
|
+
if (isResObj(v)) {
|
|
239
|
+
for (const f of v.fields.values()) {
|
|
240
|
+
if (containsSelfRef(f, fullKey)) return true;
|
|
241
|
+
}
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
const hv = v;
|
|
245
|
+
if (hv.kind === "array") {
|
|
246
|
+
return hv.items.some((item) => containsSelfRef(item, fullKey));
|
|
247
|
+
}
|
|
248
|
+
if (hv.kind === "object") {
|
|
249
|
+
for (const f of hv.fields.values()) {
|
|
250
|
+
if (containsSelfRef(f, fullKey)) return true;
|
|
251
|
+
}
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
function foldSelfRef(v, fullKey, replacement) {
|
|
257
|
+
if (isSubst(v)) {
|
|
258
|
+
return substFullKey(v) === fullKey ? replacement : v;
|
|
259
|
+
}
|
|
260
|
+
if (isConcat(v)) {
|
|
261
|
+
return {
|
|
262
|
+
_kind: "concat-placeholder",
|
|
263
|
+
nodes: v.nodes.map((n) => foldSelfRef(n, fullKey, replacement)),
|
|
264
|
+
line: v.line,
|
|
265
|
+
col: v.col
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
if (isResObj(v)) {
|
|
269
|
+
const newFields = /* @__PURE__ */ new Map();
|
|
270
|
+
for (const [k, val] of v.fields) {
|
|
271
|
+
newFields.set(k, foldSelfRef(val, fullKey, replacement));
|
|
272
|
+
}
|
|
273
|
+
return { _kind: "res-obj", fields: newFields, priorValues: new Map(v.priorValues), resetKeys: new Set(v.resetKeys) };
|
|
274
|
+
}
|
|
275
|
+
const hv = v;
|
|
276
|
+
if (hv.kind === "array") {
|
|
277
|
+
return {
|
|
278
|
+
kind: "array",
|
|
279
|
+
items: hv.items.map((item) => foldSelfRef(item, fullKey, replacement))
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
if (hv.kind === "object") {
|
|
283
|
+
const newFields = /* @__PURE__ */ new Map();
|
|
284
|
+
for (const [k, val] of hv.fields) {
|
|
285
|
+
newFields.set(k, foldSelfRef(val, fullKey, replacement));
|
|
286
|
+
}
|
|
287
|
+
return { kind: "object", fields: newFields };
|
|
288
|
+
}
|
|
289
|
+
return v;
|
|
290
|
+
}
|
|
291
|
+
function foldOrSkipPrior(prior, fullKey, old) {
|
|
292
|
+
if (!containsSelfRef(prior, fullKey)) return cloneResolverValue(prior);
|
|
293
|
+
if (old === void 0) return foldOptionalSelfRefAbsent(prior, fullKey);
|
|
294
|
+
return foldSelfRef(prior, fullKey, old);
|
|
295
|
+
}
|
|
296
|
+
function foldOptionalSelfRefAbsent(v, fullKey) {
|
|
297
|
+
if (isSubst(v) && substFullKey(v) === fullKey) {
|
|
298
|
+
if (!v.optional) return void 0;
|
|
299
|
+
return {
|
|
300
|
+
_kind: "subst-placeholder",
|
|
301
|
+
segments: v.segments.map((seg) => ({ text: seg.text, line: seg.line, col: seg.col })),
|
|
302
|
+
optional: v.optional,
|
|
303
|
+
knownAbsent: true,
|
|
304
|
+
listSuffix: v.listSuffix,
|
|
305
|
+
line: v.line,
|
|
306
|
+
col: v.col,
|
|
307
|
+
prefixLen: v.prefixLen
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
if (isConcat(v)) {
|
|
311
|
+
const nodes = [];
|
|
312
|
+
for (const node of v.nodes) {
|
|
313
|
+
const folded = foldOptionalSelfRefAbsent(node, fullKey);
|
|
314
|
+
if (folded === void 0) return void 0;
|
|
315
|
+
nodes.push(folded);
|
|
316
|
+
}
|
|
317
|
+
return { _kind: "concat-placeholder", nodes, line: v.line, col: v.col };
|
|
318
|
+
}
|
|
319
|
+
if (isResObj(v)) {
|
|
320
|
+
const fields = /* @__PURE__ */ new Map();
|
|
321
|
+
for (const [key, value] of v.fields) {
|
|
322
|
+
const folded = foldOptionalSelfRefAbsent(value, fullKey);
|
|
323
|
+
if (folded === void 0) return void 0;
|
|
324
|
+
fields.set(key, folded);
|
|
325
|
+
}
|
|
326
|
+
return { _kind: "res-obj", fields, priorValues: new Map(v.priorValues), resetKeys: new Set(v.resetKeys) };
|
|
327
|
+
}
|
|
328
|
+
const hv = v;
|
|
329
|
+
if (hv.kind === "array") {
|
|
330
|
+
const items = [];
|
|
331
|
+
for (const item of hv.items) {
|
|
332
|
+
const folded = foldOptionalSelfRefAbsent(item, fullKey);
|
|
333
|
+
if (folded === void 0) return void 0;
|
|
334
|
+
items.push(folded);
|
|
335
|
+
}
|
|
336
|
+
return { kind: "array", items };
|
|
337
|
+
}
|
|
338
|
+
if (hv.kind === "object") {
|
|
339
|
+
const fields = /* @__PURE__ */ new Map();
|
|
340
|
+
for (const [key, value] of hv.fields) {
|
|
341
|
+
const folded = foldOptionalSelfRefAbsent(value, fullKey);
|
|
342
|
+
if (folded === void 0) return void 0;
|
|
343
|
+
fields.set(key, folded);
|
|
344
|
+
}
|
|
345
|
+
return { kind: "object", fields };
|
|
346
|
+
}
|
|
347
|
+
return cloneResolverValue(v);
|
|
348
|
+
}
|
|
349
|
+
function cloneResolverValue(v) {
|
|
350
|
+
if (isSubst(v)) {
|
|
351
|
+
return {
|
|
352
|
+
_kind: "subst-placeholder",
|
|
353
|
+
segments: v.segments.map((seg) => ({ text: seg.text, line: seg.line, col: seg.col })),
|
|
354
|
+
optional: v.optional,
|
|
355
|
+
knownAbsent: v.knownAbsent,
|
|
356
|
+
listSuffix: v.listSuffix,
|
|
357
|
+
line: v.line,
|
|
358
|
+
col: v.col,
|
|
359
|
+
prefixLen: v.prefixLen
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
if (isConcat(v)) {
|
|
363
|
+
return {
|
|
364
|
+
_kind: "concat-placeholder",
|
|
365
|
+
nodes: v.nodes.map((n) => cloneResolverValue(n)),
|
|
366
|
+
line: v.line,
|
|
367
|
+
col: v.col
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
if (isResObj(v)) {
|
|
371
|
+
const newFields = /* @__PURE__ */ new Map();
|
|
372
|
+
for (const [k, val] of v.fields) newFields.set(k, cloneResolverValue(val));
|
|
373
|
+
const newPriors = /* @__PURE__ */ new Map();
|
|
374
|
+
for (const [k, val] of v.priorValues) newPriors.set(k, cloneResolverValue(val));
|
|
375
|
+
return { _kind: "res-obj", fields: newFields, priorValues: newPriors, resetKeys: new Set(v.resetKeys) };
|
|
376
|
+
}
|
|
377
|
+
const hv = v;
|
|
378
|
+
if (hv.kind === "array") {
|
|
379
|
+
return {
|
|
380
|
+
kind: "array",
|
|
381
|
+
items: hv.items.map((item) => cloneResolverValue(item))
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
if (hv.kind === "object") {
|
|
385
|
+
const newFields = /* @__PURE__ */ new Map();
|
|
386
|
+
for (const [k, val] of hv.fields) {
|
|
387
|
+
newFields.set(k, cloneResolverValue(val));
|
|
388
|
+
}
|
|
389
|
+
return { kind: "object", fields: newFields };
|
|
390
|
+
}
|
|
391
|
+
return hv;
|
|
392
|
+
}
|
|
393
|
+
function containsSubstByPath(v, target) {
|
|
394
|
+
if (isSubst(v)) return !v.knownAbsent && segmentsTextEqual(v.segments, target);
|
|
395
|
+
if (isConcat(v)) return v.nodes.some((n) => containsSubstByPath(n, target));
|
|
396
|
+
if (isResObj(v)) {
|
|
397
|
+
for (const f of v.fields.values()) {
|
|
398
|
+
if (containsSubstByPath(f, target)) return true;
|
|
399
|
+
}
|
|
400
|
+
return false;
|
|
401
|
+
}
|
|
402
|
+
const hv = v;
|
|
403
|
+
if (hv.kind === "array") {
|
|
404
|
+
return hv.items.some((item) => containsSubstByPath(item, target));
|
|
405
|
+
}
|
|
406
|
+
if (hv.kind === "object") {
|
|
407
|
+
for (const f of hv.fields.values()) {
|
|
408
|
+
if (containsSubstByPath(f, target)) return true;
|
|
409
|
+
}
|
|
410
|
+
return false;
|
|
411
|
+
}
|
|
412
|
+
return false;
|
|
413
|
+
}
|
|
414
|
+
function segmentsTextEqual(a, b) {
|
|
415
|
+
if (a.length !== b.length) return false;
|
|
416
|
+
for (let i = 0; i < a.length; i++) {
|
|
417
|
+
const ai = a[i];
|
|
418
|
+
const bi = b[i];
|
|
419
|
+
if (ai === void 0 || bi === void 0 || ai.text !== bi.text) return false;
|
|
420
|
+
}
|
|
421
|
+
return true;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// src/internal/resolver/types.ts
|
|
425
|
+
var separatorValues = /* @__PURE__ */ new WeakSet();
|
|
426
|
+
function isSubst(v) {
|
|
427
|
+
return v._kind === "subst-placeholder";
|
|
428
|
+
}
|
|
429
|
+
function isConcat(v) {
|
|
430
|
+
return v._kind === "concat-placeholder";
|
|
431
|
+
}
|
|
432
|
+
function isResObj(v) {
|
|
433
|
+
return v._kind === "res-obj";
|
|
434
|
+
}
|
|
435
|
+
function makeResObj() {
|
|
436
|
+
return { _kind: "res-obj", fields: /* @__PURE__ */ new Map(), priorValues: /* @__PURE__ */ new Map(), resetKeys: /* @__PURE__ */ new Set() };
|
|
437
|
+
}
|
|
438
|
+
function mergeUnresolved(receiver, fallback) {
|
|
439
|
+
const result = makeResObj();
|
|
440
|
+
for (const [k, v] of fallback.fields) {
|
|
441
|
+
result.fields.set(k, v);
|
|
442
|
+
}
|
|
443
|
+
for (const [k, v] of fallback.priorValues) {
|
|
444
|
+
result.priorValues.set(k, v);
|
|
445
|
+
}
|
|
446
|
+
for (const [k, rv] of receiver.fields) {
|
|
447
|
+
const existing = result.fields.get(k);
|
|
448
|
+
if (existing !== void 0) {
|
|
449
|
+
const recPrior = receiver.priorValues.get(k);
|
|
450
|
+
if (recPrior !== void 0 && !isResObj(recPrior)) {
|
|
451
|
+
result.fields.set(k, rv);
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
454
|
+
if (isResObj(rv) && isResObj(existing)) {
|
|
455
|
+
result.fields.set(k, mergeUnresolved(rv, existing));
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
458
|
+
const foldedPrior = foldOrSkipPrior(existing, k, void 0);
|
|
459
|
+
if (foldedPrior !== void 0) result.priorValues.set(k, foldedPrior);
|
|
460
|
+
}
|
|
461
|
+
result.fields.set(k, rv);
|
|
462
|
+
}
|
|
463
|
+
for (const [k, v] of receiver.priorValues) {
|
|
464
|
+
if (receiver.fields.has(k)) {
|
|
465
|
+
result.priorValues.set(k, v);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
return result;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// src/internal/resolver/utils.ts
|
|
472
|
+
function segmentsToKey(segments) {
|
|
473
|
+
return segments.map((s) => {
|
|
474
|
+
const t = s.text;
|
|
475
|
+
if (t === "" || /[^a-zA-Z0-9\-_]/.test(t)) {
|
|
476
|
+
return `"${t.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
477
|
+
}
|
|
478
|
+
return t;
|
|
479
|
+
}).join(".");
|
|
480
|
+
}
|
|
481
|
+
function lookupPath(root, segments) {
|
|
482
|
+
const [head, ...tail] = segments;
|
|
483
|
+
if (head === void 0) return void 0;
|
|
484
|
+
const key = head.text;
|
|
485
|
+
const val = root.fields.get(key);
|
|
486
|
+
if (val === void 0) return void 0;
|
|
487
|
+
if (tail.length === 0) return val;
|
|
488
|
+
if (isResObj(val)) return lookupPath(val, tail);
|
|
489
|
+
return void 0;
|
|
490
|
+
}
|
|
491
|
+
function lookupResObj(root, segments) {
|
|
492
|
+
let cur = root;
|
|
493
|
+
for (const seg of segments) {
|
|
494
|
+
const val = cur.fields.get(seg.text);
|
|
495
|
+
if (val === void 0 || !isResObj(val)) return void 0;
|
|
496
|
+
cur = val;
|
|
497
|
+
}
|
|
498
|
+
return cur;
|
|
499
|
+
}
|
|
500
|
+
function deepMergeHoconValues(base, overlay) {
|
|
501
|
+
const merged = new Map(base.fields);
|
|
502
|
+
for (const [k, v] of overlay.fields) {
|
|
503
|
+
const existing = merged.get(k);
|
|
504
|
+
if (existing?.kind === "object" && v.kind === "object") {
|
|
505
|
+
merged.set(k, deepMergeHoconValues(existing, v));
|
|
506
|
+
} else {
|
|
507
|
+
merged.set(k, v);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
return { kind: "object", fields: merged };
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// src/internal/resolver/include-loader.ts
|
|
514
|
+
import { createRequire } from "module";
|
|
515
|
+
import * as nodePath from "path";
|
|
516
|
+
|
|
517
|
+
// src/internal/properties/properties.ts
|
|
518
|
+
function nestPairs(pairs) {
|
|
519
|
+
const root = /* @__PURE__ */ Object.create(null);
|
|
520
|
+
const sorted = [...pairs].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
521
|
+
for (const [key, value] of sorted) {
|
|
522
|
+
setNested(root, key.split("."), value);
|
|
523
|
+
}
|
|
524
|
+
return root;
|
|
525
|
+
}
|
|
526
|
+
var DANGEROUS_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
527
|
+
function setNested(obj, segments, value) {
|
|
528
|
+
let current = obj;
|
|
529
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
530
|
+
const seg = segments[i];
|
|
531
|
+
if (seg === void 0 || DANGEROUS_KEYS.has(seg)) return;
|
|
532
|
+
if (!(seg in current) || typeof current[seg] !== "object" || current[seg] === null) {
|
|
533
|
+
current[seg] = /* @__PURE__ */ Object.create(null);
|
|
534
|
+
}
|
|
535
|
+
current = current[seg];
|
|
536
|
+
}
|
|
537
|
+
const last = segments[segments.length - 1];
|
|
538
|
+
if (last === void 0 || DANGEROUS_KEYS.has(last)) return;
|
|
539
|
+
if (typeof current[last] === "object" && current[last] !== null) return;
|
|
540
|
+
current[last] = value;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// src/internal/resolver/include-loader.ts
|
|
544
|
+
var _require = createRequire(import.meta.url);
|
|
545
|
+
|
|
546
|
+
// src/value/numeric-array.ts
|
|
547
|
+
function numericObjectToArray(value) {
|
|
548
|
+
if (value.kind !== "object") return null;
|
|
549
|
+
if (value.fields.size === 0) return null;
|
|
550
|
+
const canonicalInt = /^(0|[1-9][0-9]*)$/;
|
|
551
|
+
const eligible = [];
|
|
552
|
+
for (const [key, val] of value.fields) {
|
|
553
|
+
if (!canonicalInt.test(key)) continue;
|
|
554
|
+
const n = Number(key);
|
|
555
|
+
if (!Number.isInteger(n) || n > 2147483647) continue;
|
|
556
|
+
eligible.push({ n, v: val });
|
|
557
|
+
}
|
|
558
|
+
if (eligible.length === 0) return null;
|
|
559
|
+
eligible.sort((a, b) => a.n - b.n);
|
|
560
|
+
return eligible.map((e) => e.v);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// src/internal/resolver/substitution-resolver.ts
|
|
564
|
+
var SubstitutionResolver = class {
|
|
565
|
+
constructor(root, opts) {
|
|
566
|
+
this.root = root;
|
|
567
|
+
this.opts = opts;
|
|
568
|
+
this.resolving = /* @__PURE__ */ new Set();
|
|
569
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
570
|
+
// Tracks ConcatPlaceholder nodes currently being iterated by resolveConcat.
|
|
571
|
+
// Used by isSelfRef detection: fires only when found === a concat currently
|
|
572
|
+
// mid-iteration (i.e. we are inside found's own resolution), preventing the
|
|
573
|
+
// "foofoo" double-count without misfiring when an external field looks up the
|
|
574
|
+
// same concat via a substitution (multi-reviewer convergence fix).
|
|
575
|
+
//
|
|
576
|
+
// Spec deviation: the S13a.13 spec ★1 decision #1 specified path-equality
|
|
577
|
+
// preservation for self-ref detection. Round-2 multi-agent-review surfaced a
|
|
578
|
+
// false-positive on external lookups (`a = ${?a}foo; b = ${a}`), so the
|
|
579
|
+
// criterion was tightened to node-membership-in-iterating-set — strictly
|
|
580
|
+
// narrower than path-equality. Spec amendment deferred to a follow-up
|
|
581
|
+
// xx.hocon PR (see Phase 6 #3f close-out notes).
|
|
582
|
+
this.resolvingConcats = /* @__PURE__ */ new WeakSet();
|
|
583
|
+
// Path stack tracking the full dotted path of the field currently being
|
|
584
|
+
// assigned in `resolveResObj`. Leaf keys are pushed on entry to each
|
|
585
|
+
// `(key, val)` iteration and popped on exit, so nested objects build up
|
|
586
|
+
// the full path (e.g. `["o", "history"]` while resolving `o.history`).
|
|
587
|
+
//
|
|
588
|
+
// Used to tighten self-ref detection at the L192 found-lookup site:
|
|
589
|
+
// a substitution `${X}` whose looked-up value contains a Subst pointing
|
|
590
|
+
// at `X` is only a true self-reference when the field currently being
|
|
591
|
+
// assigned is `X` (or a descendant of `X`). Without this guard, an
|
|
592
|
+
// external lookup like `b = ${o}` (where o has a self-referential
|
|
593
|
+
// field) would mis-fire and short-circuit to o's prior, returning the
|
|
594
|
+
// wrong value for b. The is_owner gate (rfp prefix-matches s.segments)
|
|
595
|
+
// narrows the check to the owner case only.
|
|
596
|
+
//
|
|
597
|
+
// Cross-impl with rs.hocon v1.5.1's resolving_field_path (same purpose,
|
|
598
|
+
// same prefix-match semantics).
|
|
599
|
+
this.resolvingFieldPath = [];
|
|
600
|
+
}
|
|
601
|
+
/** Returns a prefix for error messages, including origin info when set. */
|
|
602
|
+
originPrefix() {
|
|
603
|
+
return this.opts.originDescription ? `${this.opts.originDescription}: ` : "";
|
|
604
|
+
}
|
|
605
|
+
resolve() {
|
|
606
|
+
return this.resolveResObj(this.root);
|
|
607
|
+
}
|
|
608
|
+
resolveResObj(obj) {
|
|
609
|
+
const result = /* @__PURE__ */ new Map();
|
|
610
|
+
for (const [key, val] of obj.fields) {
|
|
611
|
+
this.resolvingFieldPath.push(key);
|
|
612
|
+
const fullCacheKey = stringSegmentsToKey(this.resolvingFieldPath);
|
|
613
|
+
this.cache.delete(fullCacheKey);
|
|
614
|
+
let resolved;
|
|
615
|
+
try {
|
|
616
|
+
resolved = this.resolveVal(val, obj);
|
|
617
|
+
} finally {
|
|
618
|
+
this.resolvingFieldPath.pop();
|
|
619
|
+
}
|
|
620
|
+
if (resolved !== void 0) {
|
|
621
|
+
if (resolved.kind === "object") {
|
|
622
|
+
const prior = obj.priorValues.get(key);
|
|
623
|
+
if (prior !== void 0) {
|
|
624
|
+
const priorResolved = this.resolveVal(prior, obj);
|
|
625
|
+
if (priorResolved !== void 0 && priorResolved.kind === "object") {
|
|
626
|
+
const finalValue = deepMergeHoconValues(
|
|
627
|
+
priorResolved,
|
|
628
|
+
resolved
|
|
629
|
+
);
|
|
630
|
+
result.set(key, finalValue);
|
|
631
|
+
this.cache.set(fullCacheKey, finalValue);
|
|
632
|
+
this.cacheDescendants(fullCacheKey, finalValue);
|
|
633
|
+
continue;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
result.set(key, resolved);
|
|
638
|
+
this.cache.set(fullCacheKey, resolved);
|
|
639
|
+
this.cacheDescendants(fullCacheKey, resolved);
|
|
640
|
+
} else {
|
|
641
|
+
const prior = obj.priorValues.get(key);
|
|
642
|
+
if (prior !== void 0) {
|
|
643
|
+
const priorResolved = this.resolveVal(prior, obj);
|
|
644
|
+
if (priorResolved !== void 0) {
|
|
645
|
+
result.set(key, priorResolved);
|
|
646
|
+
this.cache.set(fullCacheKey, priorResolved);
|
|
647
|
+
this.cacheDescendants(fullCacheKey, priorResolved);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
return { kind: "object", fields: result };
|
|
653
|
+
}
|
|
654
|
+
cacheDescendants(prefix, value) {
|
|
655
|
+
if (value.kind !== "object") return;
|
|
656
|
+
for (const [key, child] of value.fields) {
|
|
657
|
+
const childKey = `${prefix}.${stringSegmentsToKey([key])}`;
|
|
658
|
+
this.cache.set(childKey, child);
|
|
659
|
+
this.cacheDescendants(childKey, child);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
resolveVal(v, scope) {
|
|
663
|
+
if (isSubst(v)) return this.resolveSubst(v, scope);
|
|
664
|
+
if (isConcat(v)) {
|
|
665
|
+
this.resolvingConcats.add(v);
|
|
666
|
+
try {
|
|
667
|
+
const concatResult = this.resolveConcat(v.nodes, scope, v.line, v.col);
|
|
668
|
+
return concatResult;
|
|
669
|
+
} finally {
|
|
670
|
+
this.resolvingConcats.delete(v);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
if (isResObj(v)) return this.resolveResObj(v);
|
|
674
|
+
const hv = v;
|
|
675
|
+
if (hv.kind === "array") {
|
|
676
|
+
return {
|
|
677
|
+
kind: "array",
|
|
678
|
+
items: hv.items.map(
|
|
679
|
+
(item) => this.resolveVal(item, scope) ?? { kind: "scalar", raw: "null", valueType: "null" }
|
|
680
|
+
)
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
return hv;
|
|
684
|
+
}
|
|
685
|
+
resolveSubst(s, scope) {
|
|
686
|
+
if (s.knownAbsent) return void 0;
|
|
687
|
+
const key = s.listSuffix ? `${segmentsToKey(s.segments)}[]` : segmentsToKey(s.segments);
|
|
688
|
+
if (this.cache.has(key)) return this.cache.get(key);
|
|
689
|
+
if (this.resolving.has(key)) {
|
|
690
|
+
let prior;
|
|
691
|
+
if (s.segments.length > 1) {
|
|
692
|
+
const leafSeg = s.segments[s.segments.length - 1]?.text ?? "";
|
|
693
|
+
const parentScope = lookupResObj(
|
|
694
|
+
this.root,
|
|
695
|
+
s.segments.slice(0, s.segments.length - 1)
|
|
696
|
+
);
|
|
697
|
+
prior = parentScope?.priorValues.get(leafSeg);
|
|
698
|
+
} else {
|
|
699
|
+
const rootSeg = s.segments[0]?.text ?? "";
|
|
700
|
+
prior = scope.priorValues.get(rootSeg) ?? this.root.priorValues.get(rootSeg);
|
|
701
|
+
}
|
|
702
|
+
if (prior !== void 0) {
|
|
703
|
+
const saved = this.resolving;
|
|
704
|
+
this.resolving = new Set(saved);
|
|
705
|
+
try {
|
|
706
|
+
return this.resolveVal(prior, scope);
|
|
707
|
+
} finally {
|
|
708
|
+
this.resolving = saved;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
if (s.optional) return void 0;
|
|
712
|
+
throw new ResolveError(
|
|
713
|
+
this.originPrefix() + `circular substitution: ${key}`,
|
|
714
|
+
key,
|
|
715
|
+
s.line,
|
|
716
|
+
s.col
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
this.resolving.add(key);
|
|
720
|
+
try {
|
|
721
|
+
const found = lookupPath(this.root, s.segments);
|
|
722
|
+
if (found !== void 0) {
|
|
723
|
+
const rfp = this.resolvingFieldPath;
|
|
724
|
+
const isOwner = rfp.length >= s.segments.length && s.segments.every((seg, i) => rfp[i] === seg.text);
|
|
725
|
+
const isSelfRef = isConcat(found) && this.resolvingConcats.has(found) || isOwner && containsSubstByPath(found, s.segments);
|
|
726
|
+
if (isSelfRef) {
|
|
727
|
+
let prior;
|
|
728
|
+
if (s.segments.length > 1) {
|
|
729
|
+
const leafSeg = s.segments[s.segments.length - 1]?.text ?? "";
|
|
730
|
+
const parentScope = lookupResObj(
|
|
731
|
+
this.root,
|
|
732
|
+
s.segments.slice(0, s.segments.length - 1)
|
|
733
|
+
);
|
|
734
|
+
prior = parentScope?.priorValues.get(leafSeg);
|
|
735
|
+
} else {
|
|
736
|
+
const rootSeg = s.segments[0]?.text ?? "";
|
|
737
|
+
prior = scope.priorValues.get(rootSeg) ?? this.root.priorValues.get(rootSeg);
|
|
738
|
+
}
|
|
739
|
+
if (prior !== void 0) {
|
|
740
|
+
const result2 = this.resolveVal(prior, scope);
|
|
741
|
+
if (result2 !== void 0) this.cache.set(key, result2);
|
|
742
|
+
return result2;
|
|
743
|
+
}
|
|
744
|
+
if (s.optional) return void 0;
|
|
745
|
+
throw new ResolveError(
|
|
746
|
+
`${this.originPrefix()}could not resolve substitution: \${${key}}`,
|
|
747
|
+
key,
|
|
748
|
+
s.line,
|
|
749
|
+
s.col
|
|
750
|
+
);
|
|
751
|
+
}
|
|
752
|
+
let result = this.resolveVal(found, scope);
|
|
753
|
+
const effectiveLen = s.segments.length - s.prefixLen;
|
|
754
|
+
if (effectiveLen === 1 && result !== void 0 && result.kind === "object") {
|
|
755
|
+
const leafSeg = s.segments[s.segments.length - 1]?.text ?? "";
|
|
756
|
+
let prior;
|
|
757
|
+
if (s.prefixLen > 0) {
|
|
758
|
+
const parentScope = lookupResObj(
|
|
759
|
+
this.root,
|
|
760
|
+
s.segments.slice(0, s.segments.length - 1)
|
|
761
|
+
);
|
|
762
|
+
prior = parentScope?.priorValues.get(leafSeg);
|
|
763
|
+
} else {
|
|
764
|
+
prior = scope.priorValues.get(leafSeg) ?? this.root.priorValues.get(leafSeg);
|
|
765
|
+
}
|
|
766
|
+
if (prior !== void 0) {
|
|
767
|
+
const priorResolved = this.resolveVal(prior, scope);
|
|
768
|
+
if (priorResolved !== void 0 && priorResolved.kind === "object") {
|
|
769
|
+
result = deepMergeHoconValues(
|
|
770
|
+
priorResolved,
|
|
771
|
+
result
|
|
772
|
+
);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
if (result !== void 0) this.cache.set(key, result);
|
|
777
|
+
return result;
|
|
778
|
+
}
|
|
779
|
+
if (s.prefixLen > 0 && s.segments.length > s.prefixLen) {
|
|
780
|
+
const originalSegments = s.segments.slice(s.prefixLen);
|
|
781
|
+
const fallbackFound = lookupPath(this.root, originalSegments);
|
|
782
|
+
if (fallbackFound !== void 0) {
|
|
783
|
+
let result = this.resolveVal(fallbackFound, scope);
|
|
784
|
+
if (originalSegments.length === 1 && result !== void 0 && result.kind === "object") {
|
|
785
|
+
const rootSeg = originalSegments[0]?.text ?? "";
|
|
786
|
+
const prior = this.root.priorValues.get(rootSeg);
|
|
787
|
+
if (prior !== void 0) {
|
|
788
|
+
const priorResolved = this.resolveVal(prior, scope);
|
|
789
|
+
if (priorResolved !== void 0 && priorResolved.kind === "object") {
|
|
790
|
+
result = deepMergeHoconValues(
|
|
791
|
+
priorResolved,
|
|
792
|
+
result
|
|
793
|
+
);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
if (result !== void 0) this.cache.set(key, result);
|
|
798
|
+
return result;
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
if (s.listSuffix && this.opts.useSystemEnvironment !== false) {
|
|
802
|
+
const result = this.resolveEnvList(s);
|
|
803
|
+
if (result !== void 0) {
|
|
804
|
+
this.cache.set(key, result);
|
|
805
|
+
return result;
|
|
806
|
+
}
|
|
807
|
+
if (s.optional) return void 0;
|
|
808
|
+
const envBase = s.segments.map((seg) => seg.text).join(".");
|
|
809
|
+
throw new ResolveError(
|
|
810
|
+
`${this.originPrefix()}could not resolve substitution: \${${key}} (no environment variables ${envBase}_0, ${envBase}_1, \u2026 set)`,
|
|
811
|
+
key,
|
|
812
|
+
s.line,
|
|
813
|
+
s.col
|
|
814
|
+
);
|
|
815
|
+
} else if (s.listSuffix) {
|
|
816
|
+
if (s.optional) return void 0;
|
|
817
|
+
if (this.opts.allowUnresolved) return s;
|
|
818
|
+
throw new ResolveError(
|
|
819
|
+
`${this.originPrefix()}could not resolve substitution: \${${key}}`,
|
|
820
|
+
key,
|
|
821
|
+
s.line,
|
|
822
|
+
s.col
|
|
823
|
+
);
|
|
824
|
+
}
|
|
825
|
+
if (this.opts.useSystemEnvironment !== false) {
|
|
826
|
+
const envKey = s.segments.map((seg) => seg.text).join(".");
|
|
827
|
+
const envVal = this.opts.env[envKey] ?? (s.prefixLen > 0 ? this.opts.env[s.segments.slice(s.prefixLen).map((seg) => seg.text).join(".")] : void 0);
|
|
828
|
+
if (envVal !== void 0) {
|
|
829
|
+
const result = { kind: "scalar", raw: envVal, valueType: "string" };
|
|
830
|
+
this.cache.set(key, result);
|
|
831
|
+
return result;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
if (s.optional) return void 0;
|
|
835
|
+
if (this.opts.allowUnresolved) {
|
|
836
|
+
return s;
|
|
837
|
+
}
|
|
838
|
+
throw new ResolveError(
|
|
839
|
+
`${this.originPrefix()}could not resolve substitution: \${${key}}`,
|
|
840
|
+
key,
|
|
841
|
+
s.line,
|
|
842
|
+
s.col
|
|
843
|
+
);
|
|
844
|
+
} finally {
|
|
845
|
+
this.resolving.delete(key);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
/**
|
|
849
|
+
* S13c: scan environment for NAME_0, NAME_1, … and return an Array<Scalar(String)>.
|
|
850
|
+
*
|
|
851
|
+
* Candidate bases follow the same fully-qualified→bare order used by the scalar
|
|
852
|
+
* env fallback. The first base whose _0 key is present wins entirely — no
|
|
853
|
+
* cross-base merging (spec §Relativized fallback precedence rules).
|
|
854
|
+
*
|
|
855
|
+
* Returns the HoconValue Array if any candidate base has _0, or undefined if
|
|
856
|
+
* none do (caller applies optional/required handling).
|
|
857
|
+
*/
|
|
858
|
+
resolveEnvList(s) {
|
|
859
|
+
const bases = this.candidateBases(s);
|
|
860
|
+
for (const base of bases) {
|
|
861
|
+
const key0 = base + "_0";
|
|
862
|
+
if (!(key0 in this.opts.env)) continue;
|
|
863
|
+
const items = [];
|
|
864
|
+
for (let i = 0; ; i++) {
|
|
865
|
+
const k = base + "_" + i;
|
|
866
|
+
if (!(k in this.opts.env)) break;
|
|
867
|
+
items.push({ kind: "scalar", raw: this.opts.env[k], valueType: "string" });
|
|
868
|
+
}
|
|
869
|
+
return { kind: "array", items };
|
|
870
|
+
}
|
|
871
|
+
return void 0;
|
|
872
|
+
}
|
|
873
|
+
/**
|
|
874
|
+
* Return the candidate base names for env-var-list lookup, in precedence order.
|
|
875
|
+
* Mirrors the fully-qualified→bare fallback used by the scalar env path.
|
|
876
|
+
*/
|
|
877
|
+
candidateBases(s) {
|
|
878
|
+
const full = s.segments.map((seg) => seg.text).join(".");
|
|
879
|
+
if (s.prefixLen === 0) return [full];
|
|
880
|
+
const bare = s.segments.slice(s.prefixLen).map((seg) => seg.text).join(".");
|
|
881
|
+
return [full, bare];
|
|
882
|
+
}
|
|
883
|
+
resolveConcat(nodes, scope, line = 0, col = 0) {
|
|
884
|
+
const rawResolved = nodes.map((n) => this.resolveVal(n, scope));
|
|
885
|
+
if (this.opts.allowUnresolved) {
|
|
886
|
+
for (const v of rawResolved) {
|
|
887
|
+
if (v !== void 0 && isSubst(v)) {
|
|
888
|
+
return v;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
const resolved = rawResolved.filter((v) => v !== void 0);
|
|
893
|
+
if (resolved.length === 0) return void 0;
|
|
894
|
+
if (resolved.length === 1) return resolved[0];
|
|
895
|
+
const nonSep = resolved.filter((v) => !separatorValues.has(v));
|
|
896
|
+
if (nonSep.length === 0) {
|
|
897
|
+
const str = resolved.map((v) => v.kind === "scalar" ? v.raw : JSON.stringify(v)).join("");
|
|
898
|
+
return { kind: "scalar", raw: str, valueType: "string" };
|
|
899
|
+
}
|
|
900
|
+
const joinPair = (left, right) => {
|
|
901
|
+
if (left.kind === "object" && right.kind === "object") {
|
|
902
|
+
return deepMergeHoconValues(left, right);
|
|
903
|
+
}
|
|
904
|
+
if (left.kind === "array" && right.kind === "object") {
|
|
905
|
+
const converted = numericObjectToArray(right);
|
|
906
|
+
if (converted !== null) {
|
|
907
|
+
return { kind: "array", items: [...left.items, ...converted] };
|
|
908
|
+
}
|
|
909
|
+
throw new ResolveError(
|
|
910
|
+
"cannot concatenate array with object: value concatenation requires same-kind operands (S10.4)",
|
|
911
|
+
"",
|
|
912
|
+
line,
|
|
913
|
+
col
|
|
914
|
+
);
|
|
915
|
+
}
|
|
916
|
+
if (left.kind === "object" && right.kind === "array") {
|
|
917
|
+
const converted = numericObjectToArray(left);
|
|
918
|
+
if (converted !== null) {
|
|
919
|
+
return { kind: "array", items: [...converted, ...right.items] };
|
|
920
|
+
}
|
|
921
|
+
throw new ResolveError(
|
|
922
|
+
"cannot concatenate object with array: value concatenation requires same-kind operands (S10.4)",
|
|
923
|
+
"",
|
|
924
|
+
line,
|
|
925
|
+
col
|
|
926
|
+
);
|
|
927
|
+
}
|
|
928
|
+
if (left.kind === "array" && right.kind === "array") {
|
|
929
|
+
return { kind: "array", items: [...left.items, ...right.items] };
|
|
930
|
+
}
|
|
931
|
+
if (left.kind === "array" || right.kind === "array") {
|
|
932
|
+
throw new ResolveError(
|
|
933
|
+
`cannot concatenate ${left.kind} with ${right.kind}: arrays and objects may not appear in string value concatenation (S10.13)`,
|
|
934
|
+
"",
|
|
935
|
+
line,
|
|
936
|
+
col
|
|
937
|
+
);
|
|
938
|
+
}
|
|
939
|
+
if (left.kind === "object" || right.kind === "object") {
|
|
940
|
+
throw new ResolveError(
|
|
941
|
+
`cannot concatenate ${left.kind} with ${right.kind}: arrays and objects may not appear in string value concatenation (S10.13)`,
|
|
942
|
+
"",
|
|
943
|
+
line,
|
|
944
|
+
col
|
|
945
|
+
);
|
|
946
|
+
}
|
|
947
|
+
return { kind: "scalar", raw: left.raw + right.raw, valueType: "string" };
|
|
948
|
+
};
|
|
949
|
+
const [head, ...tail] = nonSep;
|
|
950
|
+
const folded = tail.reduce(joinPair, head);
|
|
951
|
+
if (folded.kind === "scalar") {
|
|
952
|
+
const str = resolved.map((v) => v.kind === "scalar" ? v.raw : JSON.stringify(v)).join("");
|
|
953
|
+
return { kind: "scalar", raw: str, valueType: "string" };
|
|
954
|
+
}
|
|
955
|
+
return folded;
|
|
956
|
+
}
|
|
957
|
+
};
|
|
958
|
+
|
|
959
|
+
// src/internal/resolver/resolver.ts
|
|
960
|
+
function resolveTree(tree, opts) {
|
|
961
|
+
return new SubstitutionResolver(tree, opts).resolve();
|
|
962
|
+
}
|
|
963
|
+
function containsPlaceholders(tree) {
|
|
964
|
+
for (const val of tree.fields.values()) {
|
|
965
|
+
if (valContainsPlaceholders(val)) return true;
|
|
966
|
+
}
|
|
967
|
+
return false;
|
|
968
|
+
}
|
|
969
|
+
function valContainsPlaceholders(v) {
|
|
970
|
+
const rv = v;
|
|
971
|
+
if (isSubst(rv) || isConcat(rv)) return true;
|
|
972
|
+
if (isResObj(rv)) return containsPlaceholders(rv);
|
|
973
|
+
const hv = v;
|
|
974
|
+
if (hv?.kind === "array") {
|
|
975
|
+
return hv.items.some((item) => valContainsPlaceholders(item));
|
|
976
|
+
}
|
|
977
|
+
return false;
|
|
978
|
+
}
|
|
979
|
+
function buildPartialHoconFromResObj(tree) {
|
|
980
|
+
const fields = /* @__PURE__ */ new Map();
|
|
981
|
+
for (const [k, v] of tree.fields) {
|
|
982
|
+
if (!isSubst(v) && !isConcat(v)) {
|
|
983
|
+
if (isResObj(v)) {
|
|
984
|
+
fields.set(k, buildPartialHoconFromResObj(v));
|
|
985
|
+
} else {
|
|
986
|
+
if (!valContainsPlaceholders(v)) {
|
|
987
|
+
fields.set(k, v);
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
return { kind: "object", fields };
|
|
993
|
+
}
|
|
994
|
+
function hoconValueToResObj2(value) {
|
|
995
|
+
const obj = makeResObj();
|
|
996
|
+
for (const [k, v] of value.fields) {
|
|
997
|
+
if (v.kind === "object") {
|
|
998
|
+
obj.fields.set(k, hoconValueToResObj2(v));
|
|
999
|
+
} else {
|
|
1000
|
+
obj.fields.set(k, v);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
return obj;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
// src/config.ts
|
|
1007
|
+
var Config = class _Config {
|
|
1008
|
+
constructor(root, opts) {
|
|
1009
|
+
this.root = root;
|
|
1010
|
+
this._resolved = opts?.resolved ?? true;
|
|
1011
|
+
this._parseBaseDir = opts?.parseBaseDir;
|
|
1012
|
+
this._originDescription = opts?.originDescription;
|
|
1013
|
+
this._resObjRoot = opts?.resObjRoot;
|
|
1014
|
+
this._resolveOpts = opts?.resolveOpts;
|
|
1015
|
+
}
|
|
1016
|
+
/** Returns true when the Config's value tree contains no unresolved
|
|
1017
|
+
* substitution placeholders. Whole-config granularity (E12 decision 11). */
|
|
1018
|
+
isResolved() {
|
|
1019
|
+
return this._resolved;
|
|
1020
|
+
}
|
|
1021
|
+
/** @internal Test-only: construct a resolved Config from a HoconValue. */
|
|
1022
|
+
static _fromResolvedValue(root, opts) {
|
|
1023
|
+
return new _Config(root, { resolved: true, originDescription: opts?.originDescription });
|
|
1024
|
+
}
|
|
1025
|
+
/** @internal Construct an unresolved Config carrying a ResObj tree.
|
|
1026
|
+
* Used by parseStringWithOptions (deferred path) in T6. */
|
|
1027
|
+
static _fromUnresolvedResObj(tree, opts) {
|
|
1028
|
+
const partialRoot = buildPartialHoconFromResObj(tree);
|
|
1029
|
+
return new _Config(partialRoot, {
|
|
1030
|
+
resolved: opts.resolved,
|
|
1031
|
+
parseBaseDir: opts.parseBaseDir,
|
|
1032
|
+
originDescription: opts.originDescription,
|
|
1033
|
+
resObjRoot: tree,
|
|
1034
|
+
resolveOpts: opts.resolveOpts
|
|
1035
|
+
});
|
|
1036
|
+
}
|
|
1037
|
+
get(path) {
|
|
1038
|
+
const v = this.lookupNode(path);
|
|
1039
|
+
if (v === void 0) return void 0;
|
|
1040
|
+
return hoconToJs(v);
|
|
1041
|
+
}
|
|
1042
|
+
/**
|
|
1043
|
+
* Returns the raw {@link HoconValue} node at `path` as a deeply-immutable
|
|
1044
|
+
* {@link ReadonlyHoconValue} view, or `undefined` if the path is absent. Unlike
|
|
1045
|
+
* `get` (which decodes to a plain JS value), this exposes the value tree for
|
|
1046
|
+
* structural introspection via the `value.ts` accessors (`asString`, `asObject`,
|
|
1047
|
+
* `isScalar`, …).
|
|
1048
|
+
*
|
|
1049
|
+
* `getValue('')` returns the root object node.
|
|
1050
|
+
*
|
|
1051
|
+
* Throws {@link NotResolvedError} when the node (or its subtree) is unresolved:
|
|
1052
|
+
* the `HoconValue` union has no placeholder variant, so an unresolved value
|
|
1053
|
+
* cannot be represented. This matches `getConfig` / `getList`.
|
|
1054
|
+
*/
|
|
1055
|
+
getValue(path) {
|
|
1056
|
+
const v = this.lookupNode(path);
|
|
1057
|
+
if (v === void 0) {
|
|
1058
|
+
if (!this._resolved && this._resObjRootSubtreeHasPlaceholders(path)) {
|
|
1059
|
+
throw new NotResolvedError(path);
|
|
1060
|
+
}
|
|
1061
|
+
return void 0;
|
|
1062
|
+
}
|
|
1063
|
+
if (v.kind !== "scalar" && !this._resolved && this._resObjRootSubtreeHasPlaceholders(path)) {
|
|
1064
|
+
throw new NotResolvedError(path);
|
|
1065
|
+
}
|
|
1066
|
+
return v;
|
|
1067
|
+
}
|
|
1068
|
+
getString(path) {
|
|
1069
|
+
const v = this.requireScalar(path);
|
|
1070
|
+
if (v.valueType === "null") {
|
|
1071
|
+
throw new ConfigError(`expected string at ${path}, got ${v.valueType}`, path);
|
|
1072
|
+
}
|
|
1073
|
+
return v.raw;
|
|
1074
|
+
}
|
|
1075
|
+
getNumber(path) {
|
|
1076
|
+
const v = this.requireScalar(path);
|
|
1077
|
+
const coerced = coerceNumber(v.raw);
|
|
1078
|
+
if (coerced !== void 0) return coerced;
|
|
1079
|
+
throw new ConfigError(`expected number at ${path}, got ${v.valueType}`, path);
|
|
1080
|
+
}
|
|
1081
|
+
getBoolean(path) {
|
|
1082
|
+
const v = this.requireScalar(path);
|
|
1083
|
+
const coerced = coerceBoolean(v.raw);
|
|
1084
|
+
if (coerced !== void 0) return coerced;
|
|
1085
|
+
throw new ConfigError(`expected boolean at ${path}, got ${v.valueType}`, path);
|
|
1086
|
+
}
|
|
1087
|
+
getDuration(path, unit) {
|
|
1088
|
+
const v = this.requireScalar(path);
|
|
1089
|
+
if (v.valueType !== "string" && v.valueType !== "number") {
|
|
1090
|
+
throw new ConfigError(`expected duration at ${path}, got ${v.valueType}`, path);
|
|
1091
|
+
}
|
|
1092
|
+
const result = parseDuration(v.raw, unit);
|
|
1093
|
+
if (Number.isNaN(result)) throw new ConfigError(`invalid duration at ${path}: ${JSON.stringify(v.raw)}`, path);
|
|
1094
|
+
return result;
|
|
1095
|
+
}
|
|
1096
|
+
getBytes(path, unit) {
|
|
1097
|
+
const v = this.requireScalar(path);
|
|
1098
|
+
if (v.valueType !== "string" && v.valueType !== "number") {
|
|
1099
|
+
throw new ConfigError(`expected byte size at ${path}, got ${v.valueType}`, path);
|
|
1100
|
+
}
|
|
1101
|
+
const result = parseBytes(v.raw, unit);
|
|
1102
|
+
if (Number.isNaN(result)) throw new ConfigError(`invalid byte size at ${path}: ${JSON.stringify(v.raw)}`, path);
|
|
1103
|
+
if (result < 0) throw new ConfigError(`byte size must be non-negative at ${path}: ${JSON.stringify(v.raw)}`, path);
|
|
1104
|
+
return result;
|
|
1105
|
+
}
|
|
1106
|
+
getConfig(path) {
|
|
1107
|
+
const v = this.lookupNode(path);
|
|
1108
|
+
if (v === void 0) {
|
|
1109
|
+
if (!this._resolved && this._resObjRootSubtreeHasPlaceholders(path)) {
|
|
1110
|
+
throw new NotResolvedError(path);
|
|
1111
|
+
}
|
|
1112
|
+
throw new ConfigError(`path not found: ${path}`, path);
|
|
1113
|
+
}
|
|
1114
|
+
if (v.kind !== "object") throw new ConfigError(`expected object at ${path}`, path);
|
|
1115
|
+
if (!this._resolved && this._resObjRootSubtreeHasPlaceholders(path)) {
|
|
1116
|
+
throw new NotResolvedError(path);
|
|
1117
|
+
}
|
|
1118
|
+
return new _Config(v, { resolved: this._resolved });
|
|
1119
|
+
}
|
|
1120
|
+
getList(path) {
|
|
1121
|
+
const v = this.lookupNode(path);
|
|
1122
|
+
if (v === void 0) {
|
|
1123
|
+
if (!this._resolved && this._resObjRootSubtreeHasPlaceholders(path)) {
|
|
1124
|
+
throw new NotResolvedError(path);
|
|
1125
|
+
}
|
|
1126
|
+
throw new ConfigError(`path not found: ${path}`, path);
|
|
1127
|
+
}
|
|
1128
|
+
if (!this._resolved && this._resObjRootSubtreeHasPlaceholders(path)) {
|
|
1129
|
+
throw new NotResolvedError(path);
|
|
1130
|
+
}
|
|
1131
|
+
if (v.kind === "object") {
|
|
1132
|
+
const converted = numericObjectToArray(v);
|
|
1133
|
+
if (converted !== null) return converted.map(hoconToJs);
|
|
1134
|
+
}
|
|
1135
|
+
if (v.kind !== "array") throw new ConfigError(`expected array at ${path}`, path);
|
|
1136
|
+
return v.items.map(hoconToJs);
|
|
1137
|
+
}
|
|
1138
|
+
has(path) {
|
|
1139
|
+
return this.lookupNode(path) !== void 0;
|
|
1140
|
+
}
|
|
1141
|
+
keys() {
|
|
1142
|
+
return [...this.root.fields.keys()];
|
|
1143
|
+
}
|
|
1144
|
+
withFallback(fallback) {
|
|
1145
|
+
if (fallback == null) return this;
|
|
1146
|
+
const selfTree = this._resObjRoot ?? hoconValueToResObj2(this.root);
|
|
1147
|
+
const fbTree = fallback._resObjRoot ?? hoconValueToResObj2(fallback.root);
|
|
1148
|
+
const merged = mergeUnresolved(selfTree, fbTree);
|
|
1149
|
+
const hasPlaceholders = containsPlaceholders(merged);
|
|
1150
|
+
const partialRoot = buildPartialHoconFromResObj(merged);
|
|
1151
|
+
return new _Config(partialRoot, {
|
|
1152
|
+
resolved: !hasPlaceholders,
|
|
1153
|
+
parseBaseDir: this._parseBaseDir,
|
|
1154
|
+
originDescription: this._originDescription,
|
|
1155
|
+
resObjRoot: merged,
|
|
1156
|
+
resolveOpts: this._resolveOpts ?? fallback._resolveOpts
|
|
1157
|
+
});
|
|
1158
|
+
}
|
|
1159
|
+
/**
|
|
1160
|
+
* Runs substitution resolution (phase 2) on the stored unresolved tree.
|
|
1161
|
+
* Idempotent: calling resolve() on an already-resolved Config returns an
|
|
1162
|
+
* equivalent resolved Config.
|
|
1163
|
+
*
|
|
1164
|
+
* opts.allowUnresolved (default false) — when true, leaves unresolved
|
|
1165
|
+
* non-optional placeholders in place rather than throwing.
|
|
1166
|
+
* opts.useSystemEnvironment (default true) — when false, env var lookups
|
|
1167
|
+
* are suppressed (hermetic resolution).
|
|
1168
|
+
*
|
|
1169
|
+
* E12 decision 3.
|
|
1170
|
+
*/
|
|
1171
|
+
resolve(opts = {}) {
|
|
1172
|
+
if (this._resolved) {
|
|
1173
|
+
return new _Config(this.root, {
|
|
1174
|
+
resolved: true,
|
|
1175
|
+
parseBaseDir: this._parseBaseDir,
|
|
1176
|
+
originDescription: this._originDescription
|
|
1177
|
+
});
|
|
1178
|
+
}
|
|
1179
|
+
const tree = this._resObjRoot;
|
|
1180
|
+
if (!tree) {
|
|
1181
|
+
return new _Config(this.root, { resolved: true });
|
|
1182
|
+
}
|
|
1183
|
+
const resolveOpts = {
|
|
1184
|
+
...this._resolveOpts ?? { env: {}, baseDir: void 0, readFileSync: () => {
|
|
1185
|
+
throw new Error("no files");
|
|
1186
|
+
} },
|
|
1187
|
+
allowUnresolved: opts.allowUnresolved ?? false,
|
|
1188
|
+
useSystemEnvironment: opts.useSystemEnvironment ?? true,
|
|
1189
|
+
originDescription: this._originDescription
|
|
1190
|
+
};
|
|
1191
|
+
const resolved = resolveTree(tree, resolveOpts);
|
|
1192
|
+
if (resolved.kind !== "object") throw new Error("resolve: expected object root");
|
|
1193
|
+
if (opts.allowUnresolved) {
|
|
1194
|
+
const { stripped, hadPlaceholders } = stripPlaceholderFields(resolved);
|
|
1195
|
+
return new _Config(stripped, {
|
|
1196
|
+
resolved: !hadPlaceholders,
|
|
1197
|
+
parseBaseDir: this._parseBaseDir,
|
|
1198
|
+
originDescription: this._originDescription,
|
|
1199
|
+
resObjRoot: hadPlaceholders ? tree : void 0,
|
|
1200
|
+
resolveOpts: hadPlaceholders ? resolveOpts : void 0
|
|
1201
|
+
});
|
|
1202
|
+
}
|
|
1203
|
+
return new _Config(resolved, {
|
|
1204
|
+
resolved: true,
|
|
1205
|
+
parseBaseDir: this._parseBaseDir,
|
|
1206
|
+
originDescription: this._originDescription
|
|
1207
|
+
});
|
|
1208
|
+
}
|
|
1209
|
+
/**
|
|
1210
|
+
* Resolves receiver substitutions using source as lookup context.
|
|
1211
|
+
* Source's keys are NOT merged into the result — differs from
|
|
1212
|
+
* `this.withFallback(source).resolve()` which includes source keys.
|
|
1213
|
+
*
|
|
1214
|
+
* Precondition (E12 decision 10): source must be resolved. If
|
|
1215
|
+
* source.isResolved() is false, throws NotResolvedError.
|
|
1216
|
+
*
|
|
1217
|
+
* On an already-resolved receiver, resolveWith is a no-op (returns an
|
|
1218
|
+
* equivalent resolved Config, source keys not included).
|
|
1219
|
+
*
|
|
1220
|
+
* E12 decisions 9, 10.
|
|
1221
|
+
*/
|
|
1222
|
+
resolveWith(source, opts = {}) {
|
|
1223
|
+
if (!source.isResolved()) {
|
|
1224
|
+
throw new NotResolvedError("source");
|
|
1225
|
+
}
|
|
1226
|
+
if (this._resolved) {
|
|
1227
|
+
return new _Config(this.root, {
|
|
1228
|
+
resolved: true,
|
|
1229
|
+
parseBaseDir: this._parseBaseDir,
|
|
1230
|
+
originDescription: this._originDescription
|
|
1231
|
+
});
|
|
1232
|
+
}
|
|
1233
|
+
const receiverTree = this._resObjRoot ?? hoconValueToResObj2(this.root);
|
|
1234
|
+
const srcTree = hoconValueToResObj2(source.root);
|
|
1235
|
+
const merged = mergeUnresolved(receiverTree, srcTree);
|
|
1236
|
+
const resolveOpts = {
|
|
1237
|
+
...this._resolveOpts ?? { env: {}, baseDir: void 0, readFileSync: () => {
|
|
1238
|
+
throw new Error("no files");
|
|
1239
|
+
} },
|
|
1240
|
+
allowUnresolved: opts.allowUnresolved ?? false,
|
|
1241
|
+
useSystemEnvironment: opts.useSystemEnvironment ?? true,
|
|
1242
|
+
originDescription: this._originDescription
|
|
1243
|
+
};
|
|
1244
|
+
const resolved = resolveTree(merged, resolveOpts);
|
|
1245
|
+
if (resolved.kind !== "object") throw new Error("resolveWith: expected object root");
|
|
1246
|
+
const receiverShapeForFilter = resObjToKeyShape(receiverTree);
|
|
1247
|
+
const filtered = filterByReceiverShape(resolved, receiverShapeForFilter);
|
|
1248
|
+
if (opts.allowUnresolved) {
|
|
1249
|
+
const { stripped, hadPlaceholders } = stripPlaceholderFields(filtered);
|
|
1250
|
+
return new _Config(stripped, {
|
|
1251
|
+
resolved: !hadPlaceholders,
|
|
1252
|
+
parseBaseDir: this._parseBaseDir,
|
|
1253
|
+
originDescription: this._originDescription,
|
|
1254
|
+
resObjRoot: hadPlaceholders ? receiverTree : void 0,
|
|
1255
|
+
resolveOpts: hadPlaceholders ? resolveOpts : void 0
|
|
1256
|
+
});
|
|
1257
|
+
}
|
|
1258
|
+
return new _Config(filtered, {
|
|
1259
|
+
resolved: true,
|
|
1260
|
+
parseBaseDir: this._parseBaseDir,
|
|
1261
|
+
originDescription: this._originDescription
|
|
1262
|
+
});
|
|
1263
|
+
}
|
|
1264
|
+
toObject() {
|
|
1265
|
+
return hoconToJs(this.root);
|
|
1266
|
+
}
|
|
1267
|
+
/**
|
|
1268
|
+
* @internal Test-only: renders this resolved Config's value tree as canonical
|
|
1269
|
+
* JSON (sorted keys, no whitespace). Used by Layer-2 fixture tests to compare
|
|
1270
|
+
* against Lightbend ground truth. NOT part of the public API.
|
|
1271
|
+
*
|
|
1272
|
+
* Throws if the Config is unresolved (contains placeholders).
|
|
1273
|
+
*/
|
|
1274
|
+
_renderJSONForTest() {
|
|
1275
|
+
return renderHoconAsJSON(this.root);
|
|
1276
|
+
}
|
|
1277
|
+
lookupNode(path) {
|
|
1278
|
+
const segments = splitConfigPath(path);
|
|
1279
|
+
let current = this.root;
|
|
1280
|
+
for (const seg of segments) {
|
|
1281
|
+
if (current.kind !== "object") return void 0;
|
|
1282
|
+
const next = current.fields.get(seg);
|
|
1283
|
+
if (next === void 0) return void 0;
|
|
1284
|
+
current = next;
|
|
1285
|
+
}
|
|
1286
|
+
return current;
|
|
1287
|
+
}
|
|
1288
|
+
/**
|
|
1289
|
+
* _resObjRootSubtreeHasPlaceholders — walk _resObjRoot to the given path and
|
|
1290
|
+
* return true if the subtree there contains any unresolved placeholder.
|
|
1291
|
+
* Used by getConfig / getList (T2) to detect transitive unresolved placeholders
|
|
1292
|
+
* in object / array subtrees that would otherwise appear to be "resolved" in the
|
|
1293
|
+
* partial HoconValue root (because individual leaf fields are omitted rather than
|
|
1294
|
+
* the whole subtree when their value is a direct placeholder scalar, but object
|
|
1295
|
+
* sub-containers are retained in the partial root with the placeholder-keyed
|
|
1296
|
+
* fields omitted from them — so the object appears "present but empty-ish").
|
|
1297
|
+
*/
|
|
1298
|
+
_resObjRootSubtreeHasPlaceholders(path) {
|
|
1299
|
+
const tree = this._resObjRoot;
|
|
1300
|
+
if (!tree) return false;
|
|
1301
|
+
const segments = splitConfigPath(path);
|
|
1302
|
+
let cur = tree;
|
|
1303
|
+
for (const seg of segments) {
|
|
1304
|
+
const val = cur.fields.get(seg);
|
|
1305
|
+
if (val === void 0) return false;
|
|
1306
|
+
if (isResObj(val)) {
|
|
1307
|
+
cur = val;
|
|
1308
|
+
} else {
|
|
1309
|
+
return valContainsPlaceholders(val);
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
return containsPlaceholders(cur);
|
|
1313
|
+
}
|
|
1314
|
+
requireScalar(path) {
|
|
1315
|
+
const v = this.lookupNode(path);
|
|
1316
|
+
if (v === void 0) {
|
|
1317
|
+
if (!this._resolved && this._resObjRootSubtreeHasPlaceholders(path)) {
|
|
1318
|
+
throw new NotResolvedError(path);
|
|
1319
|
+
}
|
|
1320
|
+
throw new ConfigError(`path not found: ${path}`, path);
|
|
1321
|
+
}
|
|
1322
|
+
if (v.kind !== "scalar") throw new ConfigError(`expected scalar at ${path}, got ${v.kind}`, path);
|
|
1323
|
+
return v;
|
|
1324
|
+
}
|
|
1325
|
+
};
|
|
1326
|
+
function splitConfigPath(path) {
|
|
1327
|
+
const segments = [];
|
|
1328
|
+
let i = 0;
|
|
1329
|
+
while (i < path.length) {
|
|
1330
|
+
if (path[i] === '"') {
|
|
1331
|
+
i++;
|
|
1332
|
+
let segment = "";
|
|
1333
|
+
let closed = false;
|
|
1334
|
+
while (i < path.length) {
|
|
1335
|
+
const ch = path[i];
|
|
1336
|
+
if (ch === "\\" && i + 1 < path.length) {
|
|
1337
|
+
const next = path[i + 1];
|
|
1338
|
+
segment += next;
|
|
1339
|
+
i += 2;
|
|
1340
|
+
continue;
|
|
1341
|
+
}
|
|
1342
|
+
if (ch === '"') {
|
|
1343
|
+
closed = true;
|
|
1344
|
+
i++;
|
|
1345
|
+
break;
|
|
1346
|
+
}
|
|
1347
|
+
segment += ch;
|
|
1348
|
+
i++;
|
|
1349
|
+
}
|
|
1350
|
+
if (!closed) throw new ConfigError(`unterminated quoted path segment: ${path}`, path);
|
|
1351
|
+
segments.push(segment);
|
|
1352
|
+
if (i < path.length && path[i] === ".") i++;
|
|
1353
|
+
} else {
|
|
1354
|
+
const dot = path.indexOf(".", i);
|
|
1355
|
+
if (dot === -1) {
|
|
1356
|
+
segments.push(path.slice(i));
|
|
1357
|
+
break;
|
|
1358
|
+
}
|
|
1359
|
+
segments.push(path.slice(i, dot));
|
|
1360
|
+
i = dot + 1;
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
return segments;
|
|
1364
|
+
}
|
|
1365
|
+
function scalarToJs(raw, valueType) {
|
|
1366
|
+
switch (valueType) {
|
|
1367
|
+
case "null":
|
|
1368
|
+
return null;
|
|
1369
|
+
case "boolean":
|
|
1370
|
+
return raw === "true";
|
|
1371
|
+
case "number":
|
|
1372
|
+
return Number(raw);
|
|
1373
|
+
case "string":
|
|
1374
|
+
return raw;
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
function hoconToJs(v) {
|
|
1378
|
+
switch (v.kind) {
|
|
1379
|
+
case "scalar":
|
|
1380
|
+
return scalarToJs(v.raw, v.valueType);
|
|
1381
|
+
case "array":
|
|
1382
|
+
return v.items.map(hoconToJs);
|
|
1383
|
+
case "object": {
|
|
1384
|
+
const obj = /* @__PURE__ */ Object.create(null);
|
|
1385
|
+
for (const [k, val] of v.fields) obj[k] = hoconToJs(val);
|
|
1386
|
+
return Object.assign({}, obj);
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
function resObjToKeyShape(tree) {
|
|
1391
|
+
const fields = /* @__PURE__ */ new Map();
|
|
1392
|
+
for (const [k, v] of tree.fields) {
|
|
1393
|
+
if (isSubst(v) || isConcat(v)) {
|
|
1394
|
+
fields.set(k, { kind: "scalar", raw: "", valueType: "null" });
|
|
1395
|
+
} else if (isResObj(v)) {
|
|
1396
|
+
fields.set(k, resObjToKeyShape(v));
|
|
1397
|
+
} else {
|
|
1398
|
+
fields.set(k, v);
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
return { kind: "object", fields };
|
|
1402
|
+
}
|
|
1403
|
+
function stripPlaceholderFields(v) {
|
|
1404
|
+
const fields = /* @__PURE__ */ new Map();
|
|
1405
|
+
let hadPlaceholders = false;
|
|
1406
|
+
for (const [k, val] of v.fields) {
|
|
1407
|
+
if (val._kind === "subst-placeholder" || val._kind === "concat-placeholder") {
|
|
1408
|
+
hadPlaceholders = true;
|
|
1409
|
+
} else if (val.kind === "object") {
|
|
1410
|
+
const inner = stripPlaceholderFields(val);
|
|
1411
|
+
if (inner.hadPlaceholders) hadPlaceholders = true;
|
|
1412
|
+
fields.set(k, inner.stripped);
|
|
1413
|
+
} else {
|
|
1414
|
+
fields.set(k, val);
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
return { stripped: { kind: "object", fields }, hadPlaceholders };
|
|
1418
|
+
}
|
|
1419
|
+
function filterByReceiverShape(resolved, receiverShape) {
|
|
1420
|
+
const fields = /* @__PURE__ */ new Map();
|
|
1421
|
+
for (const [k, rv] of resolved.fields) {
|
|
1422
|
+
if (!receiverShape.fields.has(k)) continue;
|
|
1423
|
+
const receiverVal = receiverShape.fields.get(k);
|
|
1424
|
+
if (rv.kind === "object" && receiverVal.kind === "object") {
|
|
1425
|
+
fields.set(k, filterByReceiverShape(rv, receiverVal));
|
|
1426
|
+
} else {
|
|
1427
|
+
fields.set(k, rv);
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
return { kind: "object", fields };
|
|
1431
|
+
}
|
|
1432
|
+
function renderHoconAsJSON(v) {
|
|
1433
|
+
switch (v.kind) {
|
|
1434
|
+
case "scalar": {
|
|
1435
|
+
switch (v.valueType) {
|
|
1436
|
+
case "null":
|
|
1437
|
+
return "null";
|
|
1438
|
+
case "boolean":
|
|
1439
|
+
return v.raw;
|
|
1440
|
+
case "number": {
|
|
1441
|
+
const norm = normalizeLeadingZeroNumber(v.raw);
|
|
1442
|
+
return JSON_NUMBER_RE.test(norm) ? norm : JSON.stringify(v.raw);
|
|
1443
|
+
}
|
|
1444
|
+
case "string":
|
|
1445
|
+
return JSON.stringify(v.raw);
|
|
1446
|
+
}
|
|
1447
|
+
break;
|
|
1448
|
+
}
|
|
1449
|
+
case "array": {
|
|
1450
|
+
const items = v.items.map(renderHoconAsJSON);
|
|
1451
|
+
return `[${items.join(",")}]`;
|
|
1452
|
+
}
|
|
1453
|
+
case "object": {
|
|
1454
|
+
const keys = [...v.fields.keys()].sort();
|
|
1455
|
+
const pairs = keys.map((k) => {
|
|
1456
|
+
const val = v.fields.get(k);
|
|
1457
|
+
return `${JSON.stringify(k)}:${renderHoconAsJSON(val)}`;
|
|
1458
|
+
});
|
|
1459
|
+
return `{${pairs.join(",")}}`;
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
throw new Error(`renderHoconAsJSON: unsupported value kind (unresolved placeholder?)`);
|
|
1463
|
+
}
|
|
1464
|
+
var JSON_NUMBER_RE = /^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$/;
|
|
1465
|
+
function normalizeLeadingZeroNumber(raw) {
|
|
1466
|
+
return raw.replace(/^([+-]?)0+(\d)/, "$1$2");
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
// src/value-factory.ts
|
|
1470
|
+
function fromMap(values, originDescription) {
|
|
1471
|
+
const fields = coerceObject(values, "");
|
|
1472
|
+
const root = { kind: "object", fields };
|
|
1473
|
+
return Config._fromResolvedValue(root, { originDescription });
|
|
1474
|
+
}
|
|
1475
|
+
function coerceObject(obj, atPath) {
|
|
1476
|
+
const fields = /* @__PURE__ */ new Map();
|
|
1477
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
1478
|
+
const childPath = atPath ? `${atPath}.${k}` : k;
|
|
1479
|
+
fields.set(k, coerceValue(v, childPath));
|
|
1480
|
+
}
|
|
1481
|
+
return fields;
|
|
1482
|
+
}
|
|
1483
|
+
function coerceValue(v, atPath) {
|
|
1484
|
+
if (v === null) {
|
|
1485
|
+
return { kind: "scalar", raw: "null", valueType: "null" };
|
|
1486
|
+
}
|
|
1487
|
+
if (v === void 0) {
|
|
1488
|
+
throw new ConfigError(
|
|
1489
|
+
`fromMap: undefined is not a valid HOCON value at path "${atPath}"`,
|
|
1490
|
+
atPath
|
|
1491
|
+
);
|
|
1492
|
+
}
|
|
1493
|
+
switch (typeof v) {
|
|
1494
|
+
case "string":
|
|
1495
|
+
return { kind: "scalar", raw: v, valueType: "string" };
|
|
1496
|
+
case "number": {
|
|
1497
|
+
if (Number.isNaN(v) || !Number.isFinite(v)) {
|
|
1498
|
+
throw new ConfigError(
|
|
1499
|
+
`fromMap: ${v} is not representable in HOCON (NaN/Infinity not allowed) at path "${atPath}"`,
|
|
1500
|
+
atPath
|
|
1501
|
+
);
|
|
1502
|
+
}
|
|
1503
|
+
return { kind: "scalar", raw: String(v), valueType: "number" };
|
|
1504
|
+
}
|
|
1505
|
+
case "boolean":
|
|
1506
|
+
return { kind: "scalar", raw: v ? "true" : "false", valueType: "boolean" };
|
|
1507
|
+
case "bigint": {
|
|
1508
|
+
if (v > BigInt(Number.MAX_SAFE_INTEGER) || v < BigInt(Number.MIN_SAFE_INTEGER)) {
|
|
1509
|
+
throw new ConfigError(
|
|
1510
|
+
`fromMap: bigint ${v} exceeds Number.MAX_SAFE_INTEGER (not representable as HOCON number) at path "${atPath}"`,
|
|
1511
|
+
atPath
|
|
1512
|
+
);
|
|
1513
|
+
}
|
|
1514
|
+
return { kind: "scalar", raw: String(v), valueType: "number" };
|
|
1515
|
+
}
|
|
1516
|
+
case "object": {
|
|
1517
|
+
if (Array.isArray(v)) {
|
|
1518
|
+
const items = v.map((item, i) => coerceValue(item, `${atPath}[${i}]`));
|
|
1519
|
+
return { kind: "array", items };
|
|
1520
|
+
}
|
|
1521
|
+
const fields = coerceObject(v, atPath);
|
|
1522
|
+
return { kind: "object", fields };
|
|
1523
|
+
}
|
|
1524
|
+
default:
|
|
1525
|
+
throw new ConfigError(
|
|
1526
|
+
`fromMap: unsupported value type "${typeof v}" at path "${atPath}"`,
|
|
1527
|
+
atPath
|
|
1528
|
+
);
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
// src/adapters/env.ts
|
|
1533
|
+
var SEPARATOR = "__";
|
|
1534
|
+
function loadEnv(opts = {}) {
|
|
1535
|
+
const prefix = opts.prefix ?? "";
|
|
1536
|
+
if (prefix === "") {
|
|
1537
|
+
throw new ConfigError(
|
|
1538
|
+
"loadEnv: a prefix is required when mounting the environment (spec F1.1)",
|
|
1539
|
+
""
|
|
1540
|
+
);
|
|
1541
|
+
}
|
|
1542
|
+
const source = opts.env ?? globalThis.process?.env ?? {};
|
|
1543
|
+
const names = Object.keys(source).sort();
|
|
1544
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1545
|
+
const pairs = [];
|
|
1546
|
+
for (const name of names) {
|
|
1547
|
+
if (!name.startsWith(prefix)) continue;
|
|
1548
|
+
const value = source[name];
|
|
1549
|
+
if (value === void 0) continue;
|
|
1550
|
+
const path = toPath(name.slice(prefix.length));
|
|
1551
|
+
const prev = seen.get(path);
|
|
1552
|
+
if (prev !== void 0) {
|
|
1553
|
+
throw new ConfigError(`loadEnv: ${prev} and ${name} both map to "${path}"`, path);
|
|
1554
|
+
}
|
|
1555
|
+
seen.set(path, name);
|
|
1556
|
+
pairs.push([path, value]);
|
|
1557
|
+
}
|
|
1558
|
+
return fromMap(nestPairs(pairs), opts.originDescription ?? "environment variables");
|
|
1559
|
+
}
|
|
1560
|
+
function parseDotEnv(input, opts = {}) {
|
|
1561
|
+
const origin = opts.originDescription ?? ".env";
|
|
1562
|
+
const prefix = opts.prefix ?? "";
|
|
1563
|
+
const pairs = [];
|
|
1564
|
+
const lines = input.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
|
|
1565
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1566
|
+
const raw = (lines[i] ?? "").trim();
|
|
1567
|
+
if (raw === "" || raw.startsWith("#")) continue;
|
|
1568
|
+
const line = raw.startsWith("export ") ? raw.slice("export ".length) : raw;
|
|
1569
|
+
const eq = line.indexOf("=");
|
|
1570
|
+
if (eq === -1) throw new ConfigError(`${origin}:${i + 1}: expected NAME=value`, "");
|
|
1571
|
+
const name = line.slice(0, eq).trim();
|
|
1572
|
+
if (name === "") throw new ConfigError(`${origin}:${i + 1}: empty variable name`, "");
|
|
1573
|
+
const value = dotEnvValue(line.slice(eq + 1).replace(/^[ \t]+/, ""), origin, i + 1, name);
|
|
1574
|
+
if (!name.startsWith(prefix)) continue;
|
|
1575
|
+
pairs.push([toPath(name.slice(prefix.length)), value]);
|
|
1576
|
+
}
|
|
1577
|
+
return fromMap(nestPairs(pairs), origin);
|
|
1578
|
+
}
|
|
1579
|
+
function toPath(name) {
|
|
1580
|
+
const segs = name.split(SEPARATOR).map((s) => s.toLowerCase());
|
|
1581
|
+
if (segs.some((s) => s === "")) {
|
|
1582
|
+
throw new ConfigError(`env: "${name}" produces an empty path segment`, name);
|
|
1583
|
+
}
|
|
1584
|
+
return segs.join(".");
|
|
1585
|
+
}
|
|
1586
|
+
function dotEnvValue(v, origin, line, name) {
|
|
1587
|
+
const fail = (msg) => {
|
|
1588
|
+
throw new ConfigError(`${origin}:${line}: ${name}: ${msg}`, name);
|
|
1589
|
+
};
|
|
1590
|
+
if (v.startsWith("'")) {
|
|
1591
|
+
const end = v.indexOf("'", 1);
|
|
1592
|
+
if (end === -1) fail("unterminated ' quote (multi-line values are not supported)");
|
|
1593
|
+
if (v.slice(end + 1).trim() !== "") fail(`unexpected text after the closing quote`);
|
|
1594
|
+
return v.slice(1, end);
|
|
1595
|
+
}
|
|
1596
|
+
if (v.startsWith('"')) {
|
|
1597
|
+
let out = "";
|
|
1598
|
+
for (let i = 1; i < v.length; i++) {
|
|
1599
|
+
const c = v[i];
|
|
1600
|
+
if (c === '"') {
|
|
1601
|
+
if (v.slice(i + 1).trim() !== "") fail("unexpected text after the closing quote");
|
|
1602
|
+
return out;
|
|
1603
|
+
}
|
|
1604
|
+
if (c === "\\") {
|
|
1605
|
+
i++;
|
|
1606
|
+
const e = v[i];
|
|
1607
|
+
if (e === void 0) fail("dangling \\ at end of line");
|
|
1608
|
+
else if (e === "n") out += "\n";
|
|
1609
|
+
else if (e === "r") out += "\r";
|
|
1610
|
+
else if (e === "t") out += " ";
|
|
1611
|
+
else if (e === "\\") out += "\\";
|
|
1612
|
+
else if (e === '"') out += '"';
|
|
1613
|
+
else fail(`unknown escape \\${e} (supported: \\n \\r \\t \\\\ \\")`);
|
|
1614
|
+
continue;
|
|
1615
|
+
}
|
|
1616
|
+
out += c;
|
|
1617
|
+
}
|
|
1618
|
+
return fail('unterminated " quote (multi-line values are not supported)');
|
|
1619
|
+
}
|
|
1620
|
+
const trimmed = v.replace(/[ \t]+$/, "");
|
|
1621
|
+
if (/[ \t]#/.test(trimmed)) {
|
|
1622
|
+
fail(`ambiguous value "${trimmed}": trailing comments are not supported, so quote the value if the # belongs to it`);
|
|
1623
|
+
}
|
|
1624
|
+
return trimmed;
|
|
1625
|
+
}
|
|
1626
|
+
export {
|
|
1627
|
+
loadEnv,
|
|
1628
|
+
parseDotEnv
|
|
1629
|
+
};
|