@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.
@@ -72,13 +72,22 @@ function coerceNumber(value) {
72
72
  return Number(value);
73
73
  }
74
74
  var DURATION_UNITS = {
75
+ // S19.1–S19.3: the bare nano/micro/milli (+plural) aliases are part of the
76
+ // spec's unit lists (HOCON.md L1307–L1309) and accepted by Lightbend
77
+ // (typesafe-config 1.4.6 probe, 2026-08-18); they were missing here.
75
78
  ns: 1e-6,
79
+ nano: 1e-6,
80
+ nanos: 1e-6,
76
81
  nanosecond: 1e-6,
77
82
  nanoseconds: 1e-6,
78
83
  us: 1e-3,
84
+ micro: 1e-3,
85
+ micros: 1e-3,
79
86
  microsecond: 1e-3,
80
87
  microseconds: 1e-3,
81
88
  ms: 1,
89
+ milli: 1,
90
+ millis: 1,
82
91
  millisecond: 1,
83
92
  milliseconds: 1,
84
93
  s: 1e3,
@@ -130,37 +139,76 @@ function parseDuration(value, outputUnit = "ms") {
130
139
  return ms / divisor;
131
140
  }
132
141
  var BYTE_UNITS = {
142
+ // S21.1–S21.4 — the EXACT Lightbend/typesafe-config unit set (1.4.6 probe,
143
+ // 2026-08-18). Multi-letter units are case-sensitive: the SI decimal short
144
+ // form is `kB` (NOT `KB`/`kb` — Lightbend rejects both), higher decimals
145
+ // are `MB`…`YB`, binary prefixes are `Ki`/`KiB`…`Yi`/`YiB` with capital
146
+ // first letter, and long forms are lowercase only (`kilobyte`, never
147
+ // `Kilobyte`). Only the bare byte unit (`B`/`b`) and the single-letter
148
+ // -Xmx forms accept both cases. The old case-insensitive fallback and the
149
+ // lowercase alias rows accepted spellings Lightbend rejects — removed.
133
150
  B: 1,
151
+ b: 1,
134
152
  byte: 1,
135
153
  bytes: 1,
136
- KB: 1e3,
154
+ kB: 1e3,
137
155
  kilobyte: 1e3,
138
156
  kilobytes: 1e3,
139
- KiB: 1024,
140
- kibibyte: 1024,
141
- kibibytes: 1024,
142
157
  MB: 1e6,
143
158
  megabyte: 1e6,
144
159
  megabytes: 1e6,
145
- MiB: 1048576,
146
- mebibyte: 1048576,
147
- mebibytes: 1048576,
148
160
  GB: 1e9,
149
161
  gigabyte: 1e9,
150
162
  gigabytes: 1e9,
151
- GiB: 1073741824,
152
- gibibyte: 1073741824,
153
- gibibytes: 1073741824,
154
163
  TB: 1e12,
155
164
  terabyte: 1e12,
156
165
  terabytes: 1e12,
166
+ PB: 1e15,
167
+ petabyte: 1e15,
168
+ petabytes: 1e15,
169
+ EB: 1e18,
170
+ exabyte: 1e18,
171
+ exabytes: 1e18,
172
+ ZB: 1e21,
173
+ zettabyte: 1e21,
174
+ zettabytes: 1e21,
175
+ YB: 1e24,
176
+ yottabyte: 1e24,
177
+ yottabytes: 1e24,
178
+ Ki: 1024,
179
+ KiB: 1024,
180
+ kibibyte: 1024,
181
+ kibibytes: 1024,
182
+ Mi: 1048576,
183
+ MiB: 1048576,
184
+ mebibyte: 1048576,
185
+ mebibytes: 1048576,
186
+ Gi: 1073741824,
187
+ GiB: 1073741824,
188
+ gibibyte: 1073741824,
189
+ gibibytes: 1073741824,
190
+ Ti: 1099511627776,
157
191
  TiB: 1099511627776,
158
192
  tebibyte: 1099511627776,
159
193
  tebibytes: 1099511627776,
160
- // S21.4 — HOCON.md L1385: single-letter abbreviations → powers of two (java -Xmx convention).
161
- // Lightbend typesafe-config 1.4.3 verified: 1K=1024, 1M=1048576, etc.
162
- // Both upper- and lowercase accepted per Lightbend behaviour.
163
- // Z/Y (2^70/2^80) are deferred — they overflow MAX_SAFE_INTEGER and require BigInt accessor.
194
+ Pi: 1024 ** 5,
195
+ PiB: 1024 ** 5,
196
+ pebibyte: 1024 ** 5,
197
+ pebibytes: 1024 ** 5,
198
+ Ei: 1024 ** 6,
199
+ EiB: 1024 ** 6,
200
+ exbibyte: 1024 ** 6,
201
+ exbibytes: 1024 ** 6,
202
+ Zi: 1024 ** 7,
203
+ ZiB: 1024 ** 7,
204
+ zebibyte: 1024 ** 7,
205
+ zebibytes: 1024 ** 7,
206
+ Yi: 1024 ** 8,
207
+ YiB: 1024 ** 8,
208
+ yobibyte: 1024 ** 8,
209
+ yobibytes: 1024 ** 8,
210
+ // S21.4 — single-letter abbreviations → powers of two (java -Xmx
211
+ // convention); Lightbend accepts BOTH cases here, through the full ladder.
164
212
  K: 1024,
165
213
  k: 1024,
166
214
  M: 1024 ** 2,
@@ -173,16 +221,10 @@ var BYTE_UNITS = {
173
221
  p: 1024 ** 5,
174
222
  E: 1024 ** 6,
175
223
  e: 1024 ** 6,
176
- // lowercase short-form aliases
177
- b: 1,
178
- kb: 1e3,
179
- kib: 1024,
180
- mb: 1e6,
181
- mib: 1048576,
182
- gb: 1e9,
183
- gib: 1073741824,
184
- tb: 1e12,
185
- tib: 1099511627776
224
+ Z: 1024 ** 7,
225
+ z: 1024 ** 7,
226
+ Y: 1024 ** 8,
227
+ y: 1024 ** 8
186
228
  };
187
229
  var OUTPUT_BYTE_UNITS = {
188
230
  B: 1,
@@ -220,10 +262,7 @@ function parseBytes(value, outputUnit = "B") {
220
262
  }
221
263
  return bytes2 / divisor;
222
264
  }
223
- let mult = BYTE_UNITS[unit];
224
- if (mult === void 0) {
225
- mult = BYTE_UNITS[unit.toLowerCase()];
226
- }
265
+ const mult = BYTE_UNITS[unit];
227
266
  if (mult === void 0) return NaN;
228
267
  const bytes = num * mult;
229
268
  if (Math.abs(bytes) > Number.MAX_SAFE_INTEGER) {
@@ -270,35 +309,114 @@ function stringSegmentsToKey(segments) {
270
309
  return t;
271
310
  }).join(".");
272
311
  }
312
+ function prefixSelfRefRemainder(s, fullKey) {
313
+ const texts = s.segments.map((seg) => seg.text);
314
+ if (!stringSegmentsToKey(texts).startsWith(fullKey + ".")) return null;
315
+ for (let n = 1; n < texts.length; n++) {
316
+ if (stringSegmentsToKey(texts.slice(0, n)) === fullKey) return texts.slice(n);
317
+ }
318
+ return null;
319
+ }
320
+ function navigateResolverValue(v, remainder) {
321
+ let cur = v;
322
+ for (const seg of remainder) {
323
+ if (isSubst(cur) || isConcat(cur)) return null;
324
+ if (isResObj(cur)) {
325
+ const next = cur.fields.get(seg);
326
+ if (next === void 0) return void 0;
327
+ cur = next;
328
+ continue;
329
+ }
330
+ const hv = cur;
331
+ if (hv.kind === "object") {
332
+ const next = hv.fields.get(seg);
333
+ if (next === void 0) return void 0;
334
+ cur = next;
335
+ continue;
336
+ }
337
+ return void 0;
338
+ }
339
+ return cur;
340
+ }
341
+ function foldPrefixSelfRef(s, replacement, remainder) {
342
+ const navigated = navigateResolverValue(replacement, remainder);
343
+ if (navigated === null) return s;
344
+ if (navigated === void 0) {
345
+ return {
346
+ _kind: "subst-placeholder",
347
+ segments: s.segments.map((seg) => ({ text: seg.text, line: seg.line, col: seg.col })),
348
+ optional: s.optional,
349
+ knownAbsent: true,
350
+ listSuffix: s.listSuffix,
351
+ line: s.line,
352
+ col: s.col,
353
+ prefixLen: s.prefixLen
354
+ };
355
+ }
356
+ return cloneResolverValue(navigated);
357
+ }
358
+ function mergeResObjLayers(base, top) {
359
+ const fields = new Map(base.fields);
360
+ for (const [k, tv] of top.fields) {
361
+ const bv = fields.get(k);
362
+ if (bv !== void 0 && isResObj(bv) && isResObj(tv)) {
363
+ fields.set(k, mergeResObjLayers(bv, tv));
364
+ } else {
365
+ fields.set(k, tv);
366
+ }
367
+ }
368
+ const priorValues = new Map(base.priorValues);
369
+ for (const [k, pv] of top.priorValues) priorValues.set(k, pv);
370
+ const resetKeys = new Set(base.resetKeys);
371
+ for (const k of top.resetKeys) resetKeys.add(k);
372
+ return {
373
+ _kind: "res-obj",
374
+ fields,
375
+ priorValues,
376
+ resetKeys
377
+ };
378
+ }
273
379
  function containsSelfRef(v, fullKey) {
274
- if (isSubst(v)) return !v.knownAbsent && substFullKey(v) === fullKey;
275
- if (isConcat(v)) return v.nodes.some((n) => containsSelfRef(n, fullKey));
380
+ return containsSelfRefInner(v, fullKey, true);
381
+ }
382
+ function containsSelfRefInner(v, fullKey, allowPrefix) {
383
+ if (isSubst(v))
384
+ return !v.knownAbsent && (substFullKey(v) === fullKey || allowPrefix && prefixSelfRefRemainder(v, fullKey) !== null);
385
+ if (isConcat(v)) return v.nodes.some((n) => containsSelfRefInner(n, fullKey, allowPrefix));
276
386
  if (isResObj(v)) {
277
387
  for (const f of v.fields.values()) {
278
- if (containsSelfRef(f, fullKey)) return true;
388
+ if (containsSelfRefInner(f, fullKey, false)) return true;
279
389
  }
280
390
  return false;
281
391
  }
282
392
  const hv = v;
283
393
  if (hv.kind === "array") {
284
- return hv.items.some((item) => containsSelfRef(item, fullKey));
394
+ return hv.items.some((item) => containsSelfRefInner(item, fullKey, allowPrefix));
285
395
  }
286
396
  if (hv.kind === "object") {
287
397
  for (const f of hv.fields.values()) {
288
- if (containsSelfRef(f, fullKey)) return true;
398
+ if (containsSelfRefInner(f, fullKey, false)) return true;
289
399
  }
290
400
  return false;
291
401
  }
292
402
  return false;
293
403
  }
294
404
  function foldSelfRef(v, fullKey, replacement) {
405
+ return foldSelfRefInner(v, fullKey, replacement, true);
406
+ }
407
+ function foldSelfRefInner(v, fullKey, replacement, allowPrefix) {
295
408
  if (isSubst(v)) {
296
- return substFullKey(v) === fullKey ? replacement : v;
409
+ if (substFullKey(v) === fullKey) return replacement;
410
+ if (allowPrefix) {
411
+ const remainder = prefixSelfRefRemainder(v, fullKey);
412
+ if (remainder !== null) return foldPrefixSelfRef(v, replacement, remainder);
413
+ }
414
+ return v;
297
415
  }
298
416
  if (isConcat(v)) {
299
417
  return {
300
418
  _kind: "concat-placeholder",
301
- nodes: v.nodes.map((n) => foldSelfRef(n, fullKey, replacement)),
419
+ nodes: v.nodes.map((n) => foldSelfRefInner(n, fullKey, replacement, allowPrefix)),
302
420
  line: v.line,
303
421
  col: v.col
304
422
  };
@@ -306,7 +424,7 @@ function foldSelfRef(v, fullKey, replacement) {
306
424
  if (isResObj(v)) {
307
425
  const newFields = /* @__PURE__ */ new Map();
308
426
  for (const [k, val] of v.fields) {
309
- newFields.set(k, foldSelfRef(val, fullKey, replacement));
427
+ newFields.set(k, foldSelfRefInner(val, fullKey, replacement, false));
310
428
  }
311
429
  return { _kind: "res-obj", fields: newFields, priorValues: new Map(v.priorValues), resetKeys: new Set(v.resetKeys) };
312
430
  }
@@ -314,13 +432,15 @@ function foldSelfRef(v, fullKey, replacement) {
314
432
  if (hv.kind === "array") {
315
433
  return {
316
434
  kind: "array",
317
- items: hv.items.map((item) => foldSelfRef(item, fullKey, replacement))
435
+ items: hv.items.map(
436
+ (item) => foldSelfRefInner(item, fullKey, replacement, allowPrefix)
437
+ )
318
438
  };
319
439
  }
320
440
  if (hv.kind === "object") {
321
441
  const newFields = /* @__PURE__ */ new Map();
322
442
  for (const [k, val] of hv.fields) {
323
- newFields.set(k, foldSelfRef(val, fullKey, replacement));
443
+ newFields.set(k, foldSelfRefInner(val, fullKey, replacement, false));
324
444
  }
325
445
  return { kind: "object", fields: newFields };
326
446
  }
@@ -329,10 +449,26 @@ function foldSelfRef(v, fullKey, replacement) {
329
449
  function foldOrSkipPrior(prior, fullKey, old) {
330
450
  if (!containsSelfRef(prior, fullKey)) return cloneResolverValue(prior);
331
451
  if (old === void 0) return foldOptionalSelfRefAbsent(prior, fullKey);
452
+ if (isSubst(prior)) {
453
+ const remainder = prefixSelfRefRemainder(prior, fullKey);
454
+ if (remainder !== null) {
455
+ const navigated = navigateResolverValue(old, remainder);
456
+ if (navigated === void 0 && prior.optional) return cloneResolverValue(old);
457
+ if (navigated !== null && navigated !== void 0 && isResObj(navigated) && isResObj(old)) {
458
+ return mergeResObjLayers(
459
+ cloneResolverValue(old),
460
+ cloneResolverValue(navigated)
461
+ );
462
+ }
463
+ }
464
+ }
332
465
  return foldSelfRef(prior, fullKey, old);
333
466
  }
334
467
  function foldOptionalSelfRefAbsent(v, fullKey) {
335
- if (isSubst(v) && substFullKey(v) === fullKey) {
468
+ return foldOptionalSelfRefAbsentInner(v, fullKey, true);
469
+ }
470
+ function foldOptionalSelfRefAbsentInner(v, fullKey, allowPrefix) {
471
+ if (isSubst(v) && (substFullKey(v) === fullKey || allowPrefix && prefixSelfRefRemainder(v, fullKey) !== null)) {
336
472
  if (!v.optional) return void 0;
337
473
  return {
338
474
  _kind: "subst-placeholder",
@@ -348,7 +484,7 @@ function foldOptionalSelfRefAbsent(v, fullKey) {
348
484
  if (isConcat(v)) {
349
485
  const nodes = [];
350
486
  for (const node of v.nodes) {
351
- const folded = foldOptionalSelfRefAbsent(node, fullKey);
487
+ const folded = foldOptionalSelfRefAbsentInner(node, fullKey, allowPrefix);
352
488
  if (folded === void 0) return void 0;
353
489
  nodes.push(folded);
354
490
  }
@@ -357,7 +493,7 @@ function foldOptionalSelfRefAbsent(v, fullKey) {
357
493
  if (isResObj(v)) {
358
494
  const fields = /* @__PURE__ */ new Map();
359
495
  for (const [key, value] of v.fields) {
360
- const folded = foldOptionalSelfRefAbsent(value, fullKey);
496
+ const folded = foldOptionalSelfRefAbsentInner(value, fullKey, false);
361
497
  if (folded === void 0) return void 0;
362
498
  fields.set(key, folded);
363
499
  }
@@ -367,7 +503,7 @@ function foldOptionalSelfRefAbsent(v, fullKey) {
367
503
  if (hv.kind === "array") {
368
504
  const items = [];
369
505
  for (const item of hv.items) {
370
- const folded = foldOptionalSelfRefAbsent(item, fullKey);
506
+ const folded = foldOptionalSelfRefAbsentInner(item, fullKey, allowPrefix);
371
507
  if (folded === void 0) return void 0;
372
508
  items.push(folded);
373
509
  }
@@ -376,7 +512,7 @@ function foldOptionalSelfRefAbsent(v, fullKey) {
376
512
  if (hv.kind === "object") {
377
513
  const fields = /* @__PURE__ */ new Map();
378
514
  for (const [key, value] of hv.fields) {
379
- const folded = foldOptionalSelfRefAbsent(value, fullKey);
515
+ const folded = foldOptionalSelfRefAbsentInner(value, fullKey, false);
380
516
  if (folded === void 0) return void 0;
381
517
  fields.set(key, folded);
382
518
  }
@@ -558,6 +694,27 @@ function stripBom(input) {
558
694
  return input.charCodeAt(0) === 65279 ? input.slice(1) : input;
559
695
  }
560
696
 
697
+ // src/internal/depth.ts
698
+ function guardStackDepth(fn, wrap) {
699
+ try {
700
+ return fn();
701
+ } catch (e) {
702
+ if (!isStackOverflow(e)) throw e;
703
+ throw wrap(STACK_MESSAGE);
704
+ }
705
+ }
706
+ var STACK_MESSAGE = "input is nested too deeply for this engine's stack, or contains a cycle (flatten the document, or break the cycle)";
707
+ function isStackOverflow(e) {
708
+ if (!(e instanceof RangeError)) return false;
709
+ const m = e.message.toLowerCase();
710
+ return m.includes("call stack") || // V8: "Maximum call stack size exceeded"
711
+ m.includes("recursion") || // SpiderMonkey: "too much recursion"
712
+ m.includes("stack overflow");
713
+ }
714
+ function depthError(message) {
715
+ return new ConfigError(message, "");
716
+ }
717
+
561
718
  // src/value/numeric-array.ts
562
719
  function numericObjectToArray(value) {
563
720
  if (value.kind !== "object") return null;
@@ -612,6 +769,12 @@ var SubstitutionResolver = class {
612
769
  // Cross-impl with rs.hocon v1.5.1's resolving_field_path (same purpose,
613
770
  // same prefix-match semantics).
614
771
  this.resolvingFieldPath = [];
772
+ // S13a.12 recursion guard: field keys whose prior is currently being
773
+ // resolved through the prefix-self-ref branch. Saved priors are
774
+ // prefix-self-ref-free by the fold invariant, so re-entry only happens on
775
+ // a shape the fold could not see through (a live subst mid-navigation) —
776
+ // report it as a cycle instead of recursing forever.
777
+ this.resolvingPrefixPriors = /* @__PURE__ */ new Set();
615
778
  }
616
779
  /** Returns a prefix for error messages, including origin info when set. */
617
780
  originPrefix() {
@@ -626,46 +789,57 @@ var SubstitutionResolver = class {
626
789
  this.resolvingFieldPath.push(key);
627
790
  const fullCacheKey = stringSegmentsToKey(this.resolvingFieldPath);
628
791
  this.cache.delete(fullCacheKey);
629
- let resolved;
630
792
  try {
631
- resolved = this.resolveVal(val, obj);
632
- } finally {
633
- this.resolvingFieldPath.pop();
634
- }
635
- if (resolved !== void 0) {
636
- if (resolved.kind === "object") {
793
+ const resolved = this.resolveVal(val, obj);
794
+ if (resolved !== void 0) {
795
+ if (resolved.kind === "object") {
796
+ const prior = obj.priorValues.get(key);
797
+ if (prior !== void 0) {
798
+ const priorResolved = this.resolveVal(prior, obj);
799
+ if (priorResolved !== void 0 && priorResolved.kind === "object") {
800
+ const finalValue = deepMergeHoconValues(
801
+ priorResolved,
802
+ resolved
803
+ );
804
+ result.set(key, finalValue);
805
+ this.cache.set(fullCacheKey, finalValue);
806
+ this.cacheDescendants(fullCacheKey, finalValue);
807
+ continue;
808
+ }
809
+ }
810
+ }
811
+ result.set(key, resolved);
812
+ this.cache.set(fullCacheKey, resolved);
813
+ this.cacheDescendants(fullCacheKey, resolved);
814
+ } else {
637
815
  const prior = obj.priorValues.get(key);
638
816
  if (prior !== void 0) {
639
817
  const priorResolved = this.resolveVal(prior, obj);
640
- if (priorResolved !== void 0 && priorResolved.kind === "object") {
641
- const finalValue = deepMergeHoconValues(
642
- priorResolved,
643
- resolved
644
- );
645
- result.set(key, finalValue);
646
- this.cache.set(fullCacheKey, finalValue);
647
- this.cacheDescendants(fullCacheKey, finalValue);
648
- continue;
818
+ if (priorResolved !== void 0) {
819
+ result.set(key, priorResolved);
820
+ this.cache.set(fullCacheKey, priorResolved);
821
+ this.cacheDescendants(fullCacheKey, priorResolved);
649
822
  }
650
823
  }
651
824
  }
652
- result.set(key, resolved);
653
- this.cache.set(fullCacheKey, resolved);
654
- this.cacheDescendants(fullCacheKey, resolved);
655
- } else {
656
- const prior = obj.priorValues.get(key);
657
- if (prior !== void 0) {
658
- const priorResolved = this.resolveVal(prior, obj);
659
- if (priorResolved !== void 0) {
660
- result.set(key, priorResolved);
661
- this.cache.set(fullCacheKey, priorResolved);
662
- this.cacheDescendants(fullCacheKey, priorResolved);
663
- }
664
- }
825
+ } finally {
826
+ this.resolvingFieldPath.pop();
665
827
  }
666
828
  }
667
829
  return { kind: "object", fields: result };
668
830
  }
831
+ /** S13a.12: walk resolved-object fields by segment text. A missing segment
832
+ * or a walk into a scalar/array is path-absent → undefined. */
833
+ navigateResolved(v, remainder) {
834
+ let cur = v;
835
+ for (const seg of remainder) {
836
+ if (cur.kind !== "object") return void 0;
837
+ const next = cur.fields.get(seg);
838
+ if (next === void 0) return void 0;
839
+ cur = next;
840
+ }
841
+ return cur;
842
+ }
669
843
  cacheDescendants(prefix, value) {
670
844
  if (value.kind !== "object") return;
671
845
  for (const [key, child] of value.fields) {
@@ -688,17 +862,59 @@ var SubstitutionResolver = class {
688
862
  if (isResObj(v)) return this.resolveResObj(v);
689
863
  const hv = v;
690
864
  if (hv.kind === "array") {
691
- return {
692
- kind: "array",
693
- items: hv.items.map(
694
- (item) => this.resolveVal(item, scope) ?? { kind: "scalar", raw: "null", valueType: "null" }
695
- )
696
- };
865
+ const items = [];
866
+ for (const item of hv.items) {
867
+ const resolved = this.resolveVal(item, scope);
868
+ if (resolved === void 0) continue;
869
+ items.push(resolved);
870
+ }
871
+ return { kind: "array", items };
697
872
  }
698
873
  return hv;
699
874
  }
700
875
  resolveSubst(s, scope) {
701
- if (s.knownAbsent) return void 0;
876
+ if (s.knownAbsent) {
877
+ if (!s.optional) {
878
+ if (this.opts.allowUnresolved) return s;
879
+ const k = s.listSuffix ? `${segmentsToKey(s.segments)}[]` : segmentsToKey(s.segments);
880
+ throw new ResolveError(
881
+ `${this.originPrefix()}could not resolve substitution: \${${k}}`,
882
+ k,
883
+ s.line,
884
+ s.col
885
+ );
886
+ }
887
+ return void 0;
888
+ }
889
+ {
890
+ const rfp = this.resolvingFieldPath;
891
+ const sTexts = s.segments.map((seg) => seg.text);
892
+ if (rfp.length > 0 && rfp.length < sTexts.length && stringSegmentsToKey(sTexts.slice(0, rfp.length)) === stringSegmentsToKey(rfp)) {
893
+ const guardKey = stringSegmentsToKey(rfp);
894
+ const prior = scope.priorValues.get(rfp[rfp.length - 1]);
895
+ if (prior !== void 0 && !this.resolvingPrefixPriors.has(guardKey)) {
896
+ this.resolvingPrefixPriors.add(guardKey);
897
+ try {
898
+ const priorResolved = this.resolveVal(prior, scope);
899
+ if (priorResolved !== void 0) {
900
+ const navigated = this.navigateResolved(priorResolved, sTexts.slice(rfp.length));
901
+ if (navigated !== void 0) return navigated;
902
+ }
903
+ } finally {
904
+ this.resolvingPrefixPriors.delete(guardKey);
905
+ }
906
+ }
907
+ if (s.optional) return void 0;
908
+ if (this.opts.allowUnresolved) return s;
909
+ const k = s.listSuffix ? `${segmentsToKey(s.segments)}[]` : segmentsToKey(s.segments);
910
+ throw new ResolveError(
911
+ `${this.originPrefix()}could not resolve substitution: \${${k}}`,
912
+ k,
913
+ s.line,
914
+ s.col
915
+ );
916
+ }
917
+ }
702
918
  const key = s.listSuffix ? `${segmentsToKey(s.segments)}[]` : segmentsToKey(s.segments);
703
919
  if (this.cache.has(key)) return this.cache.get(key);
704
920
  if (this.resolving.has(key)) {
@@ -1018,6 +1234,121 @@ function hoconValueToResObj2(value) {
1018
1234
  return obj;
1019
1235
  }
1020
1236
 
1237
+ // src/render-hocon.ts
1238
+ function renderHoconFromRoot(root) {
1239
+ const out = [];
1240
+ renderObjectBody(out, root, 0);
1241
+ return out.join("");
1242
+ }
1243
+ function renderObjectBody(out, o, depth) {
1244
+ const indent = " ".repeat(depth);
1245
+ for (const [k, v] of o.fields) {
1246
+ out.push(indent, renderKey(k));
1247
+ if (v.kind === "object") {
1248
+ out.push(" {");
1249
+ if (v.fields.size === 0) {
1250
+ out.push("}\n");
1251
+ continue;
1252
+ }
1253
+ out.push("\n");
1254
+ renderObjectBody(out, v, depth + 1);
1255
+ out.push(indent, "}\n");
1256
+ } else {
1257
+ out.push(" = ");
1258
+ renderValue(out, v, depth);
1259
+ out.push("\n");
1260
+ }
1261
+ }
1262
+ }
1263
+ function renderValue(out, v, depth) {
1264
+ switch (v.kind) {
1265
+ case "object": {
1266
+ if (v.fields.size === 0) {
1267
+ out.push("{}");
1268
+ return;
1269
+ }
1270
+ out.push("{\n");
1271
+ renderObjectBody(out, v, depth + 1);
1272
+ out.push(" ".repeat(depth), "}");
1273
+ return;
1274
+ }
1275
+ case "array":
1276
+ renderArray(out, v, depth);
1277
+ return;
1278
+ case "scalar":
1279
+ out.push(renderScalar(v));
1280
+ return;
1281
+ default:
1282
+ throw new ConfigError(
1283
+ "renderHocon: unrenderable value (config must be resolved data)",
1284
+ ""
1285
+ );
1286
+ }
1287
+ }
1288
+ function renderArray(out, a, depth) {
1289
+ if (a.items.length === 0) {
1290
+ out.push("[]");
1291
+ return;
1292
+ }
1293
+ const inner = " ".repeat(depth + 1);
1294
+ out.push("[\n");
1295
+ for (const e of a.items) {
1296
+ out.push(inner);
1297
+ renderValue(out, e, depth + 1);
1298
+ out.push("\n");
1299
+ }
1300
+ out.push(" ".repeat(depth), "]");
1301
+ }
1302
+ function renderScalar(s) {
1303
+ switch (s.valueType) {
1304
+ case "null":
1305
+ return "null";
1306
+ case "boolean":
1307
+ case "number":
1308
+ return s.raw;
1309
+ case "string":
1310
+ return renderString(s.raw);
1311
+ }
1312
+ }
1313
+ var SAFE_UNQUOTED_KEY = /^[A-Za-z0-9_-]+$/;
1314
+ function renderKey(k) {
1315
+ if (k === "include") return quoteString(k);
1316
+ return SAFE_UNQUOTED_KEY.test(k) ? k : quoteString(k);
1317
+ }
1318
+ var SAFE_BARE_STRING = /^[A-Za-z][A-Za-z0-9_-]*$/;
1319
+ var STRING_KEYWORDS = /* @__PURE__ */ new Set(["true", "false", "null", "yes", "no", "on", "off"]);
1320
+ function renderString(s) {
1321
+ return SAFE_BARE_STRING.test(s) && !STRING_KEYWORDS.has(s.toLowerCase()) ? s : quoteString(s);
1322
+ }
1323
+ function quoteString(s) {
1324
+ if (s.includes("\n") && !s.includes("\r") && !s.includes('"""') && !s.endsWith('"')) {
1325
+ return `"""${s}"""`;
1326
+ }
1327
+ let b = '"';
1328
+ for (const ch of s) {
1329
+ switch (ch) {
1330
+ case '"':
1331
+ b += '\\"';
1332
+ break;
1333
+ case "\\":
1334
+ b += "\\\\";
1335
+ break;
1336
+ case "\n":
1337
+ b += "\\n";
1338
+ break;
1339
+ case "\r":
1340
+ b += "\\r";
1341
+ break;
1342
+ case " ":
1343
+ b += "\\t";
1344
+ break;
1345
+ default:
1346
+ b += ch;
1347
+ }
1348
+ }
1349
+ return b + '"';
1350
+ }
1351
+
1021
1352
  // src/config.ts
1022
1353
  var Config = class _Config {
1023
1354
  constructor(root, opts) {
@@ -1323,6 +1654,36 @@ var Config = class _Config {
1323
1654
  toObject(opts) {
1324
1655
  return hoconToJs(this.root, opts?.nullPrototype === true);
1325
1656
  }
1657
+ /**
1658
+ * Renders this resolved Config as HOCON text (E18; lockstep with go.hocon's
1659
+ * `Config.RenderHOCON`, v1.11.0).
1660
+ *
1661
+ * The output round-trips: parsing it back yields the same value tree. That
1662
+ * is the correctness contract, not byte-for-byte formatting — a scalar is
1663
+ * quoted whenever leaving it bare would re-parse as a different type (a
1664
+ * string `"8080"` becomes `"8080"`, not `8080`), and left bare only when it
1665
+ * provably cannot.
1666
+ *
1667
+ * The Config must be resolved and hold only data (objects, arrays, string /
1668
+ * number / boolean / null scalars) — exactly what {@link fromMap} and the
1669
+ * format adapters produce. An unresolved placeholder throws
1670
+ * {@link ConfigError}; substitutions have no textual round trip through a
1671
+ * value tree.
1672
+ *
1673
+ * The root object's fields are emitted without enclosing braces, nested
1674
+ * objects as `key { … }`, arrays as newline-separated `[ … ]`, indented two
1675
+ * spaces. Source comments are not represented — a value tree does not carry
1676
+ * them.
1677
+ */
1678
+ renderHocon() {
1679
+ if (!this._resolved) {
1680
+ throw new ConfigError(
1681
+ "renderHocon: unrenderable unresolved substitution (config must be resolved data)",
1682
+ ""
1683
+ );
1684
+ }
1685
+ return renderHoconFromRoot(this.root);
1686
+ }
1326
1687
  /**
1327
1688
  * @internal Test-only: renders this resolved Config's value tree as canonical
1328
1689
  * JSON (sorted keys, no whitespace). Used by Layer-2 fixture tests to compare
@@ -1536,7 +1897,10 @@ function normalizeLeadingZeroNumber(raw) {
1536
1897
 
1537
1898
  // src/value-factory.ts
1538
1899
  function fromMap(values, originDescription) {
1539
- const fields = coerceObject(values, "");
1900
+ const fields = guardStackDepth(
1901
+ () => coerceObject(values, ""),
1902
+ (msg) => depthError(`fromMap: ${msg}`)
1903
+ );
1540
1904
  const root = { kind: "object", fields };
1541
1905
  return Config._fromResolvedValue(root, { originDescription });
1542
1906
  }
@@ -1613,11 +1977,14 @@ function parseYaml(input, originDescription) {
1613
1977
  if (doc.errors.length > 0) {
1614
1978
  throw new ConfigError(`yaml: ${doc.errors[0]?.message ?? "parse error"}`, "");
1615
1979
  }
1616
- return fromYamlValue(doc.toJS({ mapAsMap: false }), originDescription);
1980
+ return fromYamlValue(doc.toJS({ mapAsMap: true }), originDescription);
1617
1981
  }
1618
1982
  function fromYamlValue(value, originDescription) {
1619
1983
  if (value === null || value === void 0) return fromMap({}, originDescription);
1620
- const normalized = convert(value, "");
1984
+ const normalized = guardStackDepth(
1985
+ () => convert(value, ""),
1986
+ (msg) => depthError(`yaml: ${msg}`)
1987
+ );
1621
1988
  if (typeof normalized !== "object" || normalized === null || Array.isArray(normalized)) {
1622
1989
  throw new ConfigError(
1623
1990
  `yaml: document root is ${Array.isArray(normalized) ? "an array" : typeof normalized}, but a config root must be an object (spec F0.3)`,
@@ -1633,6 +2000,7 @@ function convert(v, atPath) {
1633
2000
  }
1634
2001
  if (v instanceof Map) {
1635
2002
  const entries = [];
2003
+ const seen = /* @__PURE__ */ new Map();
1636
2004
  for (const [k, e] of v) {
1637
2005
  if (k !== null && typeof k === "object") {
1638
2006
  throw new ConfigError(
@@ -1641,6 +2009,13 @@ function convert(v, atPath) {
1641
2009
  );
1642
2010
  }
1643
2011
  const key = String(k);
2012
+ if (seen.has(key)) {
2013
+ throw new ConfigError(
2014
+ `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)`,
2015
+ atPath
2016
+ );
2017
+ }
2018
+ seen.set(key, k);
1644
2019
  entries.push([key, convert(e, atPath === "" ? key : `${atPath}.${key}`)]);
1645
2020
  }
1646
2021
  return Object.fromEntries(entries);
@@ -1674,6 +2049,10 @@ function bytesToBase64(bytes) {
1674
2049
  }
1675
2050
  return btoa(binary);
1676
2051
  }
2052
+ function keyForm(k) {
2053
+ if (typeof k === "string") return JSON.stringify(k);
2054
+ return `${String(k)} (${k === null ? "null" : typeof k})`;
2055
+ }
1677
2056
  var MAX_SAFE = BigInt(Number.MAX_SAFE_INTEGER);
1678
2057
  var MIN_SAFE = BigInt(Number.MIN_SAFE_INTEGER);
1679
2058
  function isSafeIntegerBigInt(v) {