@o3co/ts.hocon 1.12.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
  }
@@ -598,6 +734,12 @@ var SubstitutionResolver = class {
598
734
  // Cross-impl with rs.hocon v1.5.1's resolving_field_path (same purpose,
599
735
  // same prefix-match semantics).
600
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();
601
743
  }
602
744
  /** Returns a prefix for error messages, including origin info when set. */
603
745
  originPrefix() {
@@ -612,46 +754,57 @@ var SubstitutionResolver = class {
612
754
  this.resolvingFieldPath.push(key);
613
755
  const fullCacheKey = stringSegmentsToKey(this.resolvingFieldPath);
614
756
  this.cache.delete(fullCacheKey);
615
- let resolved;
616
757
  try {
617
- resolved = this.resolveVal(val, obj);
618
- } finally {
619
- this.resolvingFieldPath.pop();
620
- }
621
- if (resolved !== void 0) {
622
- 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 {
623
780
  const prior = obj.priorValues.get(key);
624
781
  if (prior !== void 0) {
625
782
  const priorResolved = this.resolveVal(prior, obj);
626
- if (priorResolved !== void 0 && priorResolved.kind === "object") {
627
- const finalValue = deepMergeHoconValues(
628
- priorResolved,
629
- resolved
630
- );
631
- result.set(key, finalValue);
632
- this.cache.set(fullCacheKey, finalValue);
633
- this.cacheDescendants(fullCacheKey, finalValue);
634
- continue;
783
+ if (priorResolved !== void 0) {
784
+ result.set(key, priorResolved);
785
+ this.cache.set(fullCacheKey, priorResolved);
786
+ this.cacheDescendants(fullCacheKey, priorResolved);
635
787
  }
636
788
  }
637
789
  }
638
- result.set(key, resolved);
639
- this.cache.set(fullCacheKey, resolved);
640
- this.cacheDescendants(fullCacheKey, resolved);
641
- } else {
642
- const prior = obj.priorValues.get(key);
643
- if (prior !== void 0) {
644
- const priorResolved = this.resolveVal(prior, obj);
645
- if (priorResolved !== void 0) {
646
- result.set(key, priorResolved);
647
- this.cache.set(fullCacheKey, priorResolved);
648
- this.cacheDescendants(fullCacheKey, priorResolved);
649
- }
650
- }
790
+ } finally {
791
+ this.resolvingFieldPath.pop();
651
792
  }
652
793
  }
653
794
  return { kind: "object", fields: result };
654
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
+ }
655
808
  cacheDescendants(prefix, value) {
656
809
  if (value.kind !== "object") return;
657
810
  for (const [key, child] of value.fields) {
@@ -674,17 +827,59 @@ var SubstitutionResolver = class {
674
827
  if (isResObj(v)) return this.resolveResObj(v);
675
828
  const hv = v;
676
829
  if (hv.kind === "array") {
677
- return {
678
- kind: "array",
679
- items: hv.items.map(
680
- (item) => this.resolveVal(item, scope) ?? { kind: "scalar", raw: "null", valueType: "null" }
681
- )
682
- };
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 };
683
837
  }
684
838
  return hv;
685
839
  }
686
840
  resolveSubst(s, scope) {
687
- 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
+ }
688
883
  const key = s.listSuffix ? `${segmentsToKey(s.segments)}[]` : segmentsToKey(s.segments);
689
884
  if (this.cache.has(key)) return this.cache.get(key);
690
885
  if (this.resolving.has(key)) {
@@ -1004,6 +1199,121 @@ function hoconValueToResObj2(value) {
1004
1199
  return obj;
1005
1200
  }
1006
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
+
1007
1317
  // src/config.ts
1008
1318
  var Config = class _Config {
1009
1319
  constructor(root, opts) {
@@ -1309,6 +1619,36 @@ var Config = class _Config {
1309
1619
  toObject(opts) {
1310
1620
  return hoconToJs(this.root, opts?.nullPrototype === true);
1311
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
+ }
1312
1652
  /**
1313
1653
  * @internal Test-only: renders this resolved Config's value tree as canonical
1314
1654
  * JSON (sorted keys, no whitespace). Used by Layer-2 fixture tests to compare