@o3co/ts.hocon 1.2.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,5 +1,26 @@
1
1
  // src/coerce.ts
2
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
+ }
3
24
  var TRUTHY = /* @__PURE__ */ new Set(["true", "yes", "on"]);
4
25
  var FALSY = /* @__PURE__ */ new Set(["false", "no", "off"]);
5
26
  function coerceBoolean(value) {
@@ -45,22 +66,29 @@ var OUTPUT_DURATION_UNITS = {
45
66
  d: 864e5
46
67
  };
47
68
  function parseDuration(value, outputUnit = "ms") {
48
- const trimmed = value.trim();
69
+ const trimmed = trimHoconWs(value);
49
70
  let i = 0;
50
71
  while (i < trimmed.length) {
51
72
  const ch = trimmed[i];
52
- if (ch !== "-" && ch !== "." && (ch < "0" || ch > "9")) break;
73
+ if (i === 0 && (ch === "-" || ch === "+")) {
74
+ i++;
75
+ continue;
76
+ }
77
+ if (ch !== "." && (ch < "0" || ch > "9")) break;
53
78
  i++;
54
79
  }
55
80
  if (i === 0) return NaN;
56
81
  const num = Number(trimmed.slice(0, i));
57
82
  if (Number.isNaN(num)) return NaN;
58
- const unit = trimmed.slice(i).trim().toLowerCase();
83
+ const unit = trimHoconWs(trimmed.slice(i)).toLowerCase();
84
+ const divisor = OUTPUT_DURATION_UNITS[outputUnit];
85
+ if (divisor === void 0) return NaN;
86
+ if (unit === "") {
87
+ return num / divisor;
88
+ }
59
89
  const mult = DURATION_UNITS[unit];
60
90
  if (mult === void 0) return NaN;
61
91
  const ms = num * mult;
62
- const divisor = OUTPUT_DURATION_UNITS[outputUnit];
63
- if (divisor === void 0) return NaN;
64
92
  return ms / divisor;
65
93
  }
66
94
  var BYTE_UNITS = {
@@ -91,6 +119,22 @@ var BYTE_UNITS = {
91
119
  TiB: 1099511627776,
92
120
  tebibyte: 1099511627776,
93
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,
94
138
  // lowercase short-form aliases
95
139
  b: 1,
96
140
  kb: 1e3,
@@ -114,25 +158,39 @@ var OUTPUT_BYTE_UNITS = {
114
158
  TiB: 1099511627776
115
159
  };
116
160
  function parseBytes(value, outputUnit = "B") {
117
- const trimmed = value.trim();
161
+ const trimmed = trimHoconWs(value);
118
162
  let i = 0;
119
163
  while (i < trimmed.length) {
120
164
  const ch = trimmed[i];
121
- if (ch !== "-" && ch !== "." && (ch < "0" || ch > "9")) break;
165
+ if (i === 0 && (ch === "-" || ch === "+")) {
166
+ i++;
167
+ continue;
168
+ }
169
+ if (ch !== "." && (ch < "0" || ch > "9")) break;
122
170
  i++;
123
171
  }
124
172
  if (i === 0) return NaN;
125
173
  const num = Number(trimmed.slice(0, i));
126
174
  if (Number.isNaN(num)) return NaN;
127
- const unit = trimmed.slice(i).trim();
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
+ }
128
185
  let mult = BYTE_UNITS[unit];
129
186
  if (mult === void 0) {
130
187
  mult = BYTE_UNITS[unit.toLowerCase()];
131
188
  }
132
189
  if (mult === void 0) return NaN;
133
190
  const bytes = num * mult;
134
- const divisor = OUTPUT_BYTE_UNITS[outputUnit];
135
- if (divisor === void 0) return NaN;
191
+ if (Math.abs(bytes) > Number.MAX_SAFE_INTEGER) {
192
+ throw new RangeError("byte size overflows representable range (max 2^53-1 bytes)");
193
+ }
136
194
  const result = bytes / divisor;
137
195
  return outputUnit === "B" ? Math.round(result) : result;
138
196
  }
@@ -157,6 +215,14 @@ var ResolveError = class extends Error {
157
215
  this.name = "ResolveError";
158
216
  }
159
217
  };
218
+ var PackageLookupError = class extends ResolveError {
219
+ constructor(message, identifier, packageFile, line, col) {
220
+ super(message, `${identifier}/${packageFile}`, line, col);
221
+ this.identifier = identifier;
222
+ this.packageFile = packageFile;
223
+ this.name = "PackageLookupError";
224
+ }
225
+ };
160
226
  var ConfigError = class extends Error {
161
227
  constructor(message, path2) {
162
228
  super(message);
@@ -164,190 +230,155 @@ var ConfigError = class extends Error {
164
230
  this.name = "ConfigError";
165
231
  }
166
232
  };
167
-
168
- // src/value/numeric-array.ts
169
- function numericObjectToArray(value) {
170
- if (value.kind !== "object") return null;
171
- if (value.fields.size === 0) return null;
172
- const canonicalInt = /^(0|[1-9][0-9]*)$/;
173
- const eligible = [];
174
- for (const [key, val] of value.fields) {
175
- if (!canonicalInt.test(key)) continue;
176
- const n = Number(key);
177
- if (!Number.isInteger(n) || n > 2147483647) continue;
178
- eligible.push({ n, v: val });
233
+ var NotResolvedError = class extends ConfigError {
234
+ constructor(path2) {
235
+ super(`value at path "${path2}" is not resolved (call resolve() first)`, path2);
236
+ this.name = "NotResolvedError";
179
237
  }
180
- if (eligible.length === 0) return null;
181
- eligible.sort((a, b) => a.n - b.n);
182
- return eligible.map((e) => e.v);
183
- }
238
+ };
184
239
 
185
- // src/config.ts
186
- var Config = class _Config {
187
- constructor(root) {
188
- this.root = root;
189
- }
190
- get(path2) {
191
- const v = this.lookupNode(path2);
192
- if (v === void 0) return void 0;
193
- return hoconToJs(v);
194
- }
195
- getString(path2) {
196
- const v = this.requireScalar(path2);
197
- return v.raw;
198
- }
199
- getNumber(path2) {
200
- const v = this.requireScalar(path2);
201
- const coerced = coerceNumber(v.raw);
202
- if (coerced !== void 0) return coerced;
203
- throw new ConfigError(`expected number at ${path2}, got ${v.valueType}`, path2);
204
- }
205
- getBoolean(path2) {
206
- const v = this.requireScalar(path2);
207
- const coerced = coerceBoolean(v.raw);
208
- if (coerced !== void 0) return coerced;
209
- throw new ConfigError(`expected boolean at ${path2}, got ${v.valueType}`, path2);
240
+ // src/internal/resolver/types.ts
241
+ var separatorValues = /* @__PURE__ */ new WeakSet();
242
+ function isSubst(v) {
243
+ return v._kind === "subst-placeholder";
244
+ }
245
+ function isConcat(v) {
246
+ return v._kind === "concat-placeholder";
247
+ }
248
+ function isAppend(v) {
249
+ return v._kind === "append-placeholder";
250
+ }
251
+ function isResObj(v) {
252
+ return v._kind === "res-obj";
253
+ }
254
+ function makeResObj() {
255
+ return { _kind: "res-obj", fields: /* @__PURE__ */ new Map(), priorValues: /* @__PURE__ */ new Map() };
256
+ }
257
+ function mergeUnresolved(receiver, fallback) {
258
+ const result = makeResObj();
259
+ for (const [k, v] of fallback.fields) {
260
+ result.fields.set(k, v);
210
261
  }
211
- getDuration(path2, unit) {
212
- const v = this.requireScalar(path2);
213
- if (v.valueType !== "string" && v.valueType !== "number") {
214
- throw new ConfigError(`expected duration at ${path2}, got ${v.valueType}`, path2);
215
- }
216
- const result = parseDuration(v.raw, unit);
217
- if (Number.isNaN(result)) throw new ConfigError(`invalid duration at ${path2}: ${JSON.stringify(v.raw)}`, path2);
218
- return result;
262
+ for (const [k, v] of fallback.priorValues) {
263
+ result.priorValues.set(k, v);
219
264
  }
220
- getBytes(path2, unit) {
221
- const v = this.requireScalar(path2);
222
- if (v.valueType !== "string" && v.valueType !== "number") {
223
- throw new ConfigError(`expected byte size at ${path2}, got ${v.valueType}`, path2);
265
+ for (const [k, rv] of receiver.fields) {
266
+ const existing = result.fields.get(k);
267
+ if (existing !== void 0) {
268
+ const recPrior = receiver.priorValues.get(k);
269
+ if (recPrior !== void 0 && !isResObj(recPrior)) {
270
+ result.fields.set(k, rv);
271
+ continue;
272
+ }
273
+ if (isResObj(rv) && isResObj(existing)) {
274
+ result.fields.set(k, mergeUnresolved(rv, existing));
275
+ continue;
276
+ }
277
+ result.priorValues.set(k, existing);
224
278
  }
225
- const result = parseBytes(v.raw, unit);
226
- if (Number.isNaN(result)) throw new ConfigError(`invalid byte size at ${path2}: ${JSON.stringify(v.raw)}`, path2);
227
- return result;
228
- }
229
- getConfig(path2) {
230
- const v = this.lookupNode(path2);
231
- if (v === void 0) throw new ConfigError(`path not found: ${path2}`, path2);
232
- if (v.kind !== "object") throw new ConfigError(`expected object at ${path2}`, path2);
233
- return new _Config(v);
279
+ result.fields.set(k, rv);
234
280
  }
235
- getList(path2) {
236
- const v = this.lookupNode(path2);
237
- if (v === void 0) throw new ConfigError(`path not found: ${path2}`, path2);
238
- if (v.kind === "object") {
239
- const converted = numericObjectToArray(v);
240
- if (converted !== null) return converted.map(hoconToJs);
281
+ for (const [k, v] of receiver.priorValues) {
282
+ if (receiver.fields.has(k)) {
283
+ result.priorValues.set(k, v);
241
284
  }
242
- if (v.kind !== "array") throw new ConfigError(`expected array at ${path2}`, path2);
243
- return v.items.map(hoconToJs);
244
- }
245
- has(path2) {
246
- return this.lookupNode(path2) !== void 0;
247
- }
248
- keys() {
249
- return [...this.root.fields.keys()];
250
- }
251
- withFallback(fallback) {
252
- const merged = deepMergeHocon(this.root, fallback.root);
253
- return new _Config(merged);
254
- }
255
- toObject() {
256
- return hoconToJs(this.root);
257
285
  }
258
- lookupNode(path2) {
259
- const segments = splitConfigPath(path2);
260
- let current = this.root;
261
- for (const seg of segments) {
262
- if (current.kind !== "object") return void 0;
263
- const next = current.fields.get(seg);
264
- if (next === void 0) return void 0;
265
- current = next;
286
+ return result;
287
+ }
288
+
289
+ // src/internal/resolver/utils.ts
290
+ function segmentsToKey(segments) {
291
+ return segments.map((s) => {
292
+ const t = s.text;
293
+ if (t === "" || /[^a-zA-Z0-9\-_]/.test(t)) {
294
+ return `"${t.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
266
295
  }
267
- return current;
268
- }
269
- requireScalar(path2) {
270
- const v = this.lookupNode(path2);
271
- if (v === void 0) throw new ConfigError(`path not found: ${path2}`, path2);
272
- if (v.kind !== "scalar") throw new ConfigError(`expected scalar at ${path2}, got ${v.kind}`, path2);
273
- return v;
296
+ return t;
297
+ }).join(".");
298
+ }
299
+ function lookupPath(root, segments) {
300
+ const [head, ...tail] = segments;
301
+ if (head === void 0) return void 0;
302
+ const key = head.text;
303
+ const val = root.fields.get(key);
304
+ if (val === void 0) return void 0;
305
+ if (tail.length === 0) return val;
306
+ if (isResObj(val)) return lookupPath(val, tail);
307
+ return void 0;
308
+ }
309
+ function lookupResObj(root, segments) {
310
+ let cur = root;
311
+ for (const seg of segments) {
312
+ const val = cur.fields.get(seg.text);
313
+ if (val === void 0 || !isResObj(val)) return void 0;
314
+ cur = val;
274
315
  }
275
- };
276
- function splitConfigPath(path2) {
277
- const segments = [];
278
- let i = 0;
279
- while (i < path2.length) {
280
- if (path2[i] === '"') {
281
- i++;
282
- let segment = "";
283
- let closed = false;
284
- while (i < path2.length) {
285
- const ch = path2[i];
286
- if (ch === "\\" && i + 1 < path2.length) {
287
- const next = path2[i + 1];
288
- segment += next;
289
- i += 2;
290
- continue;
291
- }
292
- if (ch === '"') {
293
- closed = true;
294
- i++;
295
- break;
296
- }
297
- segment += ch;
298
- i++;
299
- }
300
- if (!closed) throw new ConfigError(`unterminated quoted path segment: ${path2}`, path2);
301
- segments.push(segment);
302
- if (i < path2.length && path2[i] === ".") i++;
316
+ return cur;
317
+ }
318
+ function deepMergeHoconValues(base, overlay) {
319
+ const merged = new Map(base.fields);
320
+ for (const [k, v] of overlay.fields) {
321
+ const existing = merged.get(k);
322
+ if (existing?.kind === "object" && v.kind === "object") {
323
+ merged.set(k, deepMergeHoconValues(existing, v));
303
324
  } else {
304
- const dot = path2.indexOf(".", i);
305
- if (dot === -1) {
306
- segments.push(path2.slice(i));
307
- break;
308
- }
309
- segments.push(path2.slice(i, dot));
310
- i = dot + 1;
325
+ merged.set(k, v);
311
326
  }
312
327
  }
313
- return segments;
328
+ return { kind: "object", fields: merged };
314
329
  }
315
- function scalarToJs(raw, valueType) {
316
- switch (valueType) {
317
- case "null":
318
- return null;
319
- case "boolean":
320
- return raw === "true";
321
- case "number":
322
- return Number(raw);
323
- case "string":
324
- return raw;
330
+ function deepMergeResObjInto(dst, src) {
331
+ for (const [k, srcVal] of src.fields) {
332
+ const dstVal = dst.fields.get(k);
333
+ if (dstVal !== void 0 && isResObj(dstVal) && isResObj(srcVal)) {
334
+ deepMergeResObjInto(dstVal, srcVal);
335
+ } else {
336
+ if (dstVal !== void 0) dst.priorValues.set(k, dstVal);
337
+ dst.fields.set(k, srcVal);
338
+ }
325
339
  }
326
- }
327
- function hoconToJs(v) {
328
- switch (v.kind) {
329
- case "scalar":
330
- return scalarToJs(v.raw, v.valueType);
331
- case "array":
332
- return v.items.map(hoconToJs);
333
- case "object": {
334
- const obj = {};
335
- for (const [k, val] of v.fields) obj[k] = hoconToJs(val);
336
- return obj;
340
+ for (const [k, srcPrior] of src.priorValues) {
341
+ if (!dst.priorValues.has(k)) {
342
+ dst.priorValues.set(k, srcPrior);
337
343
  }
338
344
  }
339
345
  }
340
- function deepMergeHocon(receiver, fallback) {
341
- const merged = new Map(fallback.fields);
342
- for (const [k, v] of receiver.fields) {
343
- const fb = merged.get(k);
344
- if (fb?.kind === "object" && v.kind === "object") {
345
- merged.set(k, deepMergeHocon(v, fb));
346
+ function hoconValueToResObj(hv) {
347
+ const obj = makeResObj();
348
+ if (hv.kind !== "object") return obj;
349
+ for (const [key, val] of hv.fields) {
350
+ if (val.kind === "object") {
351
+ obj.fields.set(key, hoconValueToResObj(val));
346
352
  } else {
347
- merged.set(k, v);
353
+ obj.fields.set(key, val);
348
354
  }
349
355
  }
350
- return { kind: "object", fields: merged };
356
+ return obj;
357
+ }
358
+ function isFileNotFoundError(e) {
359
+ if (!(e instanceof Error)) return false;
360
+ const code = e.code;
361
+ if (code === "ENOENT" || code === "MODULE_NOT_FOUND") return true;
362
+ const msg = e.message.toLowerCase();
363
+ return msg.includes("not found") || msg.includes("no such file") || msg.includes("enoent");
364
+ }
365
+
366
+ // src/internal/resolver/include-loader.ts
367
+ import { createRequire } from "module";
368
+ import * as nodePath from "path";
369
+
370
+ // src/internal/parser/empty-check.ts
371
+ function hasContentTokens(tokens) {
372
+ return tokens.some((t) => t.kind !== "eof" && t.kind !== "newline");
373
+ }
374
+ function assertNonEmptyDocument(tokens, sourceDescriptor) {
375
+ if (!hasContentTokens(tokens)) {
376
+ throw new ParseError(
377
+ `empty file is not a valid HOCON document (HOCON.md L130): ${sourceDescriptor}`,
378
+ 1,
379
+ 1
380
+ );
381
+ }
351
382
  }
352
383
 
353
384
  // src/internal/lexer/lexer.ts
@@ -477,14 +508,6 @@ var Lexer = class {
477
508
  continue;
478
509
  }
479
510
  if (isUnquotedStart(ch)) {
480
- if (ch === "-" && !isDecimalDigit(this.peek(1))) {
481
- const after = this.peek(1) === "" ? "EOF" : JSON.stringify(this.peek(1));
482
- throw new ParseError(
483
- `unquoted string cannot begin with '-' unless followed by a digit (got '-' then ${after}, HOCON.md L270-276)`,
484
- sl,
485
- sc
486
- );
487
- }
488
511
  let value = "";
489
512
  while (this.pos < this.input.length && isUnquotedContinue(this.peek(), () => this.peek(1))) {
490
513
  value += this.advance();
@@ -588,6 +611,10 @@ var Lexer = class {
588
611
  /**
589
612
  * Parse the body of a `${...}` substitution (called after `${` has been consumed).
590
613
  * Implements Appendix A state machine from the cross-language spec.
614
+ *
615
+ * S13c extension: if the segment-collection loop encounters `[`, it delegates to
616
+ * `parseListSuffix()` which consumes the literal `[]` and sets `listSuffix=true`.
617
+ * ASCII space/tab before `[` is buffered in `pendingWs` and discarded (E7).
591
618
  */
592
619
  parseSubstBody(startLine, startCol) {
593
620
  let optional = false;
@@ -602,6 +629,7 @@ var Lexer = class {
602
629
  let pendingWs = "";
603
630
  const segments = [];
604
631
  let lastDot = null;
632
+ let listSuffix = false;
605
633
  while (true) {
606
634
  if (this.pos >= this.input.length) {
607
635
  throw new ParseError("unterminated substitution", startLine, startCol);
@@ -611,6 +639,23 @@ var Lexer = class {
611
639
  this.advance();
612
640
  pendingWs = "";
613
641
  break;
642
+ } else if (ch === "[") {
643
+ if (!curStarted) {
644
+ throw new ParseError("empty segment before '[]' suffix in substitution path", startLine, this.col);
645
+ }
646
+ for (const w of pendingWs) {
647
+ if (w !== " " && w !== " ") {
648
+ throw new ParseError(
649
+ `only ASCII space or tab allowed between substitution path and '[]' suffix (got ${JSON.stringify(w)}, HOCON extra-spec E7)`,
650
+ startLine,
651
+ this.col
652
+ );
653
+ }
654
+ }
655
+ segments.push({ text: curText, line: curLine, col: curCol });
656
+ pendingWs = "";
657
+ listSuffix = this.parseListSuffix(startLine);
658
+ break;
614
659
  } else if (ch === '"') {
615
660
  const qLine = startLine;
616
661
  const qCol = this.col;
@@ -670,15 +715,47 @@ var Lexer = class {
670
715
  throw new ParseError(`unexpected character in substitution path: ${JSON.stringify(ch)}`, startLine, this.col);
671
716
  }
672
717
  }
673
- if (curStarted) {
674
- segments.push({ text: curText, line: curLine, col: curCol });
675
- } else if (segments.length === 0) {
676
- throw new ParseError("empty substitution path", startLine, startCol);
677
- } else {
678
- const [errLine, errCol] = lastDot ?? [startLine, startCol];
679
- throw new ParseError("empty segment in path", errLine, errCol);
718
+ if (!listSuffix) {
719
+ if (curStarted) {
720
+ segments.push({ text: curText, line: curLine, col: curCol });
721
+ } else if (segments.length === 0) {
722
+ throw new ParseError("empty substitution path", startLine, startCol);
723
+ } else {
724
+ const [errLine, errCol] = lastDot ?? [startLine, startCol];
725
+ throw new ParseError("empty segment in path", errLine, errCol);
726
+ }
727
+ }
728
+ return { segments, optional, listSuffix };
729
+ }
730
+ /**
731
+ * Consume the literal `[]` suffix inside a substitution body.
732
+ * Called after the `[` has been detected (but not yet consumed).
733
+ * Requires the next character to be `[`, then exactly `]`, then `}`.
734
+ * Whitespace inside `[]` is not permitted (per spec Decision §1).
735
+ * Returns true (always — only called when listSuffix path is taken).
736
+ */
737
+ parseListSuffix(startLine) {
738
+ this.advance();
739
+ const afterBracket = this.peek();
740
+ if (afterBracket !== "]") {
741
+ const desc = afterBracket === "" ? "EOF" : JSON.stringify(afterBracket);
742
+ throw new ParseError(
743
+ `expected ']' after '[' in substitution list suffix (got ${desc})`,
744
+ startLine,
745
+ this.col
746
+ );
747
+ }
748
+ this.advance();
749
+ if (this.peek() !== "}") {
750
+ const desc = this.peek() === "" ? "EOF" : JSON.stringify(this.peek());
751
+ throw new ParseError(
752
+ `expected '}' after '[]' in substitution list suffix (got ${desc})`,
753
+ startLine,
754
+ this.col
755
+ );
680
756
  }
681
- return { segments, optional };
757
+ this.advance();
758
+ return true;
682
759
  }
683
760
  };
684
761
  function tokenize(input) {
@@ -766,6 +843,14 @@ var Parser = class {
766
843
  const t = this.peek();
767
844
  if (t.kind === "eof" || t.kind === "rbrace") break;
768
845
  if (t.kind === "unquoted" && t.value === "include") {
846
+ const next = this.peek(1);
847
+ if (next.kind === "equals" || next.kind === "colon" || next.kind === "plus_equals" || next.kind === "lbrace") {
848
+ throw new ParseError(
849
+ `'include' is reserved at the start of a key path expression; use "include" (quoted) to use it as a key`,
850
+ t.line,
851
+ t.col
852
+ );
853
+ }
769
854
  this.advance();
770
855
  fields.push(this.parseInclude());
771
856
  this.skip("newline");
@@ -774,7 +859,14 @@ var Parser = class {
774
859
  continue;
775
860
  }
776
861
  const keyPos = this.currentPos();
777
- const key = this.parseKey();
862
+ const { segments: key, firstWasQuoted } = this.parseKey();
863
+ if (key[0] === "include" && !firstWasQuoted) {
864
+ throw new ParseError(
865
+ `'include' is reserved at the start of a key path expression; use "include".foo (quoted) or rename the key`,
866
+ keyPos.line,
867
+ keyPos.col
868
+ );
869
+ }
778
870
  this.skip("newline");
779
871
  let append = false;
780
872
  const sep = this.peek();
@@ -804,10 +896,12 @@ var Parser = class {
804
896
  parseKey() {
805
897
  const segments = [];
806
898
  let trailingDot = false;
899
+ let firstWasQuoted = false;
807
900
  while (true) {
808
901
  const t = this.peek();
809
902
  if (t.kind === "string") {
810
903
  this.advance();
904
+ if (segments.length === 0) firstWasQuoted = true;
811
905
  segments.push(t.value);
812
906
  trailingDot = false;
813
907
  } else if (t.kind === "unquoted") {
@@ -844,64 +938,143 @@ var Parser = class {
844
938
  }
845
939
  break;
846
940
  }
847
- return segments;
941
+ return { segments, firstWasQuoted };
848
942
  }
849
943
  parseInclude() {
850
944
  const p = this.currentPos();
851
945
  this.skip("newline");
852
946
  const t = this.peek();
853
- let path2;
854
- let required = false;
855
- let isFile = false;
856
947
  if (t.kind === "unquoted" && (t.value === "required(" || t.value === "required" || t.value.startsWith("required("))) {
857
948
  if (t.value === "required") {
858
- const next2 = this.peek(1);
859
- if (next2.kind !== "unquoted" || !next2.value.startsWith("(")) {
949
+ const next = this.peek(1);
950
+ if (next.kind !== "unquoted" || !next.value.startsWith("(")) {
860
951
  throw new ParseError("include required must be followed by (", t.line, t.col);
861
952
  }
862
953
  }
863
- required = true;
864
954
  this.advance();
865
955
  const innerPrefix = t.value.startsWith("required(") ? t.value.slice("required(".length) : "";
866
- if (innerPrefix.startsWith("url") || innerPrefix.startsWith("classpath")) {
956
+ if (innerPrefix === "url" || innerPrefix.startsWith("url(") || innerPrefix === "classpath" || innerPrefix.startsWith("classpath(")) {
867
957
  throw new ParseError("include url(...) and classpath(...) are not supported", t.line, t.col);
868
958
  }
869
- if (innerPrefix.startsWith("file")) {
870
- isFile = true;
959
+ const nextTok = this.peek();
960
+ if (innerPrefix === "package" || innerPrefix.startsWith("package(") || innerPrefix === "" && nextTok.kind === "unquoted" && (nextTok.value === "package(" || nextTok.value.startsWith("package("))) {
961
+ const consumePackageToken = innerPrefix === "";
962
+ const { qualifier, path: path3 } = this.parsePackageArgs(consumePackageToken);
963
+ return this.makeIncludeField(qualifier, path3, true, p);
871
964
  }
872
- const next = this.peek();
873
- if (next.kind === "unquoted" && (next.value === "url" || next.value.startsWith("url(") || next.value === "classpath" || next.value.startsWith("classpath("))) {
874
- throw new ParseError("include url(...) and classpath(...) are not supported", next.line, next.col);
875
- }
876
- if (next.kind === "unquoted" && (next.value === "file(" || next.value === "file" || next.value.startsWith("file("))) {
877
- isFile = true;
878
- }
879
- while (this.peek().kind !== "string" && this.peek().kind !== "eof") this.advance();
880
- if (this.peek().kind === "eof") throw new ParseError("expected include path", t.line, t.col);
881
- path2 = this.advance().value;
882
- while (this.peek().kind !== "newline" && this.peek().kind !== "rbrace" && this.peek().kind !== "eof" && this.peek().kind !== "comma") this.advance();
883
- } else if (t.kind === "string") {
884
- path2 = this.advance().value;
885
- } else if (t.kind === "unquoted" && (t.value === "file(" || t.value === "file")) {
886
- isFile = true;
965
+ if (innerPrefix === "file" || innerPrefix.startsWith("file(") || innerPrefix === "" && nextTok.kind === "unquoted" && (nextTok.value === "file(" || nextTok.value === "file")) {
966
+ if (innerPrefix === "") {
967
+ this.advance();
968
+ }
969
+ const path3 = this.parseQuotedPathSkipWrapper(t);
970
+ return this.makeIncludeField({ kind: "file" }, path3, true, p);
971
+ }
972
+ if (innerPrefix === "" && nextTok.kind === "unquoted" && (nextTok.value === "url" || nextTok.value.startsWith("url(") || nextTok.value === "classpath" || nextTok.value.startsWith("classpath("))) {
973
+ throw new ParseError("include url(...) and classpath(...) are not supported", nextTok.line, nextTok.col);
974
+ }
975
+ if (innerPrefix !== "") {
976
+ if (innerPrefix === ")") {
977
+ throw new ParseError("include required(...) must contain a path", t.line, t.col);
978
+ }
979
+ const name = innerPrefix.replace(/\(.*$/, "").replace(/\).*$/, "");
980
+ throw new ParseError(`unknown include qualifier inside required(): "${name}"`, t.line, t.col);
981
+ }
982
+ const path2 = this.parseQuotedPathSkipWrapper(t);
983
+ return this.makeIncludeField({ kind: "bare" }, path2, true, p);
984
+ }
985
+ if (t.kind === "string") {
986
+ const path2 = this.advance().value;
987
+ return this.makeIncludeField({ kind: "bare" }, path2, false, p);
988
+ }
989
+ if (t.kind === "unquoted" && (t.value === "file(" || t.value === "file")) {
887
990
  this.advance();
888
- while (this.peek().kind !== "string" && this.peek().kind !== "eof") this.advance();
889
- if (this.peek().kind === "eof") throw new ParseError("expected include path", t.line, t.col);
890
- path2 = this.advance().value;
891
- while (this.peek().kind !== "newline" && this.peek().kind !== "rbrace" && this.peek().kind !== "eof" && this.peek().kind !== "comma") this.advance();
892
- } else if (t.kind === "unquoted" && (t.value === "url" || t.value.startsWith("url("))) {
991
+ const path2 = this.parseQuotedPathSkipWrapper(t);
992
+ return this.makeIncludeField({ kind: "file" }, path2, false, p);
993
+ }
994
+ if (t.kind === "unquoted" && (t.value === "package(" || t.value.startsWith("package("))) {
995
+ const { qualifier, path: path2 } = this.parsePackageArgs();
996
+ return this.makeIncludeField(qualifier, path2, false, p);
997
+ }
998
+ if (t.kind === "unquoted" && (t.value === "url" || t.value.startsWith("url("))) {
893
999
  throw new ParseError("include url(...) is not supported", t.line, t.col);
894
- } else if (t.kind === "unquoted" && (t.value === "classpath" || t.value.startsWith("classpath("))) {
1000
+ }
1001
+ if (t.kind === "unquoted" && (t.value === "classpath" || t.value.startsWith("classpath("))) {
895
1002
  throw new ParseError("include classpath(...) is not supported", t.line, t.col);
896
- } else {
897
- throw new ParseError(`expected include path, got ${t.kind}`, t.line, t.col);
898
1003
  }
899
- return {
900
- key: [],
901
- value: { kind: "include", path: path2, required, ...isFile ? { isFile: true } : {}, pos: p },
902
- append: false,
903
- pos: p
904
- };
1004
+ throw new ParseError(`expected include path, got ${t.kind}`, t.line, t.col);
1005
+ }
1006
+ makeIncludeField(qualifier, path2, required, pos) {
1007
+ return { key: [], value: { kind: "include", qualifier, path: path2, required, pos }, append: false, pos };
1008
+ }
1009
+ /**
1010
+ * Skip past include-syntax wrapper tokens to find the quoted path string.
1011
+ * Strict: only allows `(`, `)`, and bare qualifier keywords (file / file( /
1012
+ * package / package( / url / url( / classpath / classpath() between the
1013
+ * directive and the quoted path. Anything else is a parse error rather than
1014
+ * silently swallowed (see ts#113).
1015
+ */
1016
+ parseQuotedPathSkipWrapper(errTok) {
1017
+ while (this.isIncludeWrapperToken(this.peek())) this.advance();
1018
+ if (this.peek().kind !== "string") {
1019
+ const tok = this.peek();
1020
+ if (tok.kind === "eof") throw new ParseError("expected include path", errTok.line, errTok.col);
1021
+ const desc = tok.kind === "unquoted" ? `unquoted "${tok.value}"` : tok.kind;
1022
+ throw new ParseError(`expected quoted include path, got ${desc}`, tok.line, tok.col);
1023
+ }
1024
+ const path2 = this.advance().value;
1025
+ while (this.peek().kind === "unquoted" && (this.peek().value === ")" || this.peek().value === "))")) {
1026
+ this.advance();
1027
+ }
1028
+ const after = this.peek();
1029
+ if (after.kind !== "newline" && after.kind !== "rbrace" && after.kind !== "eof" && after.kind !== "comma") {
1030
+ const desc = after.kind === "unquoted" ? `unquoted "${after.value}"` : after.kind;
1031
+ throw new ParseError(`unexpected token after include path: ${desc}`, after.line, after.col);
1032
+ }
1033
+ return path2;
1034
+ }
1035
+ isIncludeWrapperToken(tok) {
1036
+ return tok.kind === "unquoted" && tok.value === "(";
1037
+ }
1038
+ /**
1039
+ * Parse the body of `package("identifier", "file")`.
1040
+ *
1041
+ * @param consumePackageToken - When true (default), the current token is `package(` and
1042
+ * must be consumed first. When false, the lexer already bundled `package(` into the
1043
+ * preceding token (e.g. `required(package("`), so we are already positioned at the
1044
+ * first string argument.
1045
+ */
1046
+ parsePackageArgs(consumePackageToken = true) {
1047
+ const pkgTok = consumePackageToken ? this.advance() : this.peek();
1048
+ this.skip("newline");
1049
+ const idTok = this.peek();
1050
+ if (idTok.kind !== "string") {
1051
+ throw new ParseError("include package: expected quoted identifier as first argument", pkgTok.line, pkgTok.col);
1052
+ }
1053
+ const identifier = this.advance().value;
1054
+ this.skip("newline");
1055
+ if (this.peek().kind !== "comma") {
1056
+ const after = this.peek();
1057
+ throw new ParseError(
1058
+ after.kind === "string" ? "include package: missing comma between identifier and file arguments" : "include package: requires exactly two arguments (identifier, file) \u2014 one-arg form is not supported",
1059
+ pkgTok.line,
1060
+ pkgTok.col
1061
+ );
1062
+ }
1063
+ this.advance();
1064
+ this.skip("newline");
1065
+ const fileTok = this.peek();
1066
+ if (fileTok.kind !== "string") {
1067
+ throw new ParseError(
1068
+ "include package: requires exactly two arguments (identifier, file) \u2014 one-arg form is not supported",
1069
+ pkgTok.line,
1070
+ pkgTok.col
1071
+ );
1072
+ }
1073
+ const filePath = this.advance().value;
1074
+ while (this.peek().kind !== "newline" && this.peek().kind !== "rbrace" && this.peek().kind !== "eof" && this.peek().kind !== "comma") {
1075
+ this.advance();
1076
+ }
1077
+ return { qualifier: { kind: "package", identifier }, path: filePath };
905
1078
  }
906
1079
  parseValue() {
907
1080
  const p = this.currentPos();
@@ -923,7 +1096,7 @@ var Parser = class {
923
1096
  throw new ParseError(`internal: subst token missing payload`, t.line, t.col);
924
1097
  }
925
1098
  const payload = t.subst;
926
- node = { kind: "subst", segments: payload.segments, optional: payload.optional, pos: { line: t.line, col: t.col } };
1099
+ node = { kind: "subst", segments: payload.segments, optional: payload.optional, listSuffix: payload.listSuffix, pos: { line: t.line, col: t.col } };
927
1100
  } else if (t.kind === "string" || t.kind === "triple_string") {
928
1101
  this.advance();
929
1102
  node = { kind: "scalar", raw: t.value, valueType: "string", pos: { line: t.line, col: t.col } };
@@ -942,7 +1115,9 @@ var Parser = class {
942
1115
  parts.push(node);
943
1116
  }
944
1117
  if (parts.length === 0) throw new ParseError("expected value", this.peek().line, this.peek().col);
945
- if (parts.length === 1) return parts[0];
1118
+ if (parts.length === 1) {
1119
+ return parts[0];
1120
+ }
946
1121
  return { kind: "concat", nodes: parts, pos: p };
947
1122
  }
948
1123
  parseArray() {
@@ -974,107 +1149,10 @@ function parseTokens(tokens) {
974
1149
  return new Parser(tokens).parse();
975
1150
  }
976
1151
 
977
- // src/internal/resolver/types.ts
978
- var separatorValues = /* @__PURE__ */ new WeakSet();
979
- function isSubst(v) {
980
- return v._kind === "subst-placeholder";
981
- }
982
- function isConcat(v) {
983
- return v._kind === "concat-placeholder";
984
- }
985
- function isAppend(v) {
986
- return v._kind === "append-placeholder";
987
- }
988
- function isResObj(v) {
989
- return v._kind === "res-obj";
990
- }
991
- function makeResObj() {
992
- return { _kind: "res-obj", fields: /* @__PURE__ */ new Map(), priorValues: /* @__PURE__ */ new Map() };
993
- }
994
-
995
- // src/internal/resolver/utils.ts
996
- function segmentsToKey(segments) {
997
- return segments.map((s) => {
998
- const t = s.text;
999
- if (t === "" || /[^a-zA-Z0-9\-_]/.test(t)) {
1000
- return `"${t.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
1001
- }
1002
- return t;
1003
- }).join(".");
1004
- }
1005
- function lookupPath(root, segments) {
1006
- const [head, ...tail] = segments;
1007
- if (head === void 0) return void 0;
1008
- const key = head.text;
1009
- const val = root.fields.get(key);
1010
- if (val === void 0) return void 0;
1011
- if (tail.length === 0) return val;
1012
- if (isResObj(val)) return lookupPath(val, tail);
1013
- return void 0;
1014
- }
1015
- function lookupResObj(root, segments) {
1016
- let cur = root;
1017
- for (const seg of segments) {
1018
- const val = cur.fields.get(seg.text);
1019
- if (val === void 0 || !isResObj(val)) return void 0;
1020
- cur = val;
1021
- }
1022
- return cur;
1023
- }
1024
- function deepMergeHoconValues(base, overlay) {
1025
- const merged = new Map(base.fields);
1026
- for (const [k, v] of overlay.fields) {
1027
- const existing = merged.get(k);
1028
- if (existing?.kind === "object" && v.kind === "object") {
1029
- merged.set(k, deepMergeHoconValues(existing, v));
1030
- } else {
1031
- merged.set(k, v);
1032
- }
1033
- }
1034
- return { kind: "object", fields: merged };
1035
- }
1036
- function deepMergeResObjInto(dst, src) {
1037
- for (const [k, srcVal] of src.fields) {
1038
- const dstVal = dst.fields.get(k);
1039
- if (dstVal !== void 0 && isResObj(dstVal) && isResObj(srcVal)) {
1040
- deepMergeResObjInto(dstVal, srcVal);
1041
- } else {
1042
- if (dstVal !== void 0) dst.priorValues.set(k, dstVal);
1043
- dst.fields.set(k, srcVal);
1044
- }
1045
- }
1046
- for (const [k, srcPrior] of src.priorValues) {
1047
- if (!dst.priorValues.has(k)) {
1048
- dst.priorValues.set(k, srcPrior);
1049
- }
1050
- }
1051
- }
1052
- function hoconValueToResObj(hv) {
1053
- const obj = makeResObj();
1054
- if (hv.kind !== "object") return obj;
1055
- for (const [key, val] of hv.fields) {
1056
- if (val.kind === "object") {
1057
- obj.fields.set(key, hoconValueToResObj(val));
1058
- } else {
1059
- obj.fields.set(key, val);
1060
- }
1061
- }
1062
- return obj;
1063
- }
1064
- function isFileNotFoundError(e) {
1065
- if (!(e instanceof Error)) return false;
1066
- const code = e.code;
1067
- if (code === "ENOENT" || code === "MODULE_NOT_FOUND") return true;
1068
- const msg = e.message.toLowerCase();
1069
- return msg.includes("not found") || msg.includes("no such file") || msg.includes("enoent");
1070
- }
1071
-
1072
- // src/internal/resolver/include-loader.ts
1073
- import * as nodePath from "path";
1074
-
1075
1152
  // src/internal/properties/properties.ts
1076
1153
  function parseProperties(input) {
1077
1154
  const root = /* @__PURE__ */ Object.create(null);
1155
+ const pairs = [];
1078
1156
  for (const line of input.split("\n")) {
1079
1157
  const trimmed = line.trim();
1080
1158
  if (trimmed === "" || trimmed.startsWith("#") || trimmed.startsWith("!")) continue;
@@ -1083,6 +1161,10 @@ function parseProperties(input) {
1083
1161
  const key = trimmed.slice(0, sepIdx).trim();
1084
1162
  const value = trimmed.slice(sepIdx + 1).trim();
1085
1163
  if (key === "") continue;
1164
+ pairs.push([key, value]);
1165
+ }
1166
+ pairs.sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
1167
+ for (const [key, value] of pairs) {
1086
1168
  setNested(root, key.split("."), value);
1087
1169
  }
1088
1170
  return root;
@@ -1106,6 +1188,7 @@ function setNested(obj, segments, value) {
1106
1188
  }
1107
1189
  const last = segments[segments.length - 1];
1108
1190
  if (last === void 0 || DANGEROUS_KEYS.has(last)) return;
1191
+ if (typeof current[last] === "object" && current[last] !== null) return;
1109
1192
  current[last] = value;
1110
1193
  }
1111
1194
  function propertiesToHoconValue(input) {
@@ -1125,6 +1208,84 @@ function recordToHoconValue(obj) {
1125
1208
  }
1126
1209
 
1127
1210
  // src/internal/resolver/include-loader.ts
1211
+ var _require = createRequire(import.meta.url);
1212
+ function isPnpPath(p) {
1213
+ return p.includes(".yarn/cache") || p.includes(".pnp.cjs") || p.includes(".pnp.loader");
1214
+ }
1215
+ function validatePackageFile(file, identifier) {
1216
+ if (file.length === 0) {
1217
+ throw new ParseError(`include package("${identifier}", ...): file argument must be non-empty`, 0, 0);
1218
+ }
1219
+ if (file.includes("\\")) {
1220
+ throw new ParseError(
1221
+ `include package("${identifier}", "${file}"): backslash not allowed in file argument (use forward slash)`,
1222
+ 0,
1223
+ 0
1224
+ );
1225
+ }
1226
+ if (file.startsWith("/")) {
1227
+ throw new ParseError(
1228
+ `include package("${identifier}", "${file}"): absolute path not allowed in file argument`,
1229
+ 0,
1230
+ 0
1231
+ );
1232
+ }
1233
+ if (/(?:^|\/)\.\.?(?:\/|$)/u.test(file)) {
1234
+ throw new ParseError(
1235
+ `include package("${identifier}", "${file}"): path traversal (. or ..) not allowed in file argument`,
1236
+ 0,
1237
+ 0
1238
+ );
1239
+ }
1240
+ if (file.includes("//")) {
1241
+ throw new ParseError(
1242
+ `include package("${identifier}", "${file}"): consecutive slashes not allowed in file argument`,
1243
+ 0,
1244
+ 0
1245
+ );
1246
+ }
1247
+ }
1248
+ function validatePackageIdentifier(identifier) {
1249
+ if (identifier.length === 0) {
1250
+ throw new ParseError("include package: identifier argument must be non-empty", 0, 0);
1251
+ }
1252
+ }
1253
+ function defaultPackageResolver(identifier, file, includingFile, baseDir, resolveFrom) {
1254
+ const from = resolveFrom ? Array.isArray(resolveFrom) ? resolveFrom : [resolveFrom] : baseDir ? [baseDir] : includingFile ? [nodePath.dirname(includingFile)] : [process.cwd()];
1255
+ let resolved;
1256
+ try {
1257
+ resolved = _require.resolve(`${identifier}/${file}`, { paths: from });
1258
+ } catch {
1259
+ throw new PackageLookupError(
1260
+ `include package("${identifier}", "${file}"): module not found (starting from: ${from.join(", ")})`,
1261
+ identifier,
1262
+ file,
1263
+ 0,
1264
+ 0
1265
+ );
1266
+ }
1267
+ if (isPnpPath(resolved)) {
1268
+ throw new ResolveError(
1269
+ `include package("${identifier}", "${file}"): Yarn Berry PnP store paths are not supported. Provide a custom packageResolver option to handle PnP resolution.`,
1270
+ `${identifier}/${file}`,
1271
+ 0,
1272
+ 0
1273
+ );
1274
+ }
1275
+ const fileParts = file.split("/");
1276
+ const expectedBasename = fileParts[fileParts.length - 1] ?? file;
1277
+ const resolvedBasename = nodePath.basename(resolved);
1278
+ if (resolvedBasename !== expectedBasename) {
1279
+ throw new PackageLookupError(
1280
+ `include package("${identifier}", "${file}"): case mismatch \u2014 resolved "${resolvedBasename}" but expected "${expectedBasename}" (E11 decision 5: case-sensitive)`,
1281
+ identifier,
1282
+ file,
1283
+ 0,
1284
+ 0
1285
+ );
1286
+ }
1287
+ return resolved;
1288
+ }
1128
1289
  var IncludeLoader = class {
1129
1290
  constructor(opts) {
1130
1291
  this.opts = opts;
@@ -1204,8 +1365,77 @@ var IncludeLoader = class {
1204
1365
  }
1205
1366
  return merged;
1206
1367
  }
1368
+ /**
1369
+ * Resolve and load `include package("identifier", "file")`.
1370
+ * Validates the identifier and file arguments, resolves via the package resolver,
1371
+ * performs cycle detection using a JSON-serialised cycle key, and parses the content.
1372
+ */
1373
+ // `required` is reserved for future optional-package semantics (E11 decision 7);
1374
+ // currently all package lookup failures throw regardless of this flag.
1375
+ loadPackage(identifier, file, _required) {
1376
+ validatePackageIdentifier(identifier);
1377
+ validatePackageFile(file, identifier);
1378
+ const { includeStack = [], baseDir, resolveFrom, packageResolver, readFileSync: readFileSync2 } = this.opts;
1379
+ const cycleKey = JSON.stringify(["package", identifier, file]);
1380
+ if (includeStack.includes(cycleKey)) {
1381
+ throw new ResolveError(
1382
+ `circular include: package("${identifier}", "${file}")`,
1383
+ `${identifier}/${file}`,
1384
+ 0,
1385
+ 0
1386
+ );
1387
+ }
1388
+ if (includeStack.length >= 50) {
1389
+ throw new ResolveError(`include depth limit exceeded (max 50)`, `${identifier}/${file}`, 0, 0);
1390
+ }
1391
+ const resolver = packageResolver ?? ((id, f, includingFile, bDir) => defaultPackageResolver(id, f, includingFile, bDir, resolveFrom));
1392
+ const resolvedPath = resolver(identifier, file, void 0, baseDir);
1393
+ const content = readFileSync2(resolvedPath);
1394
+ if (content.length === 0) return makeResObj();
1395
+ const tokens = tokenize(content);
1396
+ assertNonEmptyDocument(tokens, resolvedPath);
1397
+ const ast = parseTokens(tokens);
1398
+ return this.onBuildResObj(ast, {
1399
+ ...this.opts,
1400
+ baseDir: nodePath.dirname(resolvedPath),
1401
+ includeStack: [...includeStack, cycleKey]
1402
+ });
1403
+ }
1404
+ /**
1405
+ * Async variant of `loadPackage`.
1406
+ */
1407
+ async loadPackageAsync(identifier, file, _required) {
1408
+ validatePackageIdentifier(identifier);
1409
+ validatePackageFile(file, identifier);
1410
+ const { includeStack = [], baseDir, resolveFrom, packageResolver, readFile, readFileSync: readFileSync2 } = this.opts;
1411
+ const cycleKey = JSON.stringify(["package", identifier, file]);
1412
+ if (includeStack.includes(cycleKey)) {
1413
+ throw new ResolveError(
1414
+ `circular include: package("${identifier}", "${file}")`,
1415
+ `${identifier}/${file}`,
1416
+ 0,
1417
+ 0
1418
+ );
1419
+ }
1420
+ if (includeStack.length >= 50) {
1421
+ throw new ResolveError(`include depth limit exceeded (max 50)`, `${identifier}/${file}`, 0, 0);
1422
+ }
1423
+ const resolver = packageResolver ?? ((id, f, includingFile, bDir) => defaultPackageResolver(id, f, includingFile, bDir, resolveFrom));
1424
+ const resolvedPath = resolver(identifier, file, void 0, baseDir);
1425
+ const read = readFile ? async (p) => readFile(p) : async (p) => readFileSync2(p);
1426
+ const content = await read(resolvedPath);
1427
+ if (content.length === 0) return makeResObj();
1428
+ const tokens = tokenize(content);
1429
+ assertNonEmptyDocument(tokens, resolvedPath);
1430
+ const ast = parseTokens(tokens);
1431
+ return this.onBuildResObjAsync(ast, {
1432
+ ...this.opts,
1433
+ baseDir: nodePath.dirname(resolvedPath),
1434
+ includeStack: [...includeStack, cycleKey]
1435
+ });
1436
+ }
1207
1437
  loadSingle(candidate) {
1208
- const { readFileSync: readFileSync2, includeStack = [], env } = this.opts;
1438
+ const { readFileSync: readFileSync2, includeStack = [] } = this.opts;
1209
1439
  if (includeStack.includes(candidate)) {
1210
1440
  throw new ResolveError(`circular include: ${candidate}`, candidate, 0, 0);
1211
1441
  }
@@ -1219,16 +1449,17 @@ var IncludeLoader = class {
1219
1449
  if (candidate.endsWith(".properties")) {
1220
1450
  return hoconValueToResObj(propertiesToHoconValue(content));
1221
1451
  }
1222
- const ast = parseTokens(tokenize(content));
1452
+ const tokens = tokenize(content);
1453
+ assertNonEmptyDocument(tokens, candidate);
1454
+ const ast = parseTokens(tokens);
1223
1455
  return this.onBuildResObj(ast, {
1224
- env,
1456
+ ...this.opts,
1225
1457
  baseDir: nodePath.dirname(candidate),
1226
- readFileSync: readFileSync2,
1227
1458
  includeStack: [...includeStack, candidate]
1228
1459
  });
1229
1460
  }
1230
1461
  async loadSingleAsync(candidate) {
1231
- const { readFile, readFileSync: readFileSync2, includeStack = [], env } = this.opts;
1462
+ const { readFile, readFileSync: readFileSync2, includeStack = [] } = this.opts;
1232
1463
  const read = readFile ? async (p) => readFile(p) : async (p) => readFileSync2(p);
1233
1464
  if (includeStack.includes(candidate)) {
1234
1465
  throw new ResolveError(`circular include: ${candidate}`, candidate, 0, 0);
@@ -1243,12 +1474,12 @@ var IncludeLoader = class {
1243
1474
  if (candidate.endsWith(".properties")) {
1244
1475
  return hoconValueToResObj(propertiesToHoconValue(content));
1245
1476
  }
1246
- const ast = parseTokens(tokenize(content));
1477
+ const tokens = tokenize(content);
1478
+ assertNonEmptyDocument(tokens, candidate);
1479
+ const ast = parseTokens(tokens);
1247
1480
  return this.onBuildResObjAsync(ast, {
1248
- env,
1481
+ ...this.opts,
1249
1482
  baseDir: nodePath.dirname(candidate),
1250
- readFileSync: readFileSync2,
1251
- readFile,
1252
1483
  includeStack: [...includeStack, candidate]
1253
1484
  });
1254
1485
  }
@@ -1257,7 +1488,6 @@ var IncludeLoader = class {
1257
1488
  // src/internal/resolver/structure-builder.ts
1258
1489
  var StructureBuilder = class _StructureBuilder {
1259
1490
  constructor(opts) {
1260
- this.opts = opts;
1261
1491
  this.loader = new IncludeLoader(opts);
1262
1492
  this.loader.onBuildResObj = (a, o) => new _StructureBuilder(o).build(a);
1263
1493
  this.loader.onBuildResObjAsync = async (a, o) => new _StructureBuilder(o).buildAsync(a);
@@ -1284,7 +1514,13 @@ var StructureBuilder = class _StructureBuilder {
1284
1514
  }
1285
1515
  applyField(obj, field, pathPrefix) {
1286
1516
  if (field.key.length === 0 && field.value.kind === "include") {
1287
- const included = this.loader.load(field.value.path, field.value.required, field.value.isFile);
1517
+ const { qualifier, path: path2, required } = field.value;
1518
+ let included;
1519
+ if (qualifier.kind === "package") {
1520
+ included = this.loader.loadPackage(qualifier.identifier, path2, required);
1521
+ } else {
1522
+ included = this.loader.load(path2, required, qualifier.kind === "file");
1523
+ }
1288
1524
  if (pathPrefix.length > 0) {
1289
1525
  this.relativizeResObj(included, pathPrefix);
1290
1526
  }
@@ -1326,7 +1562,13 @@ var StructureBuilder = class _StructureBuilder {
1326
1562
  }
1327
1563
  async applyFieldAsync(obj, field, pathPrefix) {
1328
1564
  if (field.key.length === 0 && field.value.kind === "include") {
1329
- const included = await this.loader.loadAsync(field.value.path, field.value.required, field.value.isFile);
1565
+ const { qualifier, path: path2, required } = field.value;
1566
+ let included;
1567
+ if (qualifier.kind === "package") {
1568
+ included = await this.loader.loadPackageAsync(qualifier.identifier, path2, required);
1569
+ } else {
1570
+ included = await this.loader.loadAsync(path2, required, qualifier.kind === "file");
1571
+ }
1330
1572
  if (pathPrefix.length > 0) {
1331
1573
  this.relativizeResObj(included, pathPrefix);
1332
1574
  }
@@ -1380,9 +1622,9 @@ var StructureBuilder = class _StructureBuilder {
1380
1622
  return inner;
1381
1623
  }
1382
1624
  case "subst":
1383
- return { _kind: "subst-placeholder", segments: ast.segments, optional: ast.optional, line: ast.pos.line, col: ast.pos.col, prefixLen: 0 };
1625
+ return { _kind: "subst-placeholder", segments: ast.segments, optional: ast.optional, listSuffix: ast.listSuffix, line: ast.pos.line, col: ast.pos.col, prefixLen: 0 };
1384
1626
  case "concat":
1385
- return { _kind: "concat-placeholder", nodes: ast.nodes.map((n) => this.astToResolverValue(n, pathPrefix)) };
1627
+ return { _kind: "concat-placeholder", nodes: ast.nodes.map((n) => this.astToResolverValue(n, pathPrefix)), line: ast.pos.line, col: ast.pos.col };
1386
1628
  case "include":
1387
1629
  return { kind: "scalar", raw: "null", valueType: "null" };
1388
1630
  }
@@ -1404,13 +1646,13 @@ var StructureBuilder = class _StructureBuilder {
1404
1646
  case "object":
1405
1647
  return await this.buildAsync(ast, pathPrefix);
1406
1648
  case "subst":
1407
- return { _kind: "subst-placeholder", segments: ast.segments, optional: ast.optional, line: ast.pos.line, col: ast.pos.col, prefixLen: 0 };
1649
+ return { _kind: "subst-placeholder", segments: ast.segments, optional: ast.optional, listSuffix: ast.listSuffix, line: ast.pos.line, col: ast.pos.col, prefixLen: 0 };
1408
1650
  case "concat": {
1409
1651
  const nodes = [];
1410
1652
  for (const n of ast.nodes) {
1411
1653
  nodes.push(await this.astToResolverValueAsync(n, pathPrefix));
1412
1654
  }
1413
- return { _kind: "concat-placeholder", nodes };
1655
+ return { _kind: "concat-placeholder", nodes, line: ast.pos.line, col: ast.pos.col };
1414
1656
  }
1415
1657
  case "include":
1416
1658
  return { kind: "scalar", raw: "null", valueType: "null" };
@@ -1456,6 +1698,23 @@ var StructureBuilder = class _StructureBuilder {
1456
1698
  }
1457
1699
  };
1458
1700
 
1701
+ // src/value/numeric-array.ts
1702
+ function numericObjectToArray(value) {
1703
+ if (value.kind !== "object") return null;
1704
+ if (value.fields.size === 0) return null;
1705
+ const canonicalInt = /^(0|[1-9][0-9]*)$/;
1706
+ const eligible = [];
1707
+ for (const [key, val] of value.fields) {
1708
+ if (!canonicalInt.test(key)) continue;
1709
+ const n = Number(key);
1710
+ if (!Number.isInteger(n) || n > 2147483647) continue;
1711
+ eligible.push({ n, v: val });
1712
+ }
1713
+ if (eligible.length === 0) return null;
1714
+ eligible.sort((a, b) => a.n - b.n);
1715
+ return eligible.map((e) => e.v);
1716
+ }
1717
+
1459
1718
  // src/internal/resolver/substitution-resolver.ts
1460
1719
  var SubstitutionResolver = class {
1461
1720
  constructor(root, opts) {
@@ -1463,6 +1722,23 @@ var SubstitutionResolver = class {
1463
1722
  this.opts = opts;
1464
1723
  this.resolving = /* @__PURE__ */ new Set();
1465
1724
  this.cache = /* @__PURE__ */ new Map();
1725
+ // Tracks ConcatPlaceholder nodes currently being iterated by resolveConcat.
1726
+ // Used by isSelfRef detection: fires only when found === a concat currently
1727
+ // mid-iteration (i.e. we are inside found's own resolution), preventing the
1728
+ // "foofoo" double-count without misfiring when an external field looks up the
1729
+ // same concat via a substitution (multi-reviewer convergence fix).
1730
+ //
1731
+ // Spec deviation: the S13a.13 spec ★1 decision #1 specified path-equality
1732
+ // preservation for self-ref detection. Round-2 multi-agent-review surfaced a
1733
+ // false-positive on external lookups (`a = ${?a}foo; b = ${a}`), so the
1734
+ // criterion was tightened to node-membership-in-iterating-set — strictly
1735
+ // narrower than path-equality. Spec amendment deferred to a follow-up
1736
+ // xx.hocon PR (see Phase 6 #3f close-out notes).
1737
+ this.resolvingConcats = /* @__PURE__ */ new WeakSet();
1738
+ }
1739
+ /** Returns a prefix for error messages, including origin info when set. */
1740
+ originPrefix() {
1741
+ return this.opts.originDescription ? `${this.opts.originDescription}: ` : "";
1466
1742
  }
1467
1743
  resolve() {
1468
1744
  return this.resolveResObj(this.root);
@@ -1501,7 +1777,15 @@ var SubstitutionResolver = class {
1501
1777
  }
1502
1778
  resolveVal(v, scope) {
1503
1779
  if (isSubst(v)) return this.resolveSubst(v, scope);
1504
- if (isConcat(v)) return this.resolveConcat(v.nodes, scope);
1780
+ if (isConcat(v)) {
1781
+ this.resolvingConcats.add(v);
1782
+ try {
1783
+ const concatResult = this.resolveConcat(v.nodes, scope, v.line, v.col);
1784
+ return concatResult;
1785
+ } finally {
1786
+ this.resolvingConcats.delete(v);
1787
+ }
1788
+ }
1505
1789
  if (isAppend(v)) return this.resolveAppend(v, scope);
1506
1790
  if (isResObj(v)) return this.resolveResObj(v);
1507
1791
  const hv = v;
@@ -1515,15 +1799,12 @@ var SubstitutionResolver = class {
1515
1799
  }
1516
1800
  return hv;
1517
1801
  }
1518
- segmentsEqual(a, b) {
1519
- return a.length === b.length && a.every((seg, i) => seg.text === b[i]?.text);
1520
- }
1521
1802
  resolveSubst(s, scope) {
1522
- const key = segmentsToKey(s.segments);
1803
+ const key = s.listSuffix ? `${segmentsToKey(s.segments)}[]` : segmentsToKey(s.segments);
1523
1804
  if (this.cache.has(key)) return this.cache.get(key);
1524
1805
  if (this.resolving.has(key)) {
1525
1806
  let prior;
1526
- if (s.prefixLen > 0) {
1807
+ if (s.segments.length > 1) {
1527
1808
  const leafSeg = s.segments[s.segments.length - 1]?.text ?? "";
1528
1809
  const parentScope = lookupResObj(
1529
1810
  this.root,
@@ -1545,7 +1826,7 @@ var SubstitutionResolver = class {
1545
1826
  }
1546
1827
  if (s.optional) return void 0;
1547
1828
  throw new ResolveError(
1548
- `circular substitution: ${key}`,
1829
+ this.originPrefix() + `circular substitution: ${key}`,
1549
1830
  key,
1550
1831
  s.line,
1551
1832
  s.col
@@ -1555,29 +1836,31 @@ var SubstitutionResolver = class {
1555
1836
  try {
1556
1837
  const found = lookupPath(this.root, s.segments);
1557
1838
  if (found !== void 0) {
1558
- if (isSubst(found) || isConcat(found)) {
1559
- const isSelfRef = isSubst(found) ? this.segmentsEqual(found.segments, s.segments) : isConcat(found) && found.nodes.some(
1560
- (n) => isSubst(n) && this.segmentsEqual(n.segments, s.segments)
1561
- );
1562
- if (isSelfRef) {
1563
- let prior;
1564
- if (s.prefixLen > 0) {
1565
- const leafSeg = s.segments[s.segments.length - 1]?.text ?? "";
1566
- const parentScope = lookupResObj(
1567
- this.root,
1568
- s.segments.slice(0, s.segments.length - 1)
1569
- );
1570
- prior = parentScope?.priorValues.get(leafSeg);
1571
- } else {
1572
- const rootSeg = s.segments[0]?.text ?? "";
1573
- prior = scope.priorValues.get(rootSeg) ?? this.root.priorValues.get(rootSeg);
1574
- }
1575
- if (prior !== void 0) {
1576
- const result2 = this.resolveVal(prior, scope);
1577
- if (result2 !== void 0) this.cache.set(key, result2);
1578
- return result2;
1579
- }
1839
+ if (isConcat(found) && this.resolvingConcats.has(found)) {
1840
+ let prior;
1841
+ if (s.segments.length > 1) {
1842
+ const leafSeg = s.segments[s.segments.length - 1]?.text ?? "";
1843
+ const parentScope = lookupResObj(
1844
+ this.root,
1845
+ s.segments.slice(0, s.segments.length - 1)
1846
+ );
1847
+ prior = parentScope?.priorValues.get(leafSeg);
1848
+ } else {
1849
+ const rootSeg = s.segments[0]?.text ?? "";
1850
+ prior = scope.priorValues.get(rootSeg) ?? this.root.priorValues.get(rootSeg);
1851
+ }
1852
+ if (prior !== void 0) {
1853
+ const result2 = this.resolveVal(prior, scope);
1854
+ if (result2 !== void 0) this.cache.set(key, result2);
1855
+ return result2;
1580
1856
  }
1857
+ if (s.optional) return void 0;
1858
+ throw new ResolveError(
1859
+ `${this.originPrefix()}could not resolve substitution: \${${key}}`,
1860
+ key,
1861
+ s.line,
1862
+ s.col
1863
+ );
1581
1864
  }
1582
1865
  let result = this.resolveVal(found, scope);
1583
1866
  const effectiveLen = s.segments.length - s.prefixLen;
@@ -1606,16 +1889,45 @@ var SubstitutionResolver = class {
1606
1889
  if (result !== void 0) this.cache.set(key, result);
1607
1890
  return result;
1608
1891
  }
1609
- const envKey = s.segments.map((seg) => seg.text).join(".");
1610
- const envVal = this.opts.env[envKey] ?? (s.prefixLen > 0 ? this.opts.env[s.segments.slice(s.prefixLen).map((seg) => seg.text).join(".")] : void 0);
1611
- if (envVal !== void 0) {
1612
- const result = { kind: "scalar", raw: envVal, valueType: "string" };
1613
- this.cache.set(key, result);
1614
- return result;
1892
+ if (s.listSuffix && this.opts.useSystemEnvironment !== false) {
1893
+ const result = this.resolveEnvList(s);
1894
+ if (result !== void 0) {
1895
+ this.cache.set(key, result);
1896
+ return result;
1897
+ }
1898
+ if (s.optional) return void 0;
1899
+ const envBase = s.segments.map((seg) => seg.text).join(".");
1900
+ throw new ResolveError(
1901
+ `${this.originPrefix()}could not resolve substitution: \${${key}} (no environment variables ${envBase}_0, ${envBase}_1, \u2026 set)`,
1902
+ key,
1903
+ s.line,
1904
+ s.col
1905
+ );
1906
+ } else if (s.listSuffix) {
1907
+ if (s.optional) return void 0;
1908
+ if (this.opts.allowUnresolved) return s;
1909
+ throw new ResolveError(
1910
+ `${this.originPrefix()}could not resolve substitution: \${${key}}`,
1911
+ key,
1912
+ s.line,
1913
+ s.col
1914
+ );
1915
+ }
1916
+ if (this.opts.useSystemEnvironment !== false) {
1917
+ const envKey = s.segments.map((seg) => seg.text).join(".");
1918
+ const envVal = this.opts.env[envKey] ?? (s.prefixLen > 0 ? this.opts.env[s.segments.slice(s.prefixLen).map((seg) => seg.text).join(".")] : void 0);
1919
+ if (envVal !== void 0) {
1920
+ const result = { kind: "scalar", raw: envVal, valueType: "string" };
1921
+ this.cache.set(key, result);
1922
+ return result;
1923
+ }
1615
1924
  }
1616
1925
  if (s.optional) return void 0;
1926
+ if (this.opts.allowUnresolved) {
1927
+ return s;
1928
+ }
1617
1929
  throw new ResolveError(
1618
- `could not resolve substitution: \${${key}}`,
1930
+ `${this.originPrefix()}could not resolve substitution: \${${key}}`,
1619
1931
  key,
1620
1932
  s.line,
1621
1933
  s.col
@@ -1624,9 +1936,52 @@ var SubstitutionResolver = class {
1624
1936
  this.resolving.delete(key);
1625
1937
  }
1626
1938
  }
1627
- resolveConcat(nodes, scope) {
1628
- const resolved = nodes.map((n) => this.resolveVal(n, scope)).filter((v) => v !== void 0);
1629
- if (resolved.length === 0) return { kind: "scalar", raw: "null", valueType: "null" };
1939
+ /**
1940
+ * S13c: scan environment for NAME_0, NAME_1, … and return an Array<Scalar(String)>.
1941
+ *
1942
+ * Candidate bases follow the same fully-qualified→bare order used by the scalar
1943
+ * env fallback. The first base whose _0 key is present wins entirely — no
1944
+ * cross-base merging (spec §Relativized fallback precedence rules).
1945
+ *
1946
+ * Returns the HoconValue Array if any candidate base has _0, or undefined if
1947
+ * none do (caller applies optional/required handling).
1948
+ */
1949
+ resolveEnvList(s) {
1950
+ const bases = this.candidateBases(s);
1951
+ for (const base of bases) {
1952
+ const key0 = base + "_0";
1953
+ if (!(key0 in this.opts.env)) continue;
1954
+ const items = [];
1955
+ for (let i = 0; ; i++) {
1956
+ const k = base + "_" + i;
1957
+ if (!(k in this.opts.env)) break;
1958
+ items.push({ kind: "scalar", raw: this.opts.env[k], valueType: "string" });
1959
+ }
1960
+ return { kind: "array", items };
1961
+ }
1962
+ return void 0;
1963
+ }
1964
+ /**
1965
+ * Return the candidate base names for env-var-list lookup, in precedence order.
1966
+ * Mirrors the fully-qualified→bare fallback used by the scalar env path.
1967
+ */
1968
+ candidateBases(s) {
1969
+ const full = s.segments.map((seg) => seg.text).join(".");
1970
+ if (s.prefixLen === 0) return [full];
1971
+ const bare = s.segments.slice(s.prefixLen).map((seg) => seg.text).join(".");
1972
+ return [full, bare];
1973
+ }
1974
+ resolveConcat(nodes, scope, line = 0, col = 0) {
1975
+ const rawResolved = nodes.map((n) => this.resolveVal(n, scope));
1976
+ if (this.opts.allowUnresolved) {
1977
+ for (const v of rawResolved) {
1978
+ if (v !== void 0 && isSubst(v)) {
1979
+ return v;
1980
+ }
1981
+ }
1982
+ }
1983
+ const resolved = rawResolved.filter((v) => v !== void 0);
1984
+ if (resolved.length === 0) return void 0;
1630
1985
  if (resolved.length === 1) return resolved[0];
1631
1986
  const nonSep = resolved.filter((v) => !separatorValues.has(v));
1632
1987
  if (nonSep.length === 0) {
@@ -1642,27 +1997,45 @@ var SubstitutionResolver = class {
1642
1997
  if (converted !== null) {
1643
1998
  return { kind: "array", items: [...left.items, ...converted] };
1644
1999
  }
1645
- return { kind: "array", items: [...left.items, right] };
2000
+ throw new ResolveError(
2001
+ "cannot concatenate array with object: value concatenation requires same-kind operands (S10.4)",
2002
+ "",
2003
+ line,
2004
+ col
2005
+ );
1646
2006
  }
1647
2007
  if (left.kind === "object" && right.kind === "array") {
1648
2008
  const converted = numericObjectToArray(left);
1649
2009
  if (converted !== null) {
1650
2010
  return { kind: "array", items: [...converted, ...right.items] };
1651
2011
  }
1652
- return { kind: "array", items: [left, ...right.items] };
2012
+ throw new ResolveError(
2013
+ "cannot concatenate object with array: value concatenation requires same-kind operands (S10.4)",
2014
+ "",
2015
+ line,
2016
+ col
2017
+ );
1653
2018
  }
1654
2019
  if (left.kind === "array" && right.kind === "array") {
1655
2020
  return { kind: "array", items: [...left.items, ...right.items] };
1656
2021
  }
1657
- if (left.kind === "array") {
1658
- return { kind: "array", items: [...left.items, right] };
2022
+ if (left.kind === "array" || right.kind === "array") {
2023
+ throw new ResolveError(
2024
+ `cannot concatenate ${left.kind} with ${right.kind}: arrays and objects may not appear in string value concatenation (S10.13)`,
2025
+ "",
2026
+ line,
2027
+ col
2028
+ );
1659
2029
  }
1660
- if (right.kind === "array") {
1661
- return { kind: "array", items: [left, ...right.items] };
2030
+ if (left.kind === "object" || right.kind === "object") {
2031
+ throw new ResolveError(
2032
+ `cannot concatenate ${left.kind} with ${right.kind}: arrays and objects may not appear in string value concatenation (S10.13)`,
2033
+ "",
2034
+ line,
2035
+ col
2036
+ );
1662
2037
  }
1663
- const leftStr = left.kind === "scalar" ? left.raw : JSON.stringify(left);
1664
- const rightStr = right.kind === "scalar" ? right.raw : JSON.stringify(right);
1665
- return { kind: "scalar", raw: leftStr + rightStr, valueType: "string" };
2038
+ return { kind: "scalar", raw: left.raw + right.raw, valueType: "string" };
1666
2039
  };
1667
2040
  const [head, ...tail] = nonSep;
1668
2041
  const folded = tail.reduce(joinPair, head);
@@ -1682,18 +2055,503 @@ var SubstitutionResolver = class {
1682
2055
  };
1683
2056
 
1684
2057
  // src/internal/resolver/resolver.ts
2058
+ function buildTree(ast, opts) {
2059
+ return new StructureBuilder(opts).build(ast);
2060
+ }
2061
+ function resolveTree(tree, opts) {
2062
+ return new SubstitutionResolver(tree, opts).resolve();
2063
+ }
2064
+ function containsPlaceholders(tree) {
2065
+ for (const val of tree.fields.values()) {
2066
+ if (valContainsPlaceholders(val)) return true;
2067
+ }
2068
+ return false;
2069
+ }
2070
+ function valContainsPlaceholders(v) {
2071
+ const rv = v;
2072
+ if (isSubst(rv) || isConcat(rv) || isAppend(rv)) return true;
2073
+ if (isResObj(rv)) return containsPlaceholders(rv);
2074
+ const hv = v;
2075
+ if (hv?.kind === "array") {
2076
+ return hv.items.some((item) => valContainsPlaceholders(item));
2077
+ }
2078
+ return false;
2079
+ }
2080
+ function buildPartialHoconFromResObj(tree) {
2081
+ const fields = /* @__PURE__ */ new Map();
2082
+ for (const [k, v] of tree.fields) {
2083
+ if (!isSubst(v) && !isConcat(v) && !isAppend(v)) {
2084
+ if (isResObj(v)) {
2085
+ fields.set(k, buildPartialHoconFromResObj(v));
2086
+ } else {
2087
+ if (!valContainsPlaceholders(v)) {
2088
+ fields.set(k, v);
2089
+ }
2090
+ }
2091
+ }
2092
+ }
2093
+ return { kind: "object", fields };
2094
+ }
2095
+ function hoconValueToResObj2(value) {
2096
+ const obj = makeResObj();
2097
+ for (const [k, v] of value.fields) {
2098
+ if (v.kind === "object") {
2099
+ obj.fields.set(k, hoconValueToResObj2(v));
2100
+ } else {
2101
+ obj.fields.set(k, v);
2102
+ }
2103
+ }
2104
+ return obj;
2105
+ }
1685
2106
  function resolve2(ast, opts) {
1686
- const root = new StructureBuilder(opts).build(ast);
1687
- return new SubstitutionResolver(root, opts).resolve();
2107
+ const tree = buildTree(ast, opts);
2108
+ return resolveTree(tree, { ...opts, useSystemEnvironment: true, allowUnresolved: false });
1688
2109
  }
1689
2110
  async function resolveAsync(ast, opts) {
1690
2111
  const root = await new StructureBuilder(opts).buildAsync(ast);
1691
- return new SubstitutionResolver(root, opts).resolve();
2112
+ return resolveTree(root, { ...opts, useSystemEnvironment: true, allowUnresolved: false });
2113
+ }
2114
+
2115
+ // src/config.ts
2116
+ var Config = class _Config {
2117
+ constructor(root, opts) {
2118
+ this.root = root;
2119
+ this._resolved = opts?.resolved ?? true;
2120
+ this._parseBaseDir = opts?.parseBaseDir;
2121
+ this._originDescription = opts?.originDescription;
2122
+ this._resObjRoot = opts?.resObjRoot;
2123
+ this._resolveOpts = opts?.resolveOpts;
2124
+ }
2125
+ /** Returns true when the Config's value tree contains no unresolved
2126
+ * substitution placeholders. Whole-config granularity (E12 decision 11). */
2127
+ isResolved() {
2128
+ return this._resolved;
2129
+ }
2130
+ /** @internal Test-only: construct a resolved Config from a HoconValue. */
2131
+ static _fromResolvedValue(root, opts) {
2132
+ return new _Config(root, { resolved: true, originDescription: opts?.originDescription });
2133
+ }
2134
+ /** @internal Construct an unresolved Config carrying a ResObj tree.
2135
+ * Used by parseStringWithOptions (deferred path) in T6. */
2136
+ static _fromUnresolvedResObj(tree, opts) {
2137
+ const partialRoot = buildPartialHoconFromResObj(tree);
2138
+ return new _Config(partialRoot, {
2139
+ resolved: opts.resolved,
2140
+ parseBaseDir: opts.parseBaseDir,
2141
+ originDescription: opts.originDescription,
2142
+ resObjRoot: tree,
2143
+ resolveOpts: opts.resolveOpts
2144
+ });
2145
+ }
2146
+ get(path2) {
2147
+ const v = this.lookupNode(path2);
2148
+ if (v === void 0) return void 0;
2149
+ return hoconToJs(v);
2150
+ }
2151
+ getString(path2) {
2152
+ const v = this.requireScalar(path2);
2153
+ return v.raw;
2154
+ }
2155
+ getNumber(path2) {
2156
+ const v = this.requireScalar(path2);
2157
+ const coerced = coerceNumber(v.raw);
2158
+ if (coerced !== void 0) return coerced;
2159
+ throw new ConfigError(`expected number at ${path2}, got ${v.valueType}`, path2);
2160
+ }
2161
+ getBoolean(path2) {
2162
+ const v = this.requireScalar(path2);
2163
+ const coerced = coerceBoolean(v.raw);
2164
+ if (coerced !== void 0) return coerced;
2165
+ throw new ConfigError(`expected boolean at ${path2}, got ${v.valueType}`, path2);
2166
+ }
2167
+ getDuration(path2, unit) {
2168
+ const v = this.requireScalar(path2);
2169
+ if (v.valueType !== "string" && v.valueType !== "number") {
2170
+ throw new ConfigError(`expected duration at ${path2}, got ${v.valueType}`, path2);
2171
+ }
2172
+ const result = parseDuration(v.raw, unit);
2173
+ if (Number.isNaN(result)) throw new ConfigError(`invalid duration at ${path2}: ${JSON.stringify(v.raw)}`, path2);
2174
+ return result;
2175
+ }
2176
+ getBytes(path2, unit) {
2177
+ const v = this.requireScalar(path2);
2178
+ if (v.valueType !== "string" && v.valueType !== "number") {
2179
+ throw new ConfigError(`expected byte size at ${path2}, got ${v.valueType}`, path2);
2180
+ }
2181
+ const result = parseBytes(v.raw, unit);
2182
+ if (Number.isNaN(result)) throw new ConfigError(`invalid byte size at ${path2}: ${JSON.stringify(v.raw)}`, path2);
2183
+ if (result < 0) throw new ConfigError(`byte size must be non-negative at ${path2}: ${JSON.stringify(v.raw)}`, path2);
2184
+ return result;
2185
+ }
2186
+ getConfig(path2) {
2187
+ const v = this.lookupNode(path2);
2188
+ if (v === void 0) {
2189
+ if (!this._resolved && this._resObjRootSubtreeHasPlaceholders(path2)) {
2190
+ throw new NotResolvedError(path2);
2191
+ }
2192
+ throw new ConfigError(`path not found: ${path2}`, path2);
2193
+ }
2194
+ if (v.kind !== "object") throw new ConfigError(`expected object at ${path2}`, path2);
2195
+ if (!this._resolved && this._resObjRootSubtreeHasPlaceholders(path2)) {
2196
+ throw new NotResolvedError(path2);
2197
+ }
2198
+ return new _Config(v, { resolved: this._resolved });
2199
+ }
2200
+ getList(path2) {
2201
+ const v = this.lookupNode(path2);
2202
+ if (v === void 0) {
2203
+ if (!this._resolved && this._resObjRootSubtreeHasPlaceholders(path2)) {
2204
+ throw new NotResolvedError(path2);
2205
+ }
2206
+ throw new ConfigError(`path not found: ${path2}`, path2);
2207
+ }
2208
+ if (!this._resolved && this._resObjRootSubtreeHasPlaceholders(path2)) {
2209
+ throw new NotResolvedError(path2);
2210
+ }
2211
+ if (v.kind === "object") {
2212
+ const converted = numericObjectToArray(v);
2213
+ if (converted !== null) return converted.map(hoconToJs);
2214
+ }
2215
+ if (v.kind !== "array") throw new ConfigError(`expected array at ${path2}`, path2);
2216
+ return v.items.map(hoconToJs);
2217
+ }
2218
+ has(path2) {
2219
+ return this.lookupNode(path2) !== void 0;
2220
+ }
2221
+ keys() {
2222
+ return [...this.root.fields.keys()];
2223
+ }
2224
+ withFallback(fallback) {
2225
+ if (fallback == null) return this;
2226
+ const selfTree = this._resObjRoot ?? hoconValueToResObj2(this.root);
2227
+ const fbTree = fallback._resObjRoot ?? hoconValueToResObj2(fallback.root);
2228
+ const merged = mergeUnresolved(selfTree, fbTree);
2229
+ const hasPlaceholders = containsPlaceholders(merged);
2230
+ const partialRoot = buildPartialHoconFromResObj(merged);
2231
+ return new _Config(partialRoot, {
2232
+ resolved: !hasPlaceholders,
2233
+ parseBaseDir: this._parseBaseDir,
2234
+ originDescription: this._originDescription,
2235
+ resObjRoot: merged,
2236
+ resolveOpts: this._resolveOpts ?? fallback._resolveOpts
2237
+ });
2238
+ }
2239
+ /**
2240
+ * Runs substitution resolution (phase 2) on the stored unresolved tree.
2241
+ * Idempotent: calling resolve() on an already-resolved Config returns an
2242
+ * equivalent resolved Config.
2243
+ *
2244
+ * opts.allowUnresolved (default false) — when true, leaves unresolved
2245
+ * non-optional placeholders in place rather than throwing.
2246
+ * opts.useSystemEnvironment (default true) — when false, env var lookups
2247
+ * are suppressed (hermetic resolution).
2248
+ *
2249
+ * E12 decision 3.
2250
+ */
2251
+ resolve(opts = {}) {
2252
+ if (this._resolved) {
2253
+ return new _Config(this.root, {
2254
+ resolved: true,
2255
+ parseBaseDir: this._parseBaseDir,
2256
+ originDescription: this._originDescription
2257
+ });
2258
+ }
2259
+ const tree = this._resObjRoot;
2260
+ if (!tree) {
2261
+ return new _Config(this.root, { resolved: true });
2262
+ }
2263
+ const resolveOpts = {
2264
+ ...this._resolveOpts ?? { env: {}, baseDir: void 0, readFileSync: () => {
2265
+ throw new Error("no files");
2266
+ } },
2267
+ allowUnresolved: opts.allowUnresolved ?? false,
2268
+ useSystemEnvironment: opts.useSystemEnvironment ?? true,
2269
+ originDescription: this._originDescription
2270
+ };
2271
+ const resolved = resolveTree(tree, resolveOpts);
2272
+ if (resolved.kind !== "object") throw new Error("resolve: expected object root");
2273
+ if (opts.allowUnresolved) {
2274
+ const { stripped, hadPlaceholders } = stripPlaceholderFields(resolved);
2275
+ return new _Config(stripped, {
2276
+ resolved: !hadPlaceholders,
2277
+ parseBaseDir: this._parseBaseDir,
2278
+ originDescription: this._originDescription,
2279
+ resObjRoot: hadPlaceholders ? tree : void 0,
2280
+ resolveOpts: hadPlaceholders ? resolveOpts : void 0
2281
+ });
2282
+ }
2283
+ return new _Config(resolved, {
2284
+ resolved: true,
2285
+ parseBaseDir: this._parseBaseDir,
2286
+ originDescription: this._originDescription
2287
+ });
2288
+ }
2289
+ /**
2290
+ * Resolves receiver substitutions using source as lookup context.
2291
+ * Source's keys are NOT merged into the result — differs from
2292
+ * `this.withFallback(source).resolve()` which includes source keys.
2293
+ *
2294
+ * Precondition (E12 decision 10): source must be resolved. If
2295
+ * source.isResolved() is false, throws NotResolvedError.
2296
+ *
2297
+ * On an already-resolved receiver, resolveWith is a no-op (returns an
2298
+ * equivalent resolved Config, source keys not included).
2299
+ *
2300
+ * E12 decisions 9, 10.
2301
+ */
2302
+ resolveWith(source, opts = {}) {
2303
+ if (!source.isResolved()) {
2304
+ throw new NotResolvedError("source");
2305
+ }
2306
+ if (this._resolved) {
2307
+ return new _Config(this.root, {
2308
+ resolved: true,
2309
+ parseBaseDir: this._parseBaseDir,
2310
+ originDescription: this._originDescription
2311
+ });
2312
+ }
2313
+ const receiverTree = this._resObjRoot ?? hoconValueToResObj2(this.root);
2314
+ const srcTree = hoconValueToResObj2(source.root);
2315
+ const merged = mergeUnresolved(receiverTree, srcTree);
2316
+ const resolveOpts = {
2317
+ ...this._resolveOpts ?? { env: {}, baseDir: void 0, readFileSync: () => {
2318
+ throw new Error("no files");
2319
+ } },
2320
+ allowUnresolved: opts.allowUnresolved ?? false,
2321
+ useSystemEnvironment: opts.useSystemEnvironment ?? true,
2322
+ originDescription: this._originDescription
2323
+ };
2324
+ const resolved = resolveTree(merged, resolveOpts);
2325
+ if (resolved.kind !== "object") throw new Error("resolveWith: expected object root");
2326
+ const receiverShapeForFilter = resObjToKeyShape(receiverTree);
2327
+ const filtered = filterByReceiverShape(resolved, receiverShapeForFilter);
2328
+ if (opts.allowUnresolved) {
2329
+ const { stripped, hadPlaceholders } = stripPlaceholderFields(filtered);
2330
+ return new _Config(stripped, {
2331
+ resolved: !hadPlaceholders,
2332
+ parseBaseDir: this._parseBaseDir,
2333
+ originDescription: this._originDescription,
2334
+ resObjRoot: hadPlaceholders ? receiverTree : void 0,
2335
+ resolveOpts: hadPlaceholders ? resolveOpts : void 0
2336
+ });
2337
+ }
2338
+ return new _Config(filtered, {
2339
+ resolved: true,
2340
+ parseBaseDir: this._parseBaseDir,
2341
+ originDescription: this._originDescription
2342
+ });
2343
+ }
2344
+ toObject() {
2345
+ return hoconToJs(this.root);
2346
+ }
2347
+ /**
2348
+ * @internal Test-only: renders this resolved Config's value tree as canonical
2349
+ * JSON (sorted keys, no whitespace). Used by Layer-2 fixture tests to compare
2350
+ * against Lightbend ground truth. NOT part of the public API.
2351
+ *
2352
+ * Throws if the Config is unresolved (contains placeholders).
2353
+ */
2354
+ _renderJSONForTest() {
2355
+ return renderHoconAsJSON(this.root);
2356
+ }
2357
+ lookupNode(path2) {
2358
+ const segments = splitConfigPath(path2);
2359
+ let current = this.root;
2360
+ for (const seg of segments) {
2361
+ if (current.kind !== "object") return void 0;
2362
+ const next = current.fields.get(seg);
2363
+ if (next === void 0) return void 0;
2364
+ current = next;
2365
+ }
2366
+ return current;
2367
+ }
2368
+ /**
2369
+ * _resObjRootSubtreeHasPlaceholders — walk _resObjRoot to the given path and
2370
+ * return true if the subtree there contains any unresolved placeholder.
2371
+ * Used by getConfig / getList (T2) to detect transitive unresolved placeholders
2372
+ * in object / array subtrees that would otherwise appear to be "resolved" in the
2373
+ * partial HoconValue root (because individual leaf fields are omitted rather than
2374
+ * the whole subtree when their value is a direct placeholder scalar, but object
2375
+ * sub-containers are retained in the partial root with the placeholder-keyed
2376
+ * fields omitted from them — so the object appears "present but empty-ish").
2377
+ */
2378
+ _resObjRootSubtreeHasPlaceholders(path2) {
2379
+ const tree = this._resObjRoot;
2380
+ if (!tree) return false;
2381
+ const segments = splitConfigPath(path2);
2382
+ let cur = tree;
2383
+ for (const seg of segments) {
2384
+ const val = cur.fields.get(seg);
2385
+ if (val === void 0) return false;
2386
+ if (isResObj(val)) {
2387
+ cur = val;
2388
+ } else {
2389
+ return valContainsPlaceholders(val);
2390
+ }
2391
+ }
2392
+ return containsPlaceholders(cur);
2393
+ }
2394
+ requireScalar(path2) {
2395
+ const v = this.lookupNode(path2);
2396
+ if (v === void 0) {
2397
+ if (!this._resolved && this._resObjRootSubtreeHasPlaceholders(path2)) {
2398
+ throw new NotResolvedError(path2);
2399
+ }
2400
+ throw new ConfigError(`path not found: ${path2}`, path2);
2401
+ }
2402
+ if (v.kind !== "scalar") throw new ConfigError(`expected scalar at ${path2}, got ${v.kind}`, path2);
2403
+ return v;
2404
+ }
2405
+ };
2406
+ function splitConfigPath(path2) {
2407
+ const segments = [];
2408
+ let i = 0;
2409
+ while (i < path2.length) {
2410
+ if (path2[i] === '"') {
2411
+ i++;
2412
+ let segment = "";
2413
+ let closed = false;
2414
+ while (i < path2.length) {
2415
+ const ch = path2[i];
2416
+ if (ch === "\\" && i + 1 < path2.length) {
2417
+ const next = path2[i + 1];
2418
+ segment += next;
2419
+ i += 2;
2420
+ continue;
2421
+ }
2422
+ if (ch === '"') {
2423
+ closed = true;
2424
+ i++;
2425
+ break;
2426
+ }
2427
+ segment += ch;
2428
+ i++;
2429
+ }
2430
+ if (!closed) throw new ConfigError(`unterminated quoted path segment: ${path2}`, path2);
2431
+ segments.push(segment);
2432
+ if (i < path2.length && path2[i] === ".") i++;
2433
+ } else {
2434
+ const dot = path2.indexOf(".", i);
2435
+ if (dot === -1) {
2436
+ segments.push(path2.slice(i));
2437
+ break;
2438
+ }
2439
+ segments.push(path2.slice(i, dot));
2440
+ i = dot + 1;
2441
+ }
2442
+ }
2443
+ return segments;
2444
+ }
2445
+ function scalarToJs(raw, valueType) {
2446
+ switch (valueType) {
2447
+ case "null":
2448
+ return null;
2449
+ case "boolean":
2450
+ return raw === "true";
2451
+ case "number":
2452
+ return Number(raw);
2453
+ case "string":
2454
+ return raw;
2455
+ }
2456
+ }
2457
+ function hoconToJs(v) {
2458
+ switch (v.kind) {
2459
+ case "scalar":
2460
+ return scalarToJs(v.raw, v.valueType);
2461
+ case "array":
2462
+ return v.items.map(hoconToJs);
2463
+ case "object": {
2464
+ const obj = /* @__PURE__ */ Object.create(null);
2465
+ for (const [k, val] of v.fields) obj[k] = hoconToJs(val);
2466
+ return Object.assign({}, obj);
2467
+ }
2468
+ }
2469
+ }
2470
+ function resObjToKeyShape(tree) {
2471
+ const fields = /* @__PURE__ */ new Map();
2472
+ for (const [k, v] of tree.fields) {
2473
+ if (isSubst(v) || isConcat(v) || isAppend(v)) {
2474
+ fields.set(k, { kind: "scalar", raw: "", valueType: "null" });
2475
+ } else if (isResObj(v)) {
2476
+ fields.set(k, resObjToKeyShape(v));
2477
+ } else {
2478
+ fields.set(k, v);
2479
+ }
2480
+ }
2481
+ return { kind: "object", fields };
2482
+ }
2483
+ function stripPlaceholderFields(v) {
2484
+ const fields = /* @__PURE__ */ new Map();
2485
+ let hadPlaceholders = false;
2486
+ for (const [k, val] of v.fields) {
2487
+ if (val._kind === "subst-placeholder" || val._kind === "concat-placeholder" || val._kind === "append-placeholder") {
2488
+ hadPlaceholders = true;
2489
+ } else if (val.kind === "object") {
2490
+ const inner = stripPlaceholderFields(val);
2491
+ if (inner.hadPlaceholders) hadPlaceholders = true;
2492
+ fields.set(k, inner.stripped);
2493
+ } else {
2494
+ fields.set(k, val);
2495
+ }
2496
+ }
2497
+ return { stripped: { kind: "object", fields }, hadPlaceholders };
2498
+ }
2499
+ function filterByReceiverShape(resolved, receiverShape) {
2500
+ const fields = /* @__PURE__ */ new Map();
2501
+ for (const [k, rv] of resolved.fields) {
2502
+ if (!receiverShape.fields.has(k)) continue;
2503
+ const receiverVal = receiverShape.fields.get(k);
2504
+ if (rv.kind === "object" && receiverVal.kind === "object") {
2505
+ fields.set(k, filterByReceiverShape(rv, receiverVal));
2506
+ } else {
2507
+ fields.set(k, rv);
2508
+ }
2509
+ }
2510
+ return { kind: "object", fields };
2511
+ }
2512
+ function renderHoconAsJSON(v) {
2513
+ switch (v.kind) {
2514
+ case "scalar": {
2515
+ switch (v.valueType) {
2516
+ case "null":
2517
+ return "null";
2518
+ case "boolean":
2519
+ return v.raw;
2520
+ case "number": {
2521
+ const n = Number(v.raw);
2522
+ if (Number.isFinite(n)) return v.raw;
2523
+ return JSON.stringify(v.raw);
2524
+ }
2525
+ case "string":
2526
+ return JSON.stringify(v.raw);
2527
+ }
2528
+ break;
2529
+ }
2530
+ case "array": {
2531
+ const items = v.items.map(renderHoconAsJSON);
2532
+ return `[${items.join(",")}]`;
2533
+ }
2534
+ case "object": {
2535
+ const keys = [...v.fields.keys()].sort();
2536
+ const pairs = keys.map((k) => {
2537
+ const val = v.fields.get(k);
2538
+ return `${JSON.stringify(k)}:${renderHoconAsJSON(val)}`;
2539
+ });
2540
+ return `{${pairs.join(",")}}`;
2541
+ }
2542
+ }
2543
+ throw new Error(`renderHoconAsJSON: unsupported value kind (unresolved placeholder?)`);
1692
2544
  }
1693
2545
 
1694
2546
  // src/parse.ts
1695
2547
  import * as fs from "fs";
1696
2548
  import * as path from "path";
2549
+ function defaultParseOptions() {
2550
+ return { resolveSubstitutions: true };
2551
+ }
2552
+ function defaultResolveOptions() {
2553
+ return { useSystemEnvironment: true, allowUnresolved: false };
2554
+ }
1697
2555
  function getEnv(opts) {
1698
2556
  if (opts.env !== void 0) return opts.env;
1699
2557
  if (typeof process !== "undefined" && process.env) return process.env;
@@ -1707,12 +2565,16 @@ async function defaultReadFile(filePath) {
1707
2565
  }
1708
2566
  function buildResolveContext(input, opts) {
1709
2567
  const tokens = tokenize(input);
2568
+ assertNonEmptyDocument(tokens, "input");
1710
2569
  const ast = parseTokens(tokens);
1711
2570
  const resolveOpts = {
1712
2571
  env: getEnv(opts),
1713
2572
  baseDir: opts.baseDir,
1714
2573
  readFileSync: opts.readFileSync ?? defaultReadFileSync,
1715
- readFile: opts.readFile
2574
+ readFile: opts.readFile,
2575
+ originDescription: opts.originDescription,
2576
+ resolveFrom: opts.resolveFrom,
2577
+ packageResolver: opts.packageResolver
1716
2578
  };
1717
2579
  return { ast, resolveOpts };
1718
2580
  }
@@ -1720,7 +2582,38 @@ function parse(input, opts = {}) {
1720
2582
  const { ast, resolveOpts } = buildResolveContext(input, opts);
1721
2583
  const value = resolve2(ast, resolveOpts);
1722
2584
  if (value.kind !== "object") throw new Error("resolved value is not an object");
1723
- return new Config(value);
2585
+ return new Config(value, { resolved: true, originDescription: opts.originDescription });
2586
+ }
2587
+ var parseString = parse;
2588
+ function parseStringWithOptions(input, opts = {}) {
2589
+ const resolveSubstitutions = opts.resolveSubstitutions ?? true;
2590
+ const { ast, resolveOpts } = buildResolveContext(input, opts);
2591
+ if (resolveSubstitutions) {
2592
+ const value = resolve2(ast, resolveOpts);
2593
+ if (value.kind !== "object") throw new Error("resolved value is not an object");
2594
+ return new Config(value, { resolved: true, originDescription: opts.originDescription });
2595
+ }
2596
+ const tree = buildTree(ast, resolveOpts);
2597
+ const hasPlaceholders = containsPlaceholders(tree);
2598
+ return Config._fromUnresolvedResObj(tree, {
2599
+ parseBaseDir: opts.baseDir,
2600
+ originDescription: opts.originDescription,
2601
+ resolved: !hasPlaceholders,
2602
+ resolveOpts
2603
+ });
2604
+ }
2605
+ function parseFileWithOptions(filePath, opts = {}) {
2606
+ if (typeof process === "undefined" && !opts.readFileSync) {
2607
+ throw new Error("parseFileWithOptions is not supported in browser environments without opts.readFileSync.");
2608
+ }
2609
+ const resolvedPath = path.resolve(filePath);
2610
+ const readFileSync2 = opts.readFileSync ?? defaultReadFileSync;
2611
+ const input = readFileSync2(resolvedPath);
2612
+ return parseStringWithOptions(input, {
2613
+ ...opts,
2614
+ baseDir: opts.baseDir ?? path.dirname(resolvedPath),
2615
+ readFileSync: readFileSync2
2616
+ });
1724
2617
  }
1725
2618
  async function parseAsync(input, opts = {}) {
1726
2619
  const { ast, resolveOpts } = buildResolveContext(input, opts);
@@ -1755,13 +2648,89 @@ async function parseFileAsync(filePath, opts = {}) {
1755
2648
  readFileSync: opts.readFileSync ?? defaultReadFileSync
1756
2649
  });
1757
2650
  }
2651
+
2652
+ // src/value-factory.ts
2653
+ function fromMap(values, originDescription) {
2654
+ const fields = coerceObject(values, "");
2655
+ const root = { kind: "object", fields };
2656
+ return Config._fromResolvedValue(root, { originDescription });
2657
+ }
2658
+ function empty(originDescription) {
2659
+ const root = { kind: "object", fields: /* @__PURE__ */ new Map() };
2660
+ return Config._fromResolvedValue(root, { originDescription });
2661
+ }
2662
+ function coerceObject(obj, atPath) {
2663
+ const fields = /* @__PURE__ */ new Map();
2664
+ for (const [k, v] of Object.entries(obj)) {
2665
+ const childPath = atPath ? `${atPath}.${k}` : k;
2666
+ fields.set(k, coerceValue(v, childPath));
2667
+ }
2668
+ return fields;
2669
+ }
2670
+ function coerceValue(v, atPath) {
2671
+ if (v === null) {
2672
+ return { kind: "scalar", raw: "null", valueType: "null" };
2673
+ }
2674
+ if (v === void 0) {
2675
+ throw new ConfigError(
2676
+ `fromMap: undefined is not a valid HOCON value at path "${atPath}"`,
2677
+ atPath
2678
+ );
2679
+ }
2680
+ switch (typeof v) {
2681
+ case "string":
2682
+ return { kind: "scalar", raw: v, valueType: "string" };
2683
+ case "number": {
2684
+ if (Number.isNaN(v) || !Number.isFinite(v)) {
2685
+ throw new ConfigError(
2686
+ `fromMap: ${v} is not representable in HOCON (NaN/Infinity not allowed) at path "${atPath}"`,
2687
+ atPath
2688
+ );
2689
+ }
2690
+ return { kind: "scalar", raw: String(v), valueType: "number" };
2691
+ }
2692
+ case "boolean":
2693
+ return { kind: "scalar", raw: v ? "true" : "false", valueType: "boolean" };
2694
+ case "bigint": {
2695
+ if (v > BigInt(Number.MAX_SAFE_INTEGER) || v < BigInt(Number.MIN_SAFE_INTEGER)) {
2696
+ throw new ConfigError(
2697
+ `fromMap: bigint ${v} exceeds Number.MAX_SAFE_INTEGER (not representable as HOCON number) at path "${atPath}"`,
2698
+ atPath
2699
+ );
2700
+ }
2701
+ return { kind: "scalar", raw: String(v), valueType: "number" };
2702
+ }
2703
+ case "object": {
2704
+ if (Array.isArray(v)) {
2705
+ const items = v.map((item, i) => coerceValue(item, `${atPath}[${i}]`));
2706
+ return { kind: "array", items };
2707
+ }
2708
+ const fields = coerceObject(v, atPath);
2709
+ return { kind: "object", fields };
2710
+ }
2711
+ default:
2712
+ throw new ConfigError(
2713
+ `fromMap: unsupported value type "${typeof v}" at path "${atPath}"`,
2714
+ atPath
2715
+ );
2716
+ }
2717
+ }
1758
2718
  export {
1759
2719
  Config,
1760
2720
  ConfigError,
2721
+ NotResolvedError,
2722
+ PackageLookupError,
1761
2723
  ParseError,
1762
2724
  ResolveError,
2725
+ defaultParseOptions,
2726
+ defaultResolveOptions,
2727
+ empty,
2728
+ fromMap,
1763
2729
  parse,
1764
2730
  parseAsync,
1765
2731
  parseFile,
1766
- parseFileAsync
2732
+ parseFileAsync,
2733
+ parseFileWithOptions,
2734
+ parseString,
2735
+ parseStringWithOptions
1767
2736
  };