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