@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.
@@ -34,13 +34,22 @@ function coerceNumber(value) {
34
34
  return Number(value);
35
35
  }
36
36
  var DURATION_UNITS = {
37
+ // S19.1–S19.3: the bare nano/micro/milli (+plural) aliases are part of the
38
+ // spec's unit lists (HOCON.md L1307–L1309) and accepted by Lightbend
39
+ // (typesafe-config 1.4.6 probe, 2026-08-18); they were missing here.
37
40
  ns: 1e-6,
41
+ nano: 1e-6,
42
+ nanos: 1e-6,
38
43
  nanosecond: 1e-6,
39
44
  nanoseconds: 1e-6,
40
45
  us: 1e-3,
46
+ micro: 1e-3,
47
+ micros: 1e-3,
41
48
  microsecond: 1e-3,
42
49
  microseconds: 1e-3,
43
50
  ms: 1,
51
+ milli: 1,
52
+ millis: 1,
44
53
  millisecond: 1,
45
54
  milliseconds: 1,
46
55
  s: 1e3,
@@ -92,37 +101,76 @@ function parseDuration(value, outputUnit = "ms") {
92
101
  return ms / divisor;
93
102
  }
94
103
  var BYTE_UNITS = {
104
+ // S21.1–S21.4 — the EXACT Lightbend/typesafe-config unit set (1.4.6 probe,
105
+ // 2026-08-18). Multi-letter units are case-sensitive: the SI decimal short
106
+ // form is `kB` (NOT `KB`/`kb` — Lightbend rejects both), higher decimals
107
+ // are `MB`…`YB`, binary prefixes are `Ki`/`KiB`…`Yi`/`YiB` with capital
108
+ // first letter, and long forms are lowercase only (`kilobyte`, never
109
+ // `Kilobyte`). Only the bare byte unit (`B`/`b`) and the single-letter
110
+ // -Xmx forms accept both cases. The old case-insensitive fallback and the
111
+ // lowercase alias rows accepted spellings Lightbend rejects — removed.
95
112
  B: 1,
113
+ b: 1,
96
114
  byte: 1,
97
115
  bytes: 1,
98
- KB: 1e3,
116
+ kB: 1e3,
99
117
  kilobyte: 1e3,
100
118
  kilobytes: 1e3,
101
- KiB: 1024,
102
- kibibyte: 1024,
103
- kibibytes: 1024,
104
119
  MB: 1e6,
105
120
  megabyte: 1e6,
106
121
  megabytes: 1e6,
107
- MiB: 1048576,
108
- mebibyte: 1048576,
109
- mebibytes: 1048576,
110
122
  GB: 1e9,
111
123
  gigabyte: 1e9,
112
124
  gigabytes: 1e9,
113
- GiB: 1073741824,
114
- gibibyte: 1073741824,
115
- gibibytes: 1073741824,
116
125
  TB: 1e12,
117
126
  terabyte: 1e12,
118
127
  terabytes: 1e12,
128
+ PB: 1e15,
129
+ petabyte: 1e15,
130
+ petabytes: 1e15,
131
+ EB: 1e18,
132
+ exabyte: 1e18,
133
+ exabytes: 1e18,
134
+ ZB: 1e21,
135
+ zettabyte: 1e21,
136
+ zettabytes: 1e21,
137
+ YB: 1e24,
138
+ yottabyte: 1e24,
139
+ yottabytes: 1e24,
140
+ Ki: 1024,
141
+ KiB: 1024,
142
+ kibibyte: 1024,
143
+ kibibytes: 1024,
144
+ Mi: 1048576,
145
+ MiB: 1048576,
146
+ mebibyte: 1048576,
147
+ mebibytes: 1048576,
148
+ Gi: 1073741824,
149
+ GiB: 1073741824,
150
+ gibibyte: 1073741824,
151
+ gibibytes: 1073741824,
152
+ Ti: 1099511627776,
119
153
  TiB: 1099511627776,
120
154
  tebibyte: 1099511627776,
121
155
  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.
156
+ Pi: 1024 ** 5,
157
+ PiB: 1024 ** 5,
158
+ pebibyte: 1024 ** 5,
159
+ pebibytes: 1024 ** 5,
160
+ Ei: 1024 ** 6,
161
+ EiB: 1024 ** 6,
162
+ exbibyte: 1024 ** 6,
163
+ exbibytes: 1024 ** 6,
164
+ Zi: 1024 ** 7,
165
+ ZiB: 1024 ** 7,
166
+ zebibyte: 1024 ** 7,
167
+ zebibytes: 1024 ** 7,
168
+ Yi: 1024 ** 8,
169
+ YiB: 1024 ** 8,
170
+ yobibyte: 1024 ** 8,
171
+ yobibytes: 1024 ** 8,
172
+ // S21.4 — single-letter abbreviations → powers of two (java -Xmx
173
+ // convention); Lightbend accepts BOTH cases here, through the full ladder.
126
174
  K: 1024,
127
175
  k: 1024,
128
176
  M: 1024 ** 2,
@@ -135,16 +183,10 @@ var BYTE_UNITS = {
135
183
  p: 1024 ** 5,
136
184
  E: 1024 ** 6,
137
185
  e: 1024 ** 6,
138
- // lowercase short-form aliases
139
- b: 1,
140
- kb: 1e3,
141
- kib: 1024,
142
- mb: 1e6,
143
- mib: 1048576,
144
- gb: 1e9,
145
- gib: 1073741824,
146
- tb: 1e12,
147
- tib: 1099511627776
186
+ Z: 1024 ** 7,
187
+ z: 1024 ** 7,
188
+ Y: 1024 ** 8,
189
+ y: 1024 ** 8
148
190
  };
149
191
  var OUTPUT_BYTE_UNITS = {
150
192
  B: 1,
@@ -182,10 +224,7 @@ function parseBytes(value, outputUnit = "B") {
182
224
  }
183
225
  return bytes2 / divisor;
184
226
  }
185
- let mult = BYTE_UNITS[unit];
186
- if (mult === void 0) {
187
- mult = BYTE_UNITS[unit.toLowerCase()];
188
- }
227
+ const mult = BYTE_UNITS[unit];
189
228
  if (mult === void 0) return NaN;
190
229
  const bytes = num * mult;
191
230
  if (Math.abs(bytes) > Number.MAX_SAFE_INTEGER) {
@@ -241,35 +280,114 @@ function stringSegmentsToKey(segments) {
241
280
  return t;
242
281
  }).join(".");
243
282
  }
283
+ function prefixSelfRefRemainder(s, fullKey) {
284
+ const texts = s.segments.map((seg) => seg.text);
285
+ if (!stringSegmentsToKey(texts).startsWith(fullKey + ".")) return null;
286
+ for (let n = 1; n < texts.length; n++) {
287
+ if (stringSegmentsToKey(texts.slice(0, n)) === fullKey) return texts.slice(n);
288
+ }
289
+ return null;
290
+ }
291
+ function navigateResolverValue(v, remainder) {
292
+ let cur = v;
293
+ for (const seg of remainder) {
294
+ if (isSubst(cur) || isConcat(cur)) return null;
295
+ if (isResObj(cur)) {
296
+ const next = cur.fields.get(seg);
297
+ if (next === void 0) return void 0;
298
+ cur = next;
299
+ continue;
300
+ }
301
+ const hv = cur;
302
+ if (hv.kind === "object") {
303
+ const next = hv.fields.get(seg);
304
+ if (next === void 0) return void 0;
305
+ cur = next;
306
+ continue;
307
+ }
308
+ return void 0;
309
+ }
310
+ return cur;
311
+ }
312
+ function foldPrefixSelfRef(s, replacement, remainder) {
313
+ const navigated = navigateResolverValue(replacement, remainder);
314
+ if (navigated === null) return s;
315
+ if (navigated === void 0) {
316
+ return {
317
+ _kind: "subst-placeholder",
318
+ segments: s.segments.map((seg) => ({ text: seg.text, line: seg.line, col: seg.col })),
319
+ optional: s.optional,
320
+ knownAbsent: true,
321
+ listSuffix: s.listSuffix,
322
+ line: s.line,
323
+ col: s.col,
324
+ prefixLen: s.prefixLen
325
+ };
326
+ }
327
+ return cloneResolverValue(navigated);
328
+ }
329
+ function mergeResObjLayers(base, top) {
330
+ const fields = new Map(base.fields);
331
+ for (const [k, tv] of top.fields) {
332
+ const bv = fields.get(k);
333
+ if (bv !== void 0 && isResObj(bv) && isResObj(tv)) {
334
+ fields.set(k, mergeResObjLayers(bv, tv));
335
+ } else {
336
+ fields.set(k, tv);
337
+ }
338
+ }
339
+ const priorValues = new Map(base.priorValues);
340
+ for (const [k, pv] of top.priorValues) priorValues.set(k, pv);
341
+ const resetKeys = new Set(base.resetKeys);
342
+ for (const k of top.resetKeys) resetKeys.add(k);
343
+ return {
344
+ _kind: "res-obj",
345
+ fields,
346
+ priorValues,
347
+ resetKeys
348
+ };
349
+ }
244
350
  function containsSelfRef(v, fullKey) {
245
- if (isSubst(v)) return !v.knownAbsent && substFullKey(v) === fullKey;
246
- if (isConcat(v)) return v.nodes.some((n) => containsSelfRef(n, fullKey));
351
+ return containsSelfRefInner(v, fullKey, true);
352
+ }
353
+ function containsSelfRefInner(v, fullKey, allowPrefix) {
354
+ if (isSubst(v))
355
+ return !v.knownAbsent && (substFullKey(v) === fullKey || allowPrefix && prefixSelfRefRemainder(v, fullKey) !== null);
356
+ if (isConcat(v)) return v.nodes.some((n) => containsSelfRefInner(n, fullKey, allowPrefix));
247
357
  if (isResObj(v)) {
248
358
  for (const f of v.fields.values()) {
249
- if (containsSelfRef(f, fullKey)) return true;
359
+ if (containsSelfRefInner(f, fullKey, false)) return true;
250
360
  }
251
361
  return false;
252
362
  }
253
363
  const hv = v;
254
364
  if (hv.kind === "array") {
255
- return hv.items.some((item) => containsSelfRef(item, fullKey));
365
+ return hv.items.some((item) => containsSelfRefInner(item, fullKey, allowPrefix));
256
366
  }
257
367
  if (hv.kind === "object") {
258
368
  for (const f of hv.fields.values()) {
259
- if (containsSelfRef(f, fullKey)) return true;
369
+ if (containsSelfRefInner(f, fullKey, false)) return true;
260
370
  }
261
371
  return false;
262
372
  }
263
373
  return false;
264
374
  }
265
375
  function foldSelfRef(v, fullKey, replacement) {
376
+ return foldSelfRefInner(v, fullKey, replacement, true);
377
+ }
378
+ function foldSelfRefInner(v, fullKey, replacement, allowPrefix) {
266
379
  if (isSubst(v)) {
267
- return substFullKey(v) === fullKey ? replacement : v;
380
+ if (substFullKey(v) === fullKey) return replacement;
381
+ if (allowPrefix) {
382
+ const remainder = prefixSelfRefRemainder(v, fullKey);
383
+ if (remainder !== null) return foldPrefixSelfRef(v, replacement, remainder);
384
+ }
385
+ return v;
268
386
  }
269
387
  if (isConcat(v)) {
270
388
  return {
271
389
  _kind: "concat-placeholder",
272
- nodes: v.nodes.map((n) => foldSelfRef(n, fullKey, replacement)),
390
+ nodes: v.nodes.map((n) => foldSelfRefInner(n, fullKey, replacement, allowPrefix)),
273
391
  line: v.line,
274
392
  col: v.col
275
393
  };
@@ -277,7 +395,7 @@ function foldSelfRef(v, fullKey, replacement) {
277
395
  if (isResObj(v)) {
278
396
  const newFields = /* @__PURE__ */ new Map();
279
397
  for (const [k, val] of v.fields) {
280
- newFields.set(k, foldSelfRef(val, fullKey, replacement));
398
+ newFields.set(k, foldSelfRefInner(val, fullKey, replacement, false));
281
399
  }
282
400
  return { _kind: "res-obj", fields: newFields, priorValues: new Map(v.priorValues), resetKeys: new Set(v.resetKeys) };
283
401
  }
@@ -285,13 +403,15 @@ function foldSelfRef(v, fullKey, replacement) {
285
403
  if (hv.kind === "array") {
286
404
  return {
287
405
  kind: "array",
288
- items: hv.items.map((item) => foldSelfRef(item, fullKey, replacement))
406
+ items: hv.items.map(
407
+ (item) => foldSelfRefInner(item, fullKey, replacement, allowPrefix)
408
+ )
289
409
  };
290
410
  }
291
411
  if (hv.kind === "object") {
292
412
  const newFields = /* @__PURE__ */ new Map();
293
413
  for (const [k, val] of hv.fields) {
294
- newFields.set(k, foldSelfRef(val, fullKey, replacement));
414
+ newFields.set(k, foldSelfRefInner(val, fullKey, replacement, false));
295
415
  }
296
416
  return { kind: "object", fields: newFields };
297
417
  }
@@ -300,10 +420,26 @@ function foldSelfRef(v, fullKey, replacement) {
300
420
  function foldOrSkipPrior(prior, fullKey, old) {
301
421
  if (!containsSelfRef(prior, fullKey)) return cloneResolverValue(prior);
302
422
  if (old === void 0) return foldOptionalSelfRefAbsent(prior, fullKey);
423
+ if (isSubst(prior)) {
424
+ const remainder = prefixSelfRefRemainder(prior, fullKey);
425
+ if (remainder !== null) {
426
+ const navigated = navigateResolverValue(old, remainder);
427
+ if (navigated === void 0 && prior.optional) return cloneResolverValue(old);
428
+ if (navigated !== null && navigated !== void 0 && isResObj(navigated) && isResObj(old)) {
429
+ return mergeResObjLayers(
430
+ cloneResolverValue(old),
431
+ cloneResolverValue(navigated)
432
+ );
433
+ }
434
+ }
435
+ }
303
436
  return foldSelfRef(prior, fullKey, old);
304
437
  }
305
438
  function foldOptionalSelfRefAbsent(v, fullKey) {
306
- if (isSubst(v) && substFullKey(v) === fullKey) {
439
+ return foldOptionalSelfRefAbsentInner(v, fullKey, true);
440
+ }
441
+ function foldOptionalSelfRefAbsentInner(v, fullKey, allowPrefix) {
442
+ if (isSubst(v) && (substFullKey(v) === fullKey || allowPrefix && prefixSelfRefRemainder(v, fullKey) !== null)) {
307
443
  if (!v.optional) return void 0;
308
444
  return {
309
445
  _kind: "subst-placeholder",
@@ -319,7 +455,7 @@ function foldOptionalSelfRefAbsent(v, fullKey) {
319
455
  if (isConcat(v)) {
320
456
  const nodes = [];
321
457
  for (const node of v.nodes) {
322
- const folded = foldOptionalSelfRefAbsent(node, fullKey);
458
+ const folded = foldOptionalSelfRefAbsentInner(node, fullKey, allowPrefix);
323
459
  if (folded === void 0) return void 0;
324
460
  nodes.push(folded);
325
461
  }
@@ -328,7 +464,7 @@ function foldOptionalSelfRefAbsent(v, fullKey) {
328
464
  if (isResObj(v)) {
329
465
  const fields = /* @__PURE__ */ new Map();
330
466
  for (const [key, value] of v.fields) {
331
- const folded = foldOptionalSelfRefAbsent(value, fullKey);
467
+ const folded = foldOptionalSelfRefAbsentInner(value, fullKey, false);
332
468
  if (folded === void 0) return void 0;
333
469
  fields.set(key, folded);
334
470
  }
@@ -338,7 +474,7 @@ function foldOptionalSelfRefAbsent(v, fullKey) {
338
474
  if (hv.kind === "array") {
339
475
  const items = [];
340
476
  for (const item of hv.items) {
341
- const folded = foldOptionalSelfRefAbsent(item, fullKey);
477
+ const folded = foldOptionalSelfRefAbsentInner(item, fullKey, allowPrefix);
342
478
  if (folded === void 0) return void 0;
343
479
  items.push(folded);
344
480
  }
@@ -347,7 +483,7 @@ function foldOptionalSelfRefAbsent(v, fullKey) {
347
483
  if (hv.kind === "object") {
348
484
  const fields = /* @__PURE__ */ new Map();
349
485
  for (const [key, value] of hv.fields) {
350
- const folded = foldOptionalSelfRefAbsent(value, fullKey);
486
+ const folded = foldOptionalSelfRefAbsentInner(value, fullKey, false);
351
487
  if (folded === void 0) return void 0;
352
488
  fields.set(key, folded);
353
489
  }
@@ -529,6 +665,31 @@ function stripBom(input) {
529
665
  return input.charCodeAt(0) === 65279 ? input.slice(1) : input;
530
666
  }
531
667
 
668
+ // src/internal/depth.ts
669
+ var MAX_PATH_SEGMENTS = 64;
670
+ function tooDeep(segments) {
671
+ return segments > MAX_PATH_SEGMENTS;
672
+ }
673
+ function guardStackDepth(fn, wrap) {
674
+ try {
675
+ return fn();
676
+ } catch (e) {
677
+ if (!isStackOverflow(e)) throw e;
678
+ throw wrap(STACK_MESSAGE);
679
+ }
680
+ }
681
+ var STACK_MESSAGE = "input is nested too deeply for this engine's stack, or contains a cycle (flatten the document, or break the cycle)";
682
+ function isStackOverflow(e) {
683
+ if (!(e instanceof RangeError)) return false;
684
+ const m = e.message.toLowerCase();
685
+ return m.includes("call stack") || // V8: "Maximum call stack size exceeded"
686
+ m.includes("recursion") || // SpiderMonkey: "too much recursion"
687
+ m.includes("stack overflow");
688
+ }
689
+ function depthError(message) {
690
+ return new ConfigError(message, "");
691
+ }
692
+
532
693
  // src/internal/properties/properties.ts
533
694
  function parseProperties(input) {
534
695
  const pairs = [];
@@ -536,7 +697,15 @@ function parseProperties(input) {
536
697
  const [rawKey, rawValue] = splitKeyValue(text);
537
698
  const key = unescapeProps(rawKey, line);
538
699
  if (key === "") continue;
539
- pairs.push([key.split("."), unescapeProps(rawValue, line)]);
700
+ const segments = key.split(".");
701
+ if (tooDeep(segments.length)) {
702
+ throw new ParseError(
703
+ `key maps to a path ${segments.length} segments deep, over the limit of ${MAX_PATH_SEGMENTS}`,
704
+ line,
705
+ 1
706
+ );
707
+ }
708
+ pairs.push([segments, unescapeProps(rawValue, line)]);
540
709
  }
541
710
  return nestPairs(pairs);
542
711
  }
@@ -711,6 +880,12 @@ var SubstitutionResolver = class {
711
880
  // Cross-impl with rs.hocon v1.5.1's resolving_field_path (same purpose,
712
881
  // same prefix-match semantics).
713
882
  this.resolvingFieldPath = [];
883
+ // S13a.12 recursion guard: field keys whose prior is currently being
884
+ // resolved through the prefix-self-ref branch. Saved priors are
885
+ // prefix-self-ref-free by the fold invariant, so re-entry only happens on
886
+ // a shape the fold could not see through (a live subst mid-navigation) —
887
+ // report it as a cycle instead of recursing forever.
888
+ this.resolvingPrefixPriors = /* @__PURE__ */ new Set();
714
889
  }
715
890
  /** Returns a prefix for error messages, including origin info when set. */
716
891
  originPrefix() {
@@ -725,46 +900,57 @@ var SubstitutionResolver = class {
725
900
  this.resolvingFieldPath.push(key);
726
901
  const fullCacheKey = stringSegmentsToKey(this.resolvingFieldPath);
727
902
  this.cache.delete(fullCacheKey);
728
- let resolved;
729
903
  try {
730
- resolved = this.resolveVal(val, obj);
731
- } finally {
732
- this.resolvingFieldPath.pop();
733
- }
734
- if (resolved !== void 0) {
735
- if (resolved.kind === "object") {
904
+ const resolved = this.resolveVal(val, obj);
905
+ if (resolved !== void 0) {
906
+ if (resolved.kind === "object") {
907
+ const prior = obj.priorValues.get(key);
908
+ if (prior !== void 0) {
909
+ const priorResolved = this.resolveVal(prior, obj);
910
+ if (priorResolved !== void 0 && priorResolved.kind === "object") {
911
+ const finalValue = deepMergeHoconValues(
912
+ priorResolved,
913
+ resolved
914
+ );
915
+ result.set(key, finalValue);
916
+ this.cache.set(fullCacheKey, finalValue);
917
+ this.cacheDescendants(fullCacheKey, finalValue);
918
+ continue;
919
+ }
920
+ }
921
+ }
922
+ result.set(key, resolved);
923
+ this.cache.set(fullCacheKey, resolved);
924
+ this.cacheDescendants(fullCacheKey, resolved);
925
+ } else {
736
926
  const prior = obj.priorValues.get(key);
737
927
  if (prior !== void 0) {
738
928
  const priorResolved = this.resolveVal(prior, obj);
739
- if (priorResolved !== void 0 && priorResolved.kind === "object") {
740
- const finalValue = deepMergeHoconValues(
741
- priorResolved,
742
- resolved
743
- );
744
- result.set(key, finalValue);
745
- this.cache.set(fullCacheKey, finalValue);
746
- this.cacheDescendants(fullCacheKey, finalValue);
747
- continue;
929
+ if (priorResolved !== void 0) {
930
+ result.set(key, priorResolved);
931
+ this.cache.set(fullCacheKey, priorResolved);
932
+ this.cacheDescendants(fullCacheKey, priorResolved);
748
933
  }
749
934
  }
750
935
  }
751
- result.set(key, resolved);
752
- this.cache.set(fullCacheKey, resolved);
753
- this.cacheDescendants(fullCacheKey, resolved);
754
- } else {
755
- const prior = obj.priorValues.get(key);
756
- if (prior !== void 0) {
757
- const priorResolved = this.resolveVal(prior, obj);
758
- if (priorResolved !== void 0) {
759
- result.set(key, priorResolved);
760
- this.cache.set(fullCacheKey, priorResolved);
761
- this.cacheDescendants(fullCacheKey, priorResolved);
762
- }
763
- }
936
+ } finally {
937
+ this.resolvingFieldPath.pop();
764
938
  }
765
939
  }
766
940
  return { kind: "object", fields: result };
767
941
  }
942
+ /** S13a.12: walk resolved-object fields by segment text. A missing segment
943
+ * or a walk into a scalar/array is path-absent → undefined. */
944
+ navigateResolved(v, remainder) {
945
+ let cur = v;
946
+ for (const seg of remainder) {
947
+ if (cur.kind !== "object") return void 0;
948
+ const next = cur.fields.get(seg);
949
+ if (next === void 0) return void 0;
950
+ cur = next;
951
+ }
952
+ return cur;
953
+ }
768
954
  cacheDescendants(prefix, value) {
769
955
  if (value.kind !== "object") return;
770
956
  for (const [key, child] of value.fields) {
@@ -787,17 +973,59 @@ var SubstitutionResolver = class {
787
973
  if (isResObj(v)) return this.resolveResObj(v);
788
974
  const hv = v;
789
975
  if (hv.kind === "array") {
790
- return {
791
- kind: "array",
792
- items: hv.items.map(
793
- (item) => this.resolveVal(item, scope) ?? { kind: "scalar", raw: "null", valueType: "null" }
794
- )
795
- };
976
+ const items = [];
977
+ for (const item of hv.items) {
978
+ const resolved = this.resolveVal(item, scope);
979
+ if (resolved === void 0) continue;
980
+ items.push(resolved);
981
+ }
982
+ return { kind: "array", items };
796
983
  }
797
984
  return hv;
798
985
  }
799
986
  resolveSubst(s, scope) {
800
- if (s.knownAbsent) return void 0;
987
+ if (s.knownAbsent) {
988
+ if (!s.optional) {
989
+ if (this.opts.allowUnresolved) return s;
990
+ const k = s.listSuffix ? `${segmentsToKey(s.segments)}[]` : segmentsToKey(s.segments);
991
+ throw new ResolveError(
992
+ `${this.originPrefix()}could not resolve substitution: \${${k}}`,
993
+ k,
994
+ s.line,
995
+ s.col
996
+ );
997
+ }
998
+ return void 0;
999
+ }
1000
+ {
1001
+ const rfp = this.resolvingFieldPath;
1002
+ const sTexts = s.segments.map((seg) => seg.text);
1003
+ if (rfp.length > 0 && rfp.length < sTexts.length && stringSegmentsToKey(sTexts.slice(0, rfp.length)) === stringSegmentsToKey(rfp)) {
1004
+ const guardKey = stringSegmentsToKey(rfp);
1005
+ const prior = scope.priorValues.get(rfp[rfp.length - 1]);
1006
+ if (prior !== void 0 && !this.resolvingPrefixPriors.has(guardKey)) {
1007
+ this.resolvingPrefixPriors.add(guardKey);
1008
+ try {
1009
+ const priorResolved = this.resolveVal(prior, scope);
1010
+ if (priorResolved !== void 0) {
1011
+ const navigated = this.navigateResolved(priorResolved, sTexts.slice(rfp.length));
1012
+ if (navigated !== void 0) return navigated;
1013
+ }
1014
+ } finally {
1015
+ this.resolvingPrefixPriors.delete(guardKey);
1016
+ }
1017
+ }
1018
+ if (s.optional) return void 0;
1019
+ if (this.opts.allowUnresolved) return s;
1020
+ const k = s.listSuffix ? `${segmentsToKey(s.segments)}[]` : segmentsToKey(s.segments);
1021
+ throw new ResolveError(
1022
+ `${this.originPrefix()}could not resolve substitution: \${${k}}`,
1023
+ k,
1024
+ s.line,
1025
+ s.col
1026
+ );
1027
+ }
1028
+ }
801
1029
  const key = s.listSuffix ? `${segmentsToKey(s.segments)}[]` : segmentsToKey(s.segments);
802
1030
  if (this.cache.has(key)) return this.cache.get(key);
803
1031
  if (this.resolving.has(key)) {
@@ -1117,6 +1345,121 @@ function hoconValueToResObj2(value) {
1117
1345
  return obj;
1118
1346
  }
1119
1347
 
1348
+ // src/render-hocon.ts
1349
+ function renderHoconFromRoot(root) {
1350
+ const out = [];
1351
+ renderObjectBody(out, root, 0);
1352
+ return out.join("");
1353
+ }
1354
+ function renderObjectBody(out, o, depth) {
1355
+ const indent = " ".repeat(depth);
1356
+ for (const [k, v] of o.fields) {
1357
+ out.push(indent, renderKey(k));
1358
+ if (v.kind === "object") {
1359
+ out.push(" {");
1360
+ if (v.fields.size === 0) {
1361
+ out.push("}\n");
1362
+ continue;
1363
+ }
1364
+ out.push("\n");
1365
+ renderObjectBody(out, v, depth + 1);
1366
+ out.push(indent, "}\n");
1367
+ } else {
1368
+ out.push(" = ");
1369
+ renderValue(out, v, depth);
1370
+ out.push("\n");
1371
+ }
1372
+ }
1373
+ }
1374
+ function renderValue(out, v, depth) {
1375
+ switch (v.kind) {
1376
+ case "object": {
1377
+ if (v.fields.size === 0) {
1378
+ out.push("{}");
1379
+ return;
1380
+ }
1381
+ out.push("{\n");
1382
+ renderObjectBody(out, v, depth + 1);
1383
+ out.push(" ".repeat(depth), "}");
1384
+ return;
1385
+ }
1386
+ case "array":
1387
+ renderArray(out, v, depth);
1388
+ return;
1389
+ case "scalar":
1390
+ out.push(renderScalar(v));
1391
+ return;
1392
+ default:
1393
+ throw new ConfigError(
1394
+ "renderHocon: unrenderable value (config must be resolved data)",
1395
+ ""
1396
+ );
1397
+ }
1398
+ }
1399
+ function renderArray(out, a, depth) {
1400
+ if (a.items.length === 0) {
1401
+ out.push("[]");
1402
+ return;
1403
+ }
1404
+ const inner = " ".repeat(depth + 1);
1405
+ out.push("[\n");
1406
+ for (const e of a.items) {
1407
+ out.push(inner);
1408
+ renderValue(out, e, depth + 1);
1409
+ out.push("\n");
1410
+ }
1411
+ out.push(" ".repeat(depth), "]");
1412
+ }
1413
+ function renderScalar(s) {
1414
+ switch (s.valueType) {
1415
+ case "null":
1416
+ return "null";
1417
+ case "boolean":
1418
+ case "number":
1419
+ return s.raw;
1420
+ case "string":
1421
+ return renderString(s.raw);
1422
+ }
1423
+ }
1424
+ var SAFE_UNQUOTED_KEY = /^[A-Za-z0-9_-]+$/;
1425
+ function renderKey(k) {
1426
+ if (k === "include") return quoteString(k);
1427
+ return SAFE_UNQUOTED_KEY.test(k) ? k : quoteString(k);
1428
+ }
1429
+ var SAFE_BARE_STRING = /^[A-Za-z][A-Za-z0-9_-]*$/;
1430
+ var STRING_KEYWORDS = /* @__PURE__ */ new Set(["true", "false", "null", "yes", "no", "on", "off"]);
1431
+ function renderString(s) {
1432
+ return SAFE_BARE_STRING.test(s) && !STRING_KEYWORDS.has(s.toLowerCase()) ? s : quoteString(s);
1433
+ }
1434
+ function quoteString(s) {
1435
+ if (s.includes("\n") && !s.includes("\r") && !s.includes('"""') && !s.endsWith('"')) {
1436
+ return `"""${s}"""`;
1437
+ }
1438
+ let b = '"';
1439
+ for (const ch of s) {
1440
+ switch (ch) {
1441
+ case '"':
1442
+ b += '\\"';
1443
+ break;
1444
+ case "\\":
1445
+ b += "\\\\";
1446
+ break;
1447
+ case "\n":
1448
+ b += "\\n";
1449
+ break;
1450
+ case "\r":
1451
+ b += "\\r";
1452
+ break;
1453
+ case " ":
1454
+ b += "\\t";
1455
+ break;
1456
+ default:
1457
+ b += ch;
1458
+ }
1459
+ }
1460
+ return b + '"';
1461
+ }
1462
+
1120
1463
  // src/config.ts
1121
1464
  var Config = class _Config {
1122
1465
  constructor(root, opts) {
@@ -1422,6 +1765,36 @@ var Config = class _Config {
1422
1765
  toObject(opts) {
1423
1766
  return hoconToJs(this.root, opts?.nullPrototype === true);
1424
1767
  }
1768
+ /**
1769
+ * Renders this resolved Config as HOCON text (E18; lockstep with go.hocon's
1770
+ * `Config.RenderHOCON`, v1.11.0).
1771
+ *
1772
+ * The output round-trips: parsing it back yields the same value tree. That
1773
+ * is the correctness contract, not byte-for-byte formatting — a scalar is
1774
+ * quoted whenever leaving it bare would re-parse as a different type (a
1775
+ * string `"8080"` becomes `"8080"`, not `8080`), and left bare only when it
1776
+ * provably cannot.
1777
+ *
1778
+ * The Config must be resolved and hold only data (objects, arrays, string /
1779
+ * number / boolean / null scalars) — exactly what {@link fromMap} and the
1780
+ * format adapters produce. An unresolved placeholder throws
1781
+ * {@link ConfigError}; substitutions have no textual round trip through a
1782
+ * value tree.
1783
+ *
1784
+ * The root object's fields are emitted without enclosing braces, nested
1785
+ * objects as `key { … }`, arrays as newline-separated `[ … ]`, indented two
1786
+ * spaces. Source comments are not represented — a value tree does not carry
1787
+ * them.
1788
+ */
1789
+ renderHocon() {
1790
+ if (!this._resolved) {
1791
+ throw new ConfigError(
1792
+ "renderHocon: unrenderable unresolved substitution (config must be resolved data)",
1793
+ ""
1794
+ );
1795
+ }
1796
+ return renderHoconFromRoot(this.root);
1797
+ }
1425
1798
  /**
1426
1799
  * @internal Test-only: renders this resolved Config's value tree as canonical
1427
1800
  * JSON (sorted keys, no whitespace). Used by Layer-2 fixture tests to compare
@@ -1635,7 +2008,10 @@ function normalizeLeadingZeroNumber(raw) {
1635
2008
 
1636
2009
  // src/value-factory.ts
1637
2010
  function fromMap(values, originDescription) {
1638
- const fields = coerceObject(values, "");
2011
+ const fields = guardStackDepth(
2012
+ () => coerceObject(values, ""),
2013
+ (msg) => depthError(`fromMap: ${msg}`)
2014
+ );
1639
2015
  const root = { kind: "object", fields };
1640
2016
  return Config._fromResolvedValue(root, { originDescription });
1641
2017
  }