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