@o3co/ts.hocon 1.11.0 → 1.13.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.
@@ -37,13 +37,22 @@ function coerceNumber(value) {
37
37
  return Number(value);
38
38
  }
39
39
  var DURATION_UNITS = {
40
+ // S19.1–S19.3: the bare nano/micro/milli (+plural) aliases are part of the
41
+ // spec's unit lists (HOCON.md L1307–L1309) and accepted by Lightbend
42
+ // (typesafe-config 1.4.6 probe, 2026-08-18); they were missing here.
40
43
  ns: 1e-6,
44
+ nano: 1e-6,
45
+ nanos: 1e-6,
41
46
  nanosecond: 1e-6,
42
47
  nanoseconds: 1e-6,
43
48
  us: 1e-3,
49
+ micro: 1e-3,
50
+ micros: 1e-3,
44
51
  microsecond: 1e-3,
45
52
  microseconds: 1e-3,
46
53
  ms: 1,
54
+ milli: 1,
55
+ millis: 1,
47
56
  millisecond: 1,
48
57
  milliseconds: 1,
49
58
  s: 1e3,
@@ -95,37 +104,76 @@ function parseDuration(value, outputUnit = "ms") {
95
104
  return ms / divisor;
96
105
  }
97
106
  var BYTE_UNITS = {
107
+ // S21.1–S21.4 — the EXACT Lightbend/typesafe-config unit set (1.4.6 probe,
108
+ // 2026-08-18). Multi-letter units are case-sensitive: the SI decimal short
109
+ // form is `kB` (NOT `KB`/`kb` — Lightbend rejects both), higher decimals
110
+ // are `MB`…`YB`, binary prefixes are `Ki`/`KiB`…`Yi`/`YiB` with capital
111
+ // first letter, and long forms are lowercase only (`kilobyte`, never
112
+ // `Kilobyte`). Only the bare byte unit (`B`/`b`) and the single-letter
113
+ // -Xmx forms accept both cases. The old case-insensitive fallback and the
114
+ // lowercase alias rows accepted spellings Lightbend rejects — removed.
98
115
  B: 1,
116
+ b: 1,
99
117
  byte: 1,
100
118
  bytes: 1,
101
- KB: 1e3,
119
+ kB: 1e3,
102
120
  kilobyte: 1e3,
103
121
  kilobytes: 1e3,
104
- KiB: 1024,
105
- kibibyte: 1024,
106
- kibibytes: 1024,
107
122
  MB: 1e6,
108
123
  megabyte: 1e6,
109
124
  megabytes: 1e6,
110
- MiB: 1048576,
111
- mebibyte: 1048576,
112
- mebibytes: 1048576,
113
125
  GB: 1e9,
114
126
  gigabyte: 1e9,
115
127
  gigabytes: 1e9,
116
- GiB: 1073741824,
117
- gibibyte: 1073741824,
118
- gibibytes: 1073741824,
119
128
  TB: 1e12,
120
129
  terabyte: 1e12,
121
130
  terabytes: 1e12,
131
+ PB: 1e15,
132
+ petabyte: 1e15,
133
+ petabytes: 1e15,
134
+ EB: 1e18,
135
+ exabyte: 1e18,
136
+ exabytes: 1e18,
137
+ ZB: 1e21,
138
+ zettabyte: 1e21,
139
+ zettabytes: 1e21,
140
+ YB: 1e24,
141
+ yottabyte: 1e24,
142
+ yottabytes: 1e24,
143
+ Ki: 1024,
144
+ KiB: 1024,
145
+ kibibyte: 1024,
146
+ kibibytes: 1024,
147
+ Mi: 1048576,
148
+ MiB: 1048576,
149
+ mebibyte: 1048576,
150
+ mebibytes: 1048576,
151
+ Gi: 1073741824,
152
+ GiB: 1073741824,
153
+ gibibyte: 1073741824,
154
+ gibibytes: 1073741824,
155
+ Ti: 1099511627776,
122
156
  TiB: 1099511627776,
123
157
  tebibyte: 1099511627776,
124
158
  tebibytes: 1099511627776,
125
- // S21.4 — HOCON.md L1385: single-letter abbreviations → powers of two (java -Xmx convention).
126
- // Lightbend typesafe-config 1.4.3 verified: 1K=1024, 1M=1048576, etc.
127
- // Both upper- and lowercase accepted per Lightbend behaviour.
128
- // Z/Y (2^70/2^80) are deferred — they overflow MAX_SAFE_INTEGER and require BigInt accessor.
159
+ Pi: 1024 ** 5,
160
+ PiB: 1024 ** 5,
161
+ pebibyte: 1024 ** 5,
162
+ pebibytes: 1024 ** 5,
163
+ Ei: 1024 ** 6,
164
+ EiB: 1024 ** 6,
165
+ exbibyte: 1024 ** 6,
166
+ exbibytes: 1024 ** 6,
167
+ Zi: 1024 ** 7,
168
+ ZiB: 1024 ** 7,
169
+ zebibyte: 1024 ** 7,
170
+ zebibytes: 1024 ** 7,
171
+ Yi: 1024 ** 8,
172
+ YiB: 1024 ** 8,
173
+ yobibyte: 1024 ** 8,
174
+ yobibytes: 1024 ** 8,
175
+ // S21.4 — single-letter abbreviations → powers of two (java -Xmx
176
+ // convention); Lightbend accepts BOTH cases here, through the full ladder.
129
177
  K: 1024,
130
178
  k: 1024,
131
179
  M: 1024 ** 2,
@@ -138,16 +186,10 @@ var BYTE_UNITS = {
138
186
  p: 1024 ** 5,
139
187
  E: 1024 ** 6,
140
188
  e: 1024 ** 6,
141
- // lowercase short-form aliases
142
- b: 1,
143
- kb: 1e3,
144
- kib: 1024,
145
- mb: 1e6,
146
- mib: 1048576,
147
- gb: 1e9,
148
- gib: 1073741824,
149
- tb: 1e12,
150
- tib: 1099511627776
189
+ Z: 1024 ** 7,
190
+ z: 1024 ** 7,
191
+ Y: 1024 ** 8,
192
+ y: 1024 ** 8
151
193
  };
152
194
  var OUTPUT_BYTE_UNITS = {
153
195
  B: 1,
@@ -185,10 +227,7 @@ function parseBytes(value, outputUnit = "B") {
185
227
  }
186
228
  return bytes2 / divisor;
187
229
  }
188
- let mult = BYTE_UNITS[unit];
189
- if (mult === void 0) {
190
- mult = BYTE_UNITS[unit.toLowerCase()];
191
- }
230
+ const mult = BYTE_UNITS[unit];
192
231
  if (mult === void 0) return NaN;
193
232
  const bytes = num * mult;
194
233
  if (Math.abs(bytes) > Number.MAX_SAFE_INTEGER) {
@@ -235,35 +274,114 @@ function stringSegmentsToKey(segments) {
235
274
  return t;
236
275
  }).join(".");
237
276
  }
277
+ function prefixSelfRefRemainder(s, fullKey) {
278
+ const texts = s.segments.map((seg) => seg.text);
279
+ if (!stringSegmentsToKey(texts).startsWith(fullKey + ".")) return null;
280
+ for (let n = 1; n < texts.length; n++) {
281
+ if (stringSegmentsToKey(texts.slice(0, n)) === fullKey) return texts.slice(n);
282
+ }
283
+ return null;
284
+ }
285
+ function navigateResolverValue(v, remainder) {
286
+ let cur = v;
287
+ for (const seg of remainder) {
288
+ if (isSubst(cur) || isConcat(cur)) return null;
289
+ if (isResObj(cur)) {
290
+ const next = cur.fields.get(seg);
291
+ if (next === void 0) return void 0;
292
+ cur = next;
293
+ continue;
294
+ }
295
+ const hv = cur;
296
+ if (hv.kind === "object") {
297
+ const next = hv.fields.get(seg);
298
+ if (next === void 0) return void 0;
299
+ cur = next;
300
+ continue;
301
+ }
302
+ return void 0;
303
+ }
304
+ return cur;
305
+ }
306
+ function foldPrefixSelfRef(s, replacement, remainder) {
307
+ const navigated = navigateResolverValue(replacement, remainder);
308
+ if (navigated === null) return s;
309
+ if (navigated === void 0) {
310
+ return {
311
+ _kind: "subst-placeholder",
312
+ segments: s.segments.map((seg) => ({ text: seg.text, line: seg.line, col: seg.col })),
313
+ optional: s.optional,
314
+ knownAbsent: true,
315
+ listSuffix: s.listSuffix,
316
+ line: s.line,
317
+ col: s.col,
318
+ prefixLen: s.prefixLen
319
+ };
320
+ }
321
+ return cloneResolverValue(navigated);
322
+ }
323
+ function mergeResObjLayers(base, top) {
324
+ const fields = new Map(base.fields);
325
+ for (const [k, tv] of top.fields) {
326
+ const bv = fields.get(k);
327
+ if (bv !== void 0 && isResObj(bv) && isResObj(tv)) {
328
+ fields.set(k, mergeResObjLayers(bv, tv));
329
+ } else {
330
+ fields.set(k, tv);
331
+ }
332
+ }
333
+ const priorValues = new Map(base.priorValues);
334
+ for (const [k, pv] of top.priorValues) priorValues.set(k, pv);
335
+ const resetKeys = new Set(base.resetKeys);
336
+ for (const k of top.resetKeys) resetKeys.add(k);
337
+ return {
338
+ _kind: "res-obj",
339
+ fields,
340
+ priorValues,
341
+ resetKeys
342
+ };
343
+ }
238
344
  function containsSelfRef(v, fullKey) {
239
- if (isSubst(v)) return !v.knownAbsent && substFullKey(v) === fullKey;
240
- if (isConcat(v)) return v.nodes.some((n) => containsSelfRef(n, fullKey));
345
+ return containsSelfRefInner(v, fullKey, true);
346
+ }
347
+ function containsSelfRefInner(v, fullKey, allowPrefix) {
348
+ if (isSubst(v))
349
+ return !v.knownAbsent && (substFullKey(v) === fullKey || allowPrefix && prefixSelfRefRemainder(v, fullKey) !== null);
350
+ if (isConcat(v)) return v.nodes.some((n) => containsSelfRefInner(n, fullKey, allowPrefix));
241
351
  if (isResObj(v)) {
242
352
  for (const f of v.fields.values()) {
243
- if (containsSelfRef(f, fullKey)) return true;
353
+ if (containsSelfRefInner(f, fullKey, false)) return true;
244
354
  }
245
355
  return false;
246
356
  }
247
357
  const hv = v;
248
358
  if (hv.kind === "array") {
249
- return hv.items.some((item) => containsSelfRef(item, fullKey));
359
+ return hv.items.some((item) => containsSelfRefInner(item, fullKey, allowPrefix));
250
360
  }
251
361
  if (hv.kind === "object") {
252
362
  for (const f of hv.fields.values()) {
253
- if (containsSelfRef(f, fullKey)) return true;
363
+ if (containsSelfRefInner(f, fullKey, false)) return true;
254
364
  }
255
365
  return false;
256
366
  }
257
367
  return false;
258
368
  }
259
369
  function foldSelfRef(v, fullKey, replacement) {
370
+ return foldSelfRefInner(v, fullKey, replacement, true);
371
+ }
372
+ function foldSelfRefInner(v, fullKey, replacement, allowPrefix) {
260
373
  if (isSubst(v)) {
261
- return substFullKey(v) === fullKey ? replacement : v;
374
+ if (substFullKey(v) === fullKey) return replacement;
375
+ if (allowPrefix) {
376
+ const remainder = prefixSelfRefRemainder(v, fullKey);
377
+ if (remainder !== null) return foldPrefixSelfRef(v, replacement, remainder);
378
+ }
379
+ return v;
262
380
  }
263
381
  if (isConcat(v)) {
264
382
  return {
265
383
  _kind: "concat-placeholder",
266
- nodes: v.nodes.map((n) => foldSelfRef(n, fullKey, replacement)),
384
+ nodes: v.nodes.map((n) => foldSelfRefInner(n, fullKey, replacement, allowPrefix)),
267
385
  line: v.line,
268
386
  col: v.col
269
387
  };
@@ -271,7 +389,7 @@ function foldSelfRef(v, fullKey, replacement) {
271
389
  if (isResObj(v)) {
272
390
  const newFields = /* @__PURE__ */ new Map();
273
391
  for (const [k, val] of v.fields) {
274
- newFields.set(k, foldSelfRef(val, fullKey, replacement));
392
+ newFields.set(k, foldSelfRefInner(val, fullKey, replacement, false));
275
393
  }
276
394
  return { _kind: "res-obj", fields: newFields, priorValues: new Map(v.priorValues), resetKeys: new Set(v.resetKeys) };
277
395
  }
@@ -279,13 +397,15 @@ function foldSelfRef(v, fullKey, replacement) {
279
397
  if (hv.kind === "array") {
280
398
  return {
281
399
  kind: "array",
282
- items: hv.items.map((item) => foldSelfRef(item, fullKey, replacement))
400
+ items: hv.items.map(
401
+ (item) => foldSelfRefInner(item, fullKey, replacement, allowPrefix)
402
+ )
283
403
  };
284
404
  }
285
405
  if (hv.kind === "object") {
286
406
  const newFields = /* @__PURE__ */ new Map();
287
407
  for (const [k, val] of hv.fields) {
288
- newFields.set(k, foldSelfRef(val, fullKey, replacement));
408
+ newFields.set(k, foldSelfRefInner(val, fullKey, replacement, false));
289
409
  }
290
410
  return { kind: "object", fields: newFields };
291
411
  }
@@ -294,10 +414,26 @@ function foldSelfRef(v, fullKey, replacement) {
294
414
  function foldOrSkipPrior(prior, fullKey, old) {
295
415
  if (!containsSelfRef(prior, fullKey)) return cloneResolverValue(prior);
296
416
  if (old === void 0) return foldOptionalSelfRefAbsent(prior, fullKey);
417
+ if (isSubst(prior)) {
418
+ const remainder = prefixSelfRefRemainder(prior, fullKey);
419
+ if (remainder !== null) {
420
+ const navigated = navigateResolverValue(old, remainder);
421
+ if (navigated === void 0 && prior.optional) return cloneResolverValue(old);
422
+ if (navigated !== null && navigated !== void 0 && isResObj(navigated) && isResObj(old)) {
423
+ return mergeResObjLayers(
424
+ cloneResolverValue(old),
425
+ cloneResolverValue(navigated)
426
+ );
427
+ }
428
+ }
429
+ }
297
430
  return foldSelfRef(prior, fullKey, old);
298
431
  }
299
432
  function foldOptionalSelfRefAbsent(v, fullKey) {
300
- if (isSubst(v) && substFullKey(v) === fullKey) {
433
+ return foldOptionalSelfRefAbsentInner(v, fullKey, true);
434
+ }
435
+ function foldOptionalSelfRefAbsentInner(v, fullKey, allowPrefix) {
436
+ if (isSubst(v) && (substFullKey(v) === fullKey || allowPrefix && prefixSelfRefRemainder(v, fullKey) !== null)) {
301
437
  if (!v.optional) return void 0;
302
438
  return {
303
439
  _kind: "subst-placeholder",
@@ -313,7 +449,7 @@ function foldOptionalSelfRefAbsent(v, fullKey) {
313
449
  if (isConcat(v)) {
314
450
  const nodes = [];
315
451
  for (const node of v.nodes) {
316
- const folded = foldOptionalSelfRefAbsent(node, fullKey);
452
+ const folded = foldOptionalSelfRefAbsentInner(node, fullKey, allowPrefix);
317
453
  if (folded === void 0) return void 0;
318
454
  nodes.push(folded);
319
455
  }
@@ -322,7 +458,7 @@ function foldOptionalSelfRefAbsent(v, fullKey) {
322
458
  if (isResObj(v)) {
323
459
  const fields = /* @__PURE__ */ new Map();
324
460
  for (const [key, value] of v.fields) {
325
- const folded = foldOptionalSelfRefAbsent(value, fullKey);
461
+ const folded = foldOptionalSelfRefAbsentInner(value, fullKey, false);
326
462
  if (folded === void 0) return void 0;
327
463
  fields.set(key, folded);
328
464
  }
@@ -332,7 +468,7 @@ function foldOptionalSelfRefAbsent(v, fullKey) {
332
468
  if (hv.kind === "array") {
333
469
  const items = [];
334
470
  for (const item of hv.items) {
335
- const folded = foldOptionalSelfRefAbsent(item, fullKey);
471
+ const folded = foldOptionalSelfRefAbsentInner(item, fullKey, allowPrefix);
336
472
  if (folded === void 0) return void 0;
337
473
  items.push(folded);
338
474
  }
@@ -341,7 +477,7 @@ function foldOptionalSelfRefAbsent(v, fullKey) {
341
477
  if (hv.kind === "object") {
342
478
  const fields = /* @__PURE__ */ new Map();
343
479
  for (const [key, value] of hv.fields) {
344
- const folded = foldOptionalSelfRefAbsent(value, fullKey);
480
+ const folded = foldOptionalSelfRefAbsentInner(value, fullKey, false);
345
481
  if (folded === void 0) return void 0;
346
482
  fields.set(key, folded);
347
483
  }
@@ -523,6 +659,27 @@ function stripBom(input) {
523
659
  return input.charCodeAt(0) === 65279 ? input.slice(1) : input;
524
660
  }
525
661
 
662
+ // src/internal/depth.ts
663
+ function guardStackDepth(fn, wrap) {
664
+ try {
665
+ return fn();
666
+ } catch (e) {
667
+ if (!isStackOverflow(e)) throw e;
668
+ throw wrap(STACK_MESSAGE);
669
+ }
670
+ }
671
+ var STACK_MESSAGE = "input is nested too deeply for this engine's stack, or contains a cycle (flatten the document, or break the cycle)";
672
+ function isStackOverflow(e) {
673
+ if (!(e instanceof RangeError)) return false;
674
+ const m = e.message.toLowerCase();
675
+ return m.includes("call stack") || // V8: "Maximum call stack size exceeded"
676
+ m.includes("recursion") || // SpiderMonkey: "too much recursion"
677
+ m.includes("stack overflow");
678
+ }
679
+ function depthError(message) {
680
+ return new ConfigError(message, "");
681
+ }
682
+
526
683
  // src/value/numeric-array.ts
527
684
  function numericObjectToArray(value) {
528
685
  if (value.kind !== "object") return null;
@@ -577,6 +734,12 @@ var SubstitutionResolver = class {
577
734
  // Cross-impl with rs.hocon v1.5.1's resolving_field_path (same purpose,
578
735
  // same prefix-match semantics).
579
736
  this.resolvingFieldPath = [];
737
+ // S13a.12 recursion guard: field keys whose prior is currently being
738
+ // resolved through the prefix-self-ref branch. Saved priors are
739
+ // prefix-self-ref-free by the fold invariant, so re-entry only happens on
740
+ // a shape the fold could not see through (a live subst mid-navigation) —
741
+ // report it as a cycle instead of recursing forever.
742
+ this.resolvingPrefixPriors = /* @__PURE__ */ new Set();
580
743
  }
581
744
  /** Returns a prefix for error messages, including origin info when set. */
582
745
  originPrefix() {
@@ -591,46 +754,57 @@ var SubstitutionResolver = class {
591
754
  this.resolvingFieldPath.push(key);
592
755
  const fullCacheKey = stringSegmentsToKey(this.resolvingFieldPath);
593
756
  this.cache.delete(fullCacheKey);
594
- let resolved;
595
757
  try {
596
- resolved = this.resolveVal(val, obj);
597
- } finally {
598
- this.resolvingFieldPath.pop();
599
- }
600
- if (resolved !== void 0) {
601
- if (resolved.kind === "object") {
758
+ const resolved = this.resolveVal(val, obj);
759
+ if (resolved !== void 0) {
760
+ if (resolved.kind === "object") {
761
+ const prior = obj.priorValues.get(key);
762
+ if (prior !== void 0) {
763
+ const priorResolved = this.resolveVal(prior, obj);
764
+ if (priorResolved !== void 0 && priorResolved.kind === "object") {
765
+ const finalValue = deepMergeHoconValues(
766
+ priorResolved,
767
+ resolved
768
+ );
769
+ result.set(key, finalValue);
770
+ this.cache.set(fullCacheKey, finalValue);
771
+ this.cacheDescendants(fullCacheKey, finalValue);
772
+ continue;
773
+ }
774
+ }
775
+ }
776
+ result.set(key, resolved);
777
+ this.cache.set(fullCacheKey, resolved);
778
+ this.cacheDescendants(fullCacheKey, resolved);
779
+ } else {
602
780
  const prior = obj.priorValues.get(key);
603
781
  if (prior !== void 0) {
604
782
  const priorResolved = this.resolveVal(prior, obj);
605
- if (priorResolved !== void 0 && priorResolved.kind === "object") {
606
- const finalValue = deepMergeHoconValues(
607
- priorResolved,
608
- resolved
609
- );
610
- result.set(key, finalValue);
611
- this.cache.set(fullCacheKey, finalValue);
612
- this.cacheDescendants(fullCacheKey, finalValue);
613
- continue;
783
+ if (priorResolved !== void 0) {
784
+ result.set(key, priorResolved);
785
+ this.cache.set(fullCacheKey, priorResolved);
786
+ this.cacheDescendants(fullCacheKey, priorResolved);
614
787
  }
615
788
  }
616
789
  }
617
- result.set(key, resolved);
618
- this.cache.set(fullCacheKey, resolved);
619
- this.cacheDescendants(fullCacheKey, resolved);
620
- } else {
621
- const prior = obj.priorValues.get(key);
622
- if (prior !== void 0) {
623
- const priorResolved = this.resolveVal(prior, obj);
624
- if (priorResolved !== void 0) {
625
- result.set(key, priorResolved);
626
- this.cache.set(fullCacheKey, priorResolved);
627
- this.cacheDescendants(fullCacheKey, priorResolved);
628
- }
629
- }
790
+ } finally {
791
+ this.resolvingFieldPath.pop();
630
792
  }
631
793
  }
632
794
  return { kind: "object", fields: result };
633
795
  }
796
+ /** S13a.12: walk resolved-object fields by segment text. A missing segment
797
+ * or a walk into a scalar/array is path-absent → undefined. */
798
+ navigateResolved(v, remainder) {
799
+ let cur = v;
800
+ for (const seg of remainder) {
801
+ if (cur.kind !== "object") return void 0;
802
+ const next = cur.fields.get(seg);
803
+ if (next === void 0) return void 0;
804
+ cur = next;
805
+ }
806
+ return cur;
807
+ }
634
808
  cacheDescendants(prefix, value) {
635
809
  if (value.kind !== "object") return;
636
810
  for (const [key, child] of value.fields) {
@@ -653,17 +827,59 @@ var SubstitutionResolver = class {
653
827
  if (isResObj(v)) return this.resolveResObj(v);
654
828
  const hv = v;
655
829
  if (hv.kind === "array") {
656
- return {
657
- kind: "array",
658
- items: hv.items.map(
659
- (item) => this.resolveVal(item, scope) ?? { kind: "scalar", raw: "null", valueType: "null" }
660
- )
661
- };
830
+ const items = [];
831
+ for (const item of hv.items) {
832
+ const resolved = this.resolveVal(item, scope);
833
+ if (resolved === void 0) continue;
834
+ items.push(resolved);
835
+ }
836
+ return { kind: "array", items };
662
837
  }
663
838
  return hv;
664
839
  }
665
840
  resolveSubst(s, scope) {
666
- if (s.knownAbsent) return void 0;
841
+ if (s.knownAbsent) {
842
+ if (!s.optional) {
843
+ if (this.opts.allowUnresolved) return s;
844
+ const k = s.listSuffix ? `${segmentsToKey(s.segments)}[]` : segmentsToKey(s.segments);
845
+ throw new ResolveError(
846
+ `${this.originPrefix()}could not resolve substitution: \${${k}}`,
847
+ k,
848
+ s.line,
849
+ s.col
850
+ );
851
+ }
852
+ return void 0;
853
+ }
854
+ {
855
+ const rfp = this.resolvingFieldPath;
856
+ const sTexts = s.segments.map((seg) => seg.text);
857
+ if (rfp.length > 0 && rfp.length < sTexts.length && stringSegmentsToKey(sTexts.slice(0, rfp.length)) === stringSegmentsToKey(rfp)) {
858
+ const guardKey = stringSegmentsToKey(rfp);
859
+ const prior = scope.priorValues.get(rfp[rfp.length - 1]);
860
+ if (prior !== void 0 && !this.resolvingPrefixPriors.has(guardKey)) {
861
+ this.resolvingPrefixPriors.add(guardKey);
862
+ try {
863
+ const priorResolved = this.resolveVal(prior, scope);
864
+ if (priorResolved !== void 0) {
865
+ const navigated = this.navigateResolved(priorResolved, sTexts.slice(rfp.length));
866
+ if (navigated !== void 0) return navigated;
867
+ }
868
+ } finally {
869
+ this.resolvingPrefixPriors.delete(guardKey);
870
+ }
871
+ }
872
+ if (s.optional) return void 0;
873
+ if (this.opts.allowUnresolved) return s;
874
+ const k = s.listSuffix ? `${segmentsToKey(s.segments)}[]` : segmentsToKey(s.segments);
875
+ throw new ResolveError(
876
+ `${this.originPrefix()}could not resolve substitution: \${${k}}`,
877
+ k,
878
+ s.line,
879
+ s.col
880
+ );
881
+ }
882
+ }
667
883
  const key = s.listSuffix ? `${segmentsToKey(s.segments)}[]` : segmentsToKey(s.segments);
668
884
  if (this.cache.has(key)) return this.cache.get(key);
669
885
  if (this.resolving.has(key)) {
@@ -983,6 +1199,121 @@ function hoconValueToResObj2(value) {
983
1199
  return obj;
984
1200
  }
985
1201
 
1202
+ // src/render-hocon.ts
1203
+ function renderHoconFromRoot(root) {
1204
+ const out = [];
1205
+ renderObjectBody(out, root, 0);
1206
+ return out.join("");
1207
+ }
1208
+ function renderObjectBody(out, o, depth) {
1209
+ const indent = " ".repeat(depth);
1210
+ for (const [k, v] of o.fields) {
1211
+ out.push(indent, renderKey(k));
1212
+ if (v.kind === "object") {
1213
+ out.push(" {");
1214
+ if (v.fields.size === 0) {
1215
+ out.push("}\n");
1216
+ continue;
1217
+ }
1218
+ out.push("\n");
1219
+ renderObjectBody(out, v, depth + 1);
1220
+ out.push(indent, "}\n");
1221
+ } else {
1222
+ out.push(" = ");
1223
+ renderValue(out, v, depth);
1224
+ out.push("\n");
1225
+ }
1226
+ }
1227
+ }
1228
+ function renderValue(out, v, depth) {
1229
+ switch (v.kind) {
1230
+ case "object": {
1231
+ if (v.fields.size === 0) {
1232
+ out.push("{}");
1233
+ return;
1234
+ }
1235
+ out.push("{\n");
1236
+ renderObjectBody(out, v, depth + 1);
1237
+ out.push(" ".repeat(depth), "}");
1238
+ return;
1239
+ }
1240
+ case "array":
1241
+ renderArray(out, v, depth);
1242
+ return;
1243
+ case "scalar":
1244
+ out.push(renderScalar(v));
1245
+ return;
1246
+ default:
1247
+ throw new ConfigError(
1248
+ "renderHocon: unrenderable value (config must be resolved data)",
1249
+ ""
1250
+ );
1251
+ }
1252
+ }
1253
+ function renderArray(out, a, depth) {
1254
+ if (a.items.length === 0) {
1255
+ out.push("[]");
1256
+ return;
1257
+ }
1258
+ const inner = " ".repeat(depth + 1);
1259
+ out.push("[\n");
1260
+ for (const e of a.items) {
1261
+ out.push(inner);
1262
+ renderValue(out, e, depth + 1);
1263
+ out.push("\n");
1264
+ }
1265
+ out.push(" ".repeat(depth), "]");
1266
+ }
1267
+ function renderScalar(s) {
1268
+ switch (s.valueType) {
1269
+ case "null":
1270
+ return "null";
1271
+ case "boolean":
1272
+ case "number":
1273
+ return s.raw;
1274
+ case "string":
1275
+ return renderString(s.raw);
1276
+ }
1277
+ }
1278
+ var SAFE_UNQUOTED_KEY = /^[A-Za-z0-9_-]+$/;
1279
+ function renderKey(k) {
1280
+ if (k === "include") return quoteString(k);
1281
+ return SAFE_UNQUOTED_KEY.test(k) ? k : quoteString(k);
1282
+ }
1283
+ var SAFE_BARE_STRING = /^[A-Za-z][A-Za-z0-9_-]*$/;
1284
+ var STRING_KEYWORDS = /* @__PURE__ */ new Set(["true", "false", "null", "yes", "no", "on", "off"]);
1285
+ function renderString(s) {
1286
+ return SAFE_BARE_STRING.test(s) && !STRING_KEYWORDS.has(s.toLowerCase()) ? s : quoteString(s);
1287
+ }
1288
+ function quoteString(s) {
1289
+ if (s.includes("\n") && !s.includes("\r") && !s.includes('"""') && !s.endsWith('"')) {
1290
+ return `"""${s}"""`;
1291
+ }
1292
+ let b = '"';
1293
+ for (const ch of s) {
1294
+ switch (ch) {
1295
+ case '"':
1296
+ b += '\\"';
1297
+ break;
1298
+ case "\\":
1299
+ b += "\\\\";
1300
+ break;
1301
+ case "\n":
1302
+ b += "\\n";
1303
+ break;
1304
+ case "\r":
1305
+ b += "\\r";
1306
+ break;
1307
+ case " ":
1308
+ b += "\\t";
1309
+ break;
1310
+ default:
1311
+ b += ch;
1312
+ }
1313
+ }
1314
+ return b + '"';
1315
+ }
1316
+
986
1317
  // src/config.ts
987
1318
  var Config = class _Config {
988
1319
  constructor(root, opts) {
@@ -1288,6 +1619,36 @@ var Config = class _Config {
1288
1619
  toObject(opts) {
1289
1620
  return hoconToJs(this.root, opts?.nullPrototype === true);
1290
1621
  }
1622
+ /**
1623
+ * Renders this resolved Config as HOCON text (E18; lockstep with go.hocon's
1624
+ * `Config.RenderHOCON`, v1.11.0).
1625
+ *
1626
+ * The output round-trips: parsing it back yields the same value tree. That
1627
+ * is the correctness contract, not byte-for-byte formatting — a scalar is
1628
+ * quoted whenever leaving it bare would re-parse as a different type (a
1629
+ * string `"8080"` becomes `"8080"`, not `8080`), and left bare only when it
1630
+ * provably cannot.
1631
+ *
1632
+ * The Config must be resolved and hold only data (objects, arrays, string /
1633
+ * number / boolean / null scalars) — exactly what {@link fromMap} and the
1634
+ * format adapters produce. An unresolved placeholder throws
1635
+ * {@link ConfigError}; substitutions have no textual round trip through a
1636
+ * value tree.
1637
+ *
1638
+ * The root object's fields are emitted without enclosing braces, nested
1639
+ * objects as `key { … }`, arrays as newline-separated `[ … ]`, indented two
1640
+ * spaces. Source comments are not represented — a value tree does not carry
1641
+ * them.
1642
+ */
1643
+ renderHocon() {
1644
+ if (!this._resolved) {
1645
+ throw new ConfigError(
1646
+ "renderHocon: unrenderable unresolved substitution (config must be resolved data)",
1647
+ ""
1648
+ );
1649
+ }
1650
+ return renderHoconFromRoot(this.root);
1651
+ }
1291
1652
  /**
1292
1653
  * @internal Test-only: renders this resolved Config's value tree as canonical
1293
1654
  * JSON (sorted keys, no whitespace). Used by Layer-2 fixture tests to compare
@@ -1501,7 +1862,10 @@ function normalizeLeadingZeroNumber(raw) {
1501
1862
 
1502
1863
  // src/value-factory.ts
1503
1864
  function fromMap(values, originDescription) {
1504
- const fields = coerceObject(values, "");
1865
+ const fields = guardStackDepth(
1866
+ () => coerceObject(values, ""),
1867
+ (msg) => depthError(`fromMap: ${msg}`)
1868
+ );
1505
1869
  const root = { kind: "object", fields };
1506
1870
  return Config._fromResolvedValue(root, { originDescription });
1507
1871
  }
@@ -1578,11 +1942,14 @@ function parseYaml(input, originDescription) {
1578
1942
  if (doc.errors.length > 0) {
1579
1943
  throw new ConfigError(`yaml: ${doc.errors[0]?.message ?? "parse error"}`, "");
1580
1944
  }
1581
- return fromYamlValue(doc.toJS({ mapAsMap: false }), originDescription);
1945
+ return fromYamlValue(doc.toJS({ mapAsMap: true }), originDescription);
1582
1946
  }
1583
1947
  function fromYamlValue(value, originDescription) {
1584
1948
  if (value === null || value === void 0) return fromMap({}, originDescription);
1585
- const normalized = convert(value, "");
1949
+ const normalized = guardStackDepth(
1950
+ () => convert(value, ""),
1951
+ (msg) => depthError(`yaml: ${msg}`)
1952
+ );
1586
1953
  if (typeof normalized !== "object" || normalized === null || Array.isArray(normalized)) {
1587
1954
  throw new ConfigError(
1588
1955
  `yaml: document root is ${Array.isArray(normalized) ? "an array" : typeof normalized}, but a config root must be an object (spec F0.3)`,
@@ -1598,6 +1965,7 @@ function convert(v, atPath) {
1598
1965
  }
1599
1966
  if (v instanceof Map) {
1600
1967
  const entries = [];
1968
+ const seen = /* @__PURE__ */ new Map();
1601
1969
  for (const [k, e] of v) {
1602
1970
  if (k !== null && typeof k === "object") {
1603
1971
  throw new ConfigError(
@@ -1606,6 +1974,13 @@ function convert(v, atPath) {
1606
1974
  );
1607
1975
  }
1608
1976
  const key = String(k);
1977
+ if (seen.has(key)) {
1978
+ throw new ConfigError(
1979
+ `yaml: sibling mapping keys ${keyForm(seen.get(key))} and ${keyForm(k)} both give the key "${key}"${atPath === "" ? "" : ` at "${atPath}"`}; rename one of them, because one of the two values would otherwise be lost. Quoting a non-string key helps only where that changes the key text, as 0x10 does and 1 does not (spec F5.3)`,
1980
+ atPath
1981
+ );
1982
+ }
1983
+ seen.set(key, k);
1609
1984
  entries.push([key, convert(e, atPath === "" ? key : `${atPath}.${key}`)]);
1610
1985
  }
1611
1986
  return Object.fromEntries(entries);
@@ -1639,6 +2014,10 @@ function bytesToBase64(bytes) {
1639
2014
  }
1640
2015
  return btoa(binary);
1641
2016
  }
2017
+ function keyForm(k) {
2018
+ if (typeof k === "string") return JSON.stringify(k);
2019
+ return `${String(k)} (${k === null ? "null" : typeof k})`;
2020
+ }
1642
2021
  var MAX_SAFE = BigInt(Number.MAX_SAFE_INTEGER);
1643
2022
  var MIN_SAFE = BigInt(Number.MIN_SAFE_INTEGER);
1644
2023
  function isSafeIntegerBigInt(v) {