@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.
@@ -70,13 +70,22 @@ function coerceNumber(value) {
70
70
  return Number(value);
71
71
  }
72
72
  var DURATION_UNITS = {
73
+ // S19.1–S19.3: the bare nano/micro/milli (+plural) aliases are part of the
74
+ // spec's unit lists (HOCON.md L1307–L1309) and accepted by Lightbend
75
+ // (typesafe-config 1.4.6 probe, 2026-08-18); they were missing here.
73
76
  ns: 1e-6,
77
+ nano: 1e-6,
78
+ nanos: 1e-6,
74
79
  nanosecond: 1e-6,
75
80
  nanoseconds: 1e-6,
76
81
  us: 1e-3,
82
+ micro: 1e-3,
83
+ micros: 1e-3,
77
84
  microsecond: 1e-3,
78
85
  microseconds: 1e-3,
79
86
  ms: 1,
87
+ milli: 1,
88
+ millis: 1,
80
89
  millisecond: 1,
81
90
  milliseconds: 1,
82
91
  s: 1e3,
@@ -128,37 +137,76 @@ function parseDuration(value, outputUnit = "ms") {
128
137
  return ms / divisor;
129
138
  }
130
139
  var BYTE_UNITS = {
140
+ // S21.1–S21.4 — the EXACT Lightbend/typesafe-config unit set (1.4.6 probe,
141
+ // 2026-08-18). Multi-letter units are case-sensitive: the SI decimal short
142
+ // form is `kB` (NOT `KB`/`kb` — Lightbend rejects both), higher decimals
143
+ // are `MB`…`YB`, binary prefixes are `Ki`/`KiB`…`Yi`/`YiB` with capital
144
+ // first letter, and long forms are lowercase only (`kilobyte`, never
145
+ // `Kilobyte`). Only the bare byte unit (`B`/`b`) and the single-letter
146
+ // -Xmx forms accept both cases. The old case-insensitive fallback and the
147
+ // lowercase alias rows accepted spellings Lightbend rejects — removed.
131
148
  B: 1,
149
+ b: 1,
132
150
  byte: 1,
133
151
  bytes: 1,
134
- KB: 1e3,
152
+ kB: 1e3,
135
153
  kilobyte: 1e3,
136
154
  kilobytes: 1e3,
137
- KiB: 1024,
138
- kibibyte: 1024,
139
- kibibytes: 1024,
140
155
  MB: 1e6,
141
156
  megabyte: 1e6,
142
157
  megabytes: 1e6,
143
- MiB: 1048576,
144
- mebibyte: 1048576,
145
- mebibytes: 1048576,
146
158
  GB: 1e9,
147
159
  gigabyte: 1e9,
148
160
  gigabytes: 1e9,
149
- GiB: 1073741824,
150
- gibibyte: 1073741824,
151
- gibibytes: 1073741824,
152
161
  TB: 1e12,
153
162
  terabyte: 1e12,
154
163
  terabytes: 1e12,
164
+ PB: 1e15,
165
+ petabyte: 1e15,
166
+ petabytes: 1e15,
167
+ EB: 1e18,
168
+ exabyte: 1e18,
169
+ exabytes: 1e18,
170
+ ZB: 1e21,
171
+ zettabyte: 1e21,
172
+ zettabytes: 1e21,
173
+ YB: 1e24,
174
+ yottabyte: 1e24,
175
+ yottabytes: 1e24,
176
+ Ki: 1024,
177
+ KiB: 1024,
178
+ kibibyte: 1024,
179
+ kibibytes: 1024,
180
+ Mi: 1048576,
181
+ MiB: 1048576,
182
+ mebibyte: 1048576,
183
+ mebibytes: 1048576,
184
+ Gi: 1073741824,
185
+ GiB: 1073741824,
186
+ gibibyte: 1073741824,
187
+ gibibytes: 1073741824,
188
+ Ti: 1099511627776,
155
189
  TiB: 1099511627776,
156
190
  tebibyte: 1099511627776,
157
191
  tebibytes: 1099511627776,
158
- // S21.4 — HOCON.md L1385: single-letter abbreviations → powers of two (java -Xmx convention).
159
- // Lightbend typesafe-config 1.4.3 verified: 1K=1024, 1M=1048576, etc.
160
- // Both upper- and lowercase accepted per Lightbend behaviour.
161
- // Z/Y (2^70/2^80) are deferred — they overflow MAX_SAFE_INTEGER and require BigInt accessor.
192
+ Pi: 1024 ** 5,
193
+ PiB: 1024 ** 5,
194
+ pebibyte: 1024 ** 5,
195
+ pebibytes: 1024 ** 5,
196
+ Ei: 1024 ** 6,
197
+ EiB: 1024 ** 6,
198
+ exbibyte: 1024 ** 6,
199
+ exbibytes: 1024 ** 6,
200
+ Zi: 1024 ** 7,
201
+ ZiB: 1024 ** 7,
202
+ zebibyte: 1024 ** 7,
203
+ zebibytes: 1024 ** 7,
204
+ Yi: 1024 ** 8,
205
+ YiB: 1024 ** 8,
206
+ yobibyte: 1024 ** 8,
207
+ yobibytes: 1024 ** 8,
208
+ // S21.4 — single-letter abbreviations → powers of two (java -Xmx
209
+ // convention); Lightbend accepts BOTH cases here, through the full ladder.
162
210
  K: 1024,
163
211
  k: 1024,
164
212
  M: 1024 ** 2,
@@ -171,16 +219,10 @@ var BYTE_UNITS = {
171
219
  p: 1024 ** 5,
172
220
  E: 1024 ** 6,
173
221
  e: 1024 ** 6,
174
- // lowercase short-form aliases
175
- b: 1,
176
- kb: 1e3,
177
- kib: 1024,
178
- mb: 1e6,
179
- mib: 1048576,
180
- gb: 1e9,
181
- gib: 1073741824,
182
- tb: 1e12,
183
- tib: 1099511627776
222
+ Z: 1024 ** 7,
223
+ z: 1024 ** 7,
224
+ Y: 1024 ** 8,
225
+ y: 1024 ** 8
184
226
  };
185
227
  var OUTPUT_BYTE_UNITS = {
186
228
  B: 1,
@@ -218,10 +260,7 @@ function parseBytes(value, outputUnit = "B") {
218
260
  }
219
261
  return bytes2 / divisor;
220
262
  }
221
- let mult = BYTE_UNITS[unit];
222
- if (mult === void 0) {
223
- mult = BYTE_UNITS[unit.toLowerCase()];
224
- }
263
+ const mult = BYTE_UNITS[unit];
225
264
  if (mult === void 0) return NaN;
226
265
  const bytes = num * mult;
227
266
  if (Math.abs(bytes) > Number.MAX_SAFE_INTEGER) {
@@ -277,35 +316,114 @@ function stringSegmentsToKey(segments) {
277
316
  return t;
278
317
  }).join(".");
279
318
  }
319
+ function prefixSelfRefRemainder(s, fullKey) {
320
+ const texts = s.segments.map((seg) => seg.text);
321
+ if (!stringSegmentsToKey(texts).startsWith(fullKey + ".")) return null;
322
+ for (let n = 1; n < texts.length; n++) {
323
+ if (stringSegmentsToKey(texts.slice(0, n)) === fullKey) return texts.slice(n);
324
+ }
325
+ return null;
326
+ }
327
+ function navigateResolverValue(v, remainder) {
328
+ let cur = v;
329
+ for (const seg of remainder) {
330
+ if (isSubst(cur) || isConcat(cur)) return null;
331
+ if (isResObj(cur)) {
332
+ const next = cur.fields.get(seg);
333
+ if (next === void 0) return void 0;
334
+ cur = next;
335
+ continue;
336
+ }
337
+ const hv = cur;
338
+ if (hv.kind === "object") {
339
+ const next = hv.fields.get(seg);
340
+ if (next === void 0) return void 0;
341
+ cur = next;
342
+ continue;
343
+ }
344
+ return void 0;
345
+ }
346
+ return cur;
347
+ }
348
+ function foldPrefixSelfRef(s, replacement, remainder) {
349
+ const navigated = navigateResolverValue(replacement, remainder);
350
+ if (navigated === null) return s;
351
+ if (navigated === void 0) {
352
+ return {
353
+ _kind: "subst-placeholder",
354
+ segments: s.segments.map((seg) => ({ text: seg.text, line: seg.line, col: seg.col })),
355
+ optional: s.optional,
356
+ knownAbsent: true,
357
+ listSuffix: s.listSuffix,
358
+ line: s.line,
359
+ col: s.col,
360
+ prefixLen: s.prefixLen
361
+ };
362
+ }
363
+ return cloneResolverValue(navigated);
364
+ }
365
+ function mergeResObjLayers(base, top) {
366
+ const fields = new Map(base.fields);
367
+ for (const [k, tv] of top.fields) {
368
+ const bv = fields.get(k);
369
+ if (bv !== void 0 && isResObj(bv) && isResObj(tv)) {
370
+ fields.set(k, mergeResObjLayers(bv, tv));
371
+ } else {
372
+ fields.set(k, tv);
373
+ }
374
+ }
375
+ const priorValues = new Map(base.priorValues);
376
+ for (const [k, pv] of top.priorValues) priorValues.set(k, pv);
377
+ const resetKeys = new Set(base.resetKeys);
378
+ for (const k of top.resetKeys) resetKeys.add(k);
379
+ return {
380
+ _kind: "res-obj",
381
+ fields,
382
+ priorValues,
383
+ resetKeys
384
+ };
385
+ }
280
386
  function containsSelfRef(v, fullKey) {
281
- if (isSubst(v)) return !v.knownAbsent && substFullKey(v) === fullKey;
282
- if (isConcat(v)) return v.nodes.some((n) => containsSelfRef(n, fullKey));
387
+ return containsSelfRefInner(v, fullKey, true);
388
+ }
389
+ function containsSelfRefInner(v, fullKey, allowPrefix) {
390
+ if (isSubst(v))
391
+ return !v.knownAbsent && (substFullKey(v) === fullKey || allowPrefix && prefixSelfRefRemainder(v, fullKey) !== null);
392
+ if (isConcat(v)) return v.nodes.some((n) => containsSelfRefInner(n, fullKey, allowPrefix));
283
393
  if (isResObj(v)) {
284
394
  for (const f of v.fields.values()) {
285
- if (containsSelfRef(f, fullKey)) return true;
395
+ if (containsSelfRefInner(f, fullKey, false)) return true;
286
396
  }
287
397
  return false;
288
398
  }
289
399
  const hv = v;
290
400
  if (hv.kind === "array") {
291
- return hv.items.some((item) => containsSelfRef(item, fullKey));
401
+ return hv.items.some((item) => containsSelfRefInner(item, fullKey, allowPrefix));
292
402
  }
293
403
  if (hv.kind === "object") {
294
404
  for (const f of hv.fields.values()) {
295
- if (containsSelfRef(f, fullKey)) return true;
405
+ if (containsSelfRefInner(f, fullKey, false)) return true;
296
406
  }
297
407
  return false;
298
408
  }
299
409
  return false;
300
410
  }
301
411
  function foldSelfRef(v, fullKey, replacement) {
412
+ return foldSelfRefInner(v, fullKey, replacement, true);
413
+ }
414
+ function foldSelfRefInner(v, fullKey, replacement, allowPrefix) {
302
415
  if (isSubst(v)) {
303
- return substFullKey(v) === fullKey ? replacement : v;
416
+ if (substFullKey(v) === fullKey) return replacement;
417
+ if (allowPrefix) {
418
+ const remainder = prefixSelfRefRemainder(v, fullKey);
419
+ if (remainder !== null) return foldPrefixSelfRef(v, replacement, remainder);
420
+ }
421
+ return v;
304
422
  }
305
423
  if (isConcat(v)) {
306
424
  return {
307
425
  _kind: "concat-placeholder",
308
- nodes: v.nodes.map((n) => foldSelfRef(n, fullKey, replacement)),
426
+ nodes: v.nodes.map((n) => foldSelfRefInner(n, fullKey, replacement, allowPrefix)),
309
427
  line: v.line,
310
428
  col: v.col
311
429
  };
@@ -313,7 +431,7 @@ function foldSelfRef(v, fullKey, replacement) {
313
431
  if (isResObj(v)) {
314
432
  const newFields = /* @__PURE__ */ new Map();
315
433
  for (const [k, val] of v.fields) {
316
- newFields.set(k, foldSelfRef(val, fullKey, replacement));
434
+ newFields.set(k, foldSelfRefInner(val, fullKey, replacement, false));
317
435
  }
318
436
  return { _kind: "res-obj", fields: newFields, priorValues: new Map(v.priorValues), resetKeys: new Set(v.resetKeys) };
319
437
  }
@@ -321,13 +439,15 @@ function foldSelfRef(v, fullKey, replacement) {
321
439
  if (hv.kind === "array") {
322
440
  return {
323
441
  kind: "array",
324
- items: hv.items.map((item) => foldSelfRef(item, fullKey, replacement))
442
+ items: hv.items.map(
443
+ (item) => foldSelfRefInner(item, fullKey, replacement, allowPrefix)
444
+ )
325
445
  };
326
446
  }
327
447
  if (hv.kind === "object") {
328
448
  const newFields = /* @__PURE__ */ new Map();
329
449
  for (const [k, val] of hv.fields) {
330
- newFields.set(k, foldSelfRef(val, fullKey, replacement));
450
+ newFields.set(k, foldSelfRefInner(val, fullKey, replacement, false));
331
451
  }
332
452
  return { kind: "object", fields: newFields };
333
453
  }
@@ -336,10 +456,26 @@ function foldSelfRef(v, fullKey, replacement) {
336
456
  function foldOrSkipPrior(prior, fullKey, old) {
337
457
  if (!containsSelfRef(prior, fullKey)) return cloneResolverValue(prior);
338
458
  if (old === void 0) return foldOptionalSelfRefAbsent(prior, fullKey);
459
+ if (isSubst(prior)) {
460
+ const remainder = prefixSelfRefRemainder(prior, fullKey);
461
+ if (remainder !== null) {
462
+ const navigated = navigateResolverValue(old, remainder);
463
+ if (navigated === void 0 && prior.optional) return cloneResolverValue(old);
464
+ if (navigated !== null && navigated !== void 0 && isResObj(navigated) && isResObj(old)) {
465
+ return mergeResObjLayers(
466
+ cloneResolverValue(old),
467
+ cloneResolverValue(navigated)
468
+ );
469
+ }
470
+ }
471
+ }
339
472
  return foldSelfRef(prior, fullKey, old);
340
473
  }
341
474
  function foldOptionalSelfRefAbsent(v, fullKey) {
342
- if (isSubst(v) && substFullKey(v) === fullKey) {
475
+ return foldOptionalSelfRefAbsentInner(v, fullKey, true);
476
+ }
477
+ function foldOptionalSelfRefAbsentInner(v, fullKey, allowPrefix) {
478
+ if (isSubst(v) && (substFullKey(v) === fullKey || allowPrefix && prefixSelfRefRemainder(v, fullKey) !== null)) {
343
479
  if (!v.optional) return void 0;
344
480
  return {
345
481
  _kind: "subst-placeholder",
@@ -355,7 +491,7 @@ function foldOptionalSelfRefAbsent(v, fullKey) {
355
491
  if (isConcat(v)) {
356
492
  const nodes = [];
357
493
  for (const node of v.nodes) {
358
- const folded = foldOptionalSelfRefAbsent(node, fullKey);
494
+ const folded = foldOptionalSelfRefAbsentInner(node, fullKey, allowPrefix);
359
495
  if (folded === void 0) return void 0;
360
496
  nodes.push(folded);
361
497
  }
@@ -364,7 +500,7 @@ function foldOptionalSelfRefAbsent(v, fullKey) {
364
500
  if (isResObj(v)) {
365
501
  const fields = /* @__PURE__ */ new Map();
366
502
  for (const [key, value] of v.fields) {
367
- const folded = foldOptionalSelfRefAbsent(value, fullKey);
503
+ const folded = foldOptionalSelfRefAbsentInner(value, fullKey, false);
368
504
  if (folded === void 0) return void 0;
369
505
  fields.set(key, folded);
370
506
  }
@@ -374,7 +510,7 @@ function foldOptionalSelfRefAbsent(v, fullKey) {
374
510
  if (hv.kind === "array") {
375
511
  const items = [];
376
512
  for (const item of hv.items) {
377
- const folded = foldOptionalSelfRefAbsent(item, fullKey);
513
+ const folded = foldOptionalSelfRefAbsentInner(item, fullKey, allowPrefix);
378
514
  if (folded === void 0) return void 0;
379
515
  items.push(folded);
380
516
  }
@@ -383,7 +519,7 @@ function foldOptionalSelfRefAbsent(v, fullKey) {
383
519
  if (hv.kind === "object") {
384
520
  const fields = /* @__PURE__ */ new Map();
385
521
  for (const [key, value] of hv.fields) {
386
- const folded = foldOptionalSelfRefAbsent(value, fullKey);
522
+ const folded = foldOptionalSelfRefAbsentInner(value, fullKey, false);
387
523
  if (folded === void 0) return void 0;
388
524
  fields.set(key, folded);
389
525
  }
@@ -565,6 +701,31 @@ function stripBom(input) {
565
701
  return input.charCodeAt(0) === 65279 ? input.slice(1) : input;
566
702
  }
567
703
 
704
+ // src/internal/depth.ts
705
+ var MAX_PATH_SEGMENTS = 64;
706
+ function tooDeep(segments) {
707
+ return segments > MAX_PATH_SEGMENTS;
708
+ }
709
+ function guardStackDepth(fn, wrap) {
710
+ try {
711
+ return fn();
712
+ } catch (e) {
713
+ if (!isStackOverflow(e)) throw e;
714
+ throw wrap(STACK_MESSAGE);
715
+ }
716
+ }
717
+ var STACK_MESSAGE = "input is nested too deeply for this engine's stack, or contains a cycle (flatten the document, or break the cycle)";
718
+ function isStackOverflow(e) {
719
+ if (!(e instanceof RangeError)) return false;
720
+ const m = e.message.toLowerCase();
721
+ return m.includes("call stack") || // V8: "Maximum call stack size exceeded"
722
+ m.includes("recursion") || // SpiderMonkey: "too much recursion"
723
+ m.includes("stack overflow");
724
+ }
725
+ function depthError(message) {
726
+ return new ConfigError(message, "");
727
+ }
728
+
568
729
  // src/internal/properties/properties.ts
569
730
  function parseProperties(input) {
570
731
  const pairs = [];
@@ -572,7 +733,15 @@ function parseProperties(input) {
572
733
  const [rawKey, rawValue] = splitKeyValue(text);
573
734
  const key = unescapeProps(rawKey, line);
574
735
  if (key === "") continue;
575
- pairs.push([key.split("."), unescapeProps(rawValue, line)]);
736
+ const segments = key.split(".");
737
+ if (tooDeep(segments.length)) {
738
+ throw new ParseError(
739
+ `key maps to a path ${segments.length} segments deep, over the limit of ${MAX_PATH_SEGMENTS}`,
740
+ line,
741
+ 1
742
+ );
743
+ }
744
+ pairs.push([segments, unescapeProps(rawValue, line)]);
576
745
  }
577
746
  return nestPairs(pairs);
578
747
  }
@@ -747,6 +916,12 @@ var SubstitutionResolver = class {
747
916
  // Cross-impl with rs.hocon v1.5.1's resolving_field_path (same purpose,
748
917
  // same prefix-match semantics).
749
918
  this.resolvingFieldPath = [];
919
+ // S13a.12 recursion guard: field keys whose prior is currently being
920
+ // resolved through the prefix-self-ref branch. Saved priors are
921
+ // prefix-self-ref-free by the fold invariant, so re-entry only happens on
922
+ // a shape the fold could not see through (a live subst mid-navigation) —
923
+ // report it as a cycle instead of recursing forever.
924
+ this.resolvingPrefixPriors = /* @__PURE__ */ new Set();
750
925
  }
751
926
  /** Returns a prefix for error messages, including origin info when set. */
752
927
  originPrefix() {
@@ -761,46 +936,57 @@ var SubstitutionResolver = class {
761
936
  this.resolvingFieldPath.push(key);
762
937
  const fullCacheKey = stringSegmentsToKey(this.resolvingFieldPath);
763
938
  this.cache.delete(fullCacheKey);
764
- let resolved;
765
939
  try {
766
- resolved = this.resolveVal(val, obj);
767
- } finally {
768
- this.resolvingFieldPath.pop();
769
- }
770
- if (resolved !== void 0) {
771
- if (resolved.kind === "object") {
940
+ const resolved = this.resolveVal(val, obj);
941
+ if (resolved !== void 0) {
942
+ if (resolved.kind === "object") {
943
+ const prior = obj.priorValues.get(key);
944
+ if (prior !== void 0) {
945
+ const priorResolved = this.resolveVal(prior, obj);
946
+ if (priorResolved !== void 0 && priorResolved.kind === "object") {
947
+ const finalValue = deepMergeHoconValues(
948
+ priorResolved,
949
+ resolved
950
+ );
951
+ result.set(key, finalValue);
952
+ this.cache.set(fullCacheKey, finalValue);
953
+ this.cacheDescendants(fullCacheKey, finalValue);
954
+ continue;
955
+ }
956
+ }
957
+ }
958
+ result.set(key, resolved);
959
+ this.cache.set(fullCacheKey, resolved);
960
+ this.cacheDescendants(fullCacheKey, resolved);
961
+ } else {
772
962
  const prior = obj.priorValues.get(key);
773
963
  if (prior !== void 0) {
774
964
  const priorResolved = this.resolveVal(prior, obj);
775
- if (priorResolved !== void 0 && priorResolved.kind === "object") {
776
- const finalValue = deepMergeHoconValues(
777
- priorResolved,
778
- resolved
779
- );
780
- result.set(key, finalValue);
781
- this.cache.set(fullCacheKey, finalValue);
782
- this.cacheDescendants(fullCacheKey, finalValue);
783
- continue;
965
+ if (priorResolved !== void 0) {
966
+ result.set(key, priorResolved);
967
+ this.cache.set(fullCacheKey, priorResolved);
968
+ this.cacheDescendants(fullCacheKey, priorResolved);
784
969
  }
785
970
  }
786
971
  }
787
- result.set(key, resolved);
788
- this.cache.set(fullCacheKey, resolved);
789
- this.cacheDescendants(fullCacheKey, resolved);
790
- } else {
791
- const prior = obj.priorValues.get(key);
792
- if (prior !== void 0) {
793
- const priorResolved = this.resolveVal(prior, obj);
794
- if (priorResolved !== void 0) {
795
- result.set(key, priorResolved);
796
- this.cache.set(fullCacheKey, priorResolved);
797
- this.cacheDescendants(fullCacheKey, priorResolved);
798
- }
799
- }
972
+ } finally {
973
+ this.resolvingFieldPath.pop();
800
974
  }
801
975
  }
802
976
  return { kind: "object", fields: result };
803
977
  }
978
+ /** S13a.12: walk resolved-object fields by segment text. A missing segment
979
+ * or a walk into a scalar/array is path-absent → undefined. */
980
+ navigateResolved(v, remainder) {
981
+ let cur = v;
982
+ for (const seg of remainder) {
983
+ if (cur.kind !== "object") return void 0;
984
+ const next = cur.fields.get(seg);
985
+ if (next === void 0) return void 0;
986
+ cur = next;
987
+ }
988
+ return cur;
989
+ }
804
990
  cacheDescendants(prefix, value) {
805
991
  if (value.kind !== "object") return;
806
992
  for (const [key, child] of value.fields) {
@@ -823,17 +1009,59 @@ var SubstitutionResolver = class {
823
1009
  if (isResObj(v)) return this.resolveResObj(v);
824
1010
  const hv = v;
825
1011
  if (hv.kind === "array") {
826
- return {
827
- kind: "array",
828
- items: hv.items.map(
829
- (item) => this.resolveVal(item, scope) ?? { kind: "scalar", raw: "null", valueType: "null" }
830
- )
831
- };
1012
+ const items = [];
1013
+ for (const item of hv.items) {
1014
+ const resolved = this.resolveVal(item, scope);
1015
+ if (resolved === void 0) continue;
1016
+ items.push(resolved);
1017
+ }
1018
+ return { kind: "array", items };
832
1019
  }
833
1020
  return hv;
834
1021
  }
835
1022
  resolveSubst(s, scope) {
836
- if (s.knownAbsent) return void 0;
1023
+ if (s.knownAbsent) {
1024
+ if (!s.optional) {
1025
+ if (this.opts.allowUnresolved) return s;
1026
+ const k = s.listSuffix ? `${segmentsToKey(s.segments)}[]` : segmentsToKey(s.segments);
1027
+ throw new ResolveError(
1028
+ `${this.originPrefix()}could not resolve substitution: \${${k}}`,
1029
+ k,
1030
+ s.line,
1031
+ s.col
1032
+ );
1033
+ }
1034
+ return void 0;
1035
+ }
1036
+ {
1037
+ const rfp = this.resolvingFieldPath;
1038
+ const sTexts = s.segments.map((seg) => seg.text);
1039
+ if (rfp.length > 0 && rfp.length < sTexts.length && stringSegmentsToKey(sTexts.slice(0, rfp.length)) === stringSegmentsToKey(rfp)) {
1040
+ const guardKey = stringSegmentsToKey(rfp);
1041
+ const prior = scope.priorValues.get(rfp[rfp.length - 1]);
1042
+ if (prior !== void 0 && !this.resolvingPrefixPriors.has(guardKey)) {
1043
+ this.resolvingPrefixPriors.add(guardKey);
1044
+ try {
1045
+ const priorResolved = this.resolveVal(prior, scope);
1046
+ if (priorResolved !== void 0) {
1047
+ const navigated = this.navigateResolved(priorResolved, sTexts.slice(rfp.length));
1048
+ if (navigated !== void 0) return navigated;
1049
+ }
1050
+ } finally {
1051
+ this.resolvingPrefixPriors.delete(guardKey);
1052
+ }
1053
+ }
1054
+ if (s.optional) return void 0;
1055
+ if (this.opts.allowUnresolved) return s;
1056
+ const k = s.listSuffix ? `${segmentsToKey(s.segments)}[]` : segmentsToKey(s.segments);
1057
+ throw new ResolveError(
1058
+ `${this.originPrefix()}could not resolve substitution: \${${k}}`,
1059
+ k,
1060
+ s.line,
1061
+ s.col
1062
+ );
1063
+ }
1064
+ }
837
1065
  const key = s.listSuffix ? `${segmentsToKey(s.segments)}[]` : segmentsToKey(s.segments);
838
1066
  if (this.cache.has(key)) return this.cache.get(key);
839
1067
  if (this.resolving.has(key)) {
@@ -1153,6 +1381,121 @@ function hoconValueToResObj2(value) {
1153
1381
  return obj;
1154
1382
  }
1155
1383
 
1384
+ // src/render-hocon.ts
1385
+ function renderHoconFromRoot(root) {
1386
+ const out = [];
1387
+ renderObjectBody(out, root, 0);
1388
+ return out.join("");
1389
+ }
1390
+ function renderObjectBody(out, o, depth) {
1391
+ const indent = " ".repeat(depth);
1392
+ for (const [k, v] of o.fields) {
1393
+ out.push(indent, renderKey(k));
1394
+ if (v.kind === "object") {
1395
+ out.push(" {");
1396
+ if (v.fields.size === 0) {
1397
+ out.push("}\n");
1398
+ continue;
1399
+ }
1400
+ out.push("\n");
1401
+ renderObjectBody(out, v, depth + 1);
1402
+ out.push(indent, "}\n");
1403
+ } else {
1404
+ out.push(" = ");
1405
+ renderValue(out, v, depth);
1406
+ out.push("\n");
1407
+ }
1408
+ }
1409
+ }
1410
+ function renderValue(out, v, depth) {
1411
+ switch (v.kind) {
1412
+ case "object": {
1413
+ if (v.fields.size === 0) {
1414
+ out.push("{}");
1415
+ return;
1416
+ }
1417
+ out.push("{\n");
1418
+ renderObjectBody(out, v, depth + 1);
1419
+ out.push(" ".repeat(depth), "}");
1420
+ return;
1421
+ }
1422
+ case "array":
1423
+ renderArray(out, v, depth);
1424
+ return;
1425
+ case "scalar":
1426
+ out.push(renderScalar(v));
1427
+ return;
1428
+ default:
1429
+ throw new ConfigError(
1430
+ "renderHocon: unrenderable value (config must be resolved data)",
1431
+ ""
1432
+ );
1433
+ }
1434
+ }
1435
+ function renderArray(out, a, depth) {
1436
+ if (a.items.length === 0) {
1437
+ out.push("[]");
1438
+ return;
1439
+ }
1440
+ const inner = " ".repeat(depth + 1);
1441
+ out.push("[\n");
1442
+ for (const e of a.items) {
1443
+ out.push(inner);
1444
+ renderValue(out, e, depth + 1);
1445
+ out.push("\n");
1446
+ }
1447
+ out.push(" ".repeat(depth), "]");
1448
+ }
1449
+ function renderScalar(s) {
1450
+ switch (s.valueType) {
1451
+ case "null":
1452
+ return "null";
1453
+ case "boolean":
1454
+ case "number":
1455
+ return s.raw;
1456
+ case "string":
1457
+ return renderString(s.raw);
1458
+ }
1459
+ }
1460
+ var SAFE_UNQUOTED_KEY = /^[A-Za-z0-9_-]+$/;
1461
+ function renderKey(k) {
1462
+ if (k === "include") return quoteString(k);
1463
+ return SAFE_UNQUOTED_KEY.test(k) ? k : quoteString(k);
1464
+ }
1465
+ var SAFE_BARE_STRING = /^[A-Za-z][A-Za-z0-9_-]*$/;
1466
+ var STRING_KEYWORDS = /* @__PURE__ */ new Set(["true", "false", "null", "yes", "no", "on", "off"]);
1467
+ function renderString(s) {
1468
+ return SAFE_BARE_STRING.test(s) && !STRING_KEYWORDS.has(s.toLowerCase()) ? s : quoteString(s);
1469
+ }
1470
+ function quoteString(s) {
1471
+ if (s.includes("\n") && !s.includes("\r") && !s.includes('"""') && !s.endsWith('"')) {
1472
+ return `"""${s}"""`;
1473
+ }
1474
+ let b = '"';
1475
+ for (const ch of s) {
1476
+ switch (ch) {
1477
+ case '"':
1478
+ b += '\\"';
1479
+ break;
1480
+ case "\\":
1481
+ b += "\\\\";
1482
+ break;
1483
+ case "\n":
1484
+ b += "\\n";
1485
+ break;
1486
+ case "\r":
1487
+ b += "\\r";
1488
+ break;
1489
+ case " ":
1490
+ b += "\\t";
1491
+ break;
1492
+ default:
1493
+ b += ch;
1494
+ }
1495
+ }
1496
+ return b + '"';
1497
+ }
1498
+
1156
1499
  // src/config.ts
1157
1500
  var Config = class _Config {
1158
1501
  constructor(root, opts) {
@@ -1458,6 +1801,36 @@ var Config = class _Config {
1458
1801
  toObject(opts) {
1459
1802
  return hoconToJs(this.root, opts?.nullPrototype === true);
1460
1803
  }
1804
+ /**
1805
+ * Renders this resolved Config as HOCON text (E18; lockstep with go.hocon's
1806
+ * `Config.RenderHOCON`, v1.11.0).
1807
+ *
1808
+ * The output round-trips: parsing it back yields the same value tree. That
1809
+ * is the correctness contract, not byte-for-byte formatting — a scalar is
1810
+ * quoted whenever leaving it bare would re-parse as a different type (a
1811
+ * string `"8080"` becomes `"8080"`, not `8080`), and left bare only when it
1812
+ * provably cannot.
1813
+ *
1814
+ * The Config must be resolved and hold only data (objects, arrays, string /
1815
+ * number / boolean / null scalars) — exactly what {@link fromMap} and the
1816
+ * format adapters produce. An unresolved placeholder throws
1817
+ * {@link ConfigError}; substitutions have no textual round trip through a
1818
+ * value tree.
1819
+ *
1820
+ * The root object's fields are emitted without enclosing braces, nested
1821
+ * objects as `key { … }`, arrays as newline-separated `[ … ]`, indented two
1822
+ * spaces. Source comments are not represented — a value tree does not carry
1823
+ * them.
1824
+ */
1825
+ renderHocon() {
1826
+ if (!this._resolved) {
1827
+ throw new ConfigError(
1828
+ "renderHocon: unrenderable unresolved substitution (config must be resolved data)",
1829
+ ""
1830
+ );
1831
+ }
1832
+ return renderHoconFromRoot(this.root);
1833
+ }
1461
1834
  /**
1462
1835
  * @internal Test-only: renders this resolved Config's value tree as canonical
1463
1836
  * JSON (sorted keys, no whitespace). Used by Layer-2 fixture tests to compare
@@ -1671,7 +2044,10 @@ function normalizeLeadingZeroNumber(raw) {
1671
2044
 
1672
2045
  // src/value-factory.ts
1673
2046
  function fromMap(values, originDescription) {
1674
- const fields = coerceObject(values, "");
2047
+ const fields = guardStackDepth(
2048
+ () => coerceObject(values, ""),
2049
+ (msg) => depthError(`fromMap: ${msg}`)
2050
+ );
1675
2051
  const root = { kind: "object", fields };
1676
2052
  return Config._fromResolvedValue(root, { originDescription });
1677
2053
  }