@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
  }
@@ -671,6 +807,12 @@ var SubstitutionResolver = class {
671
807
  // Cross-impl with rs.hocon v1.5.1's resolving_field_path (same purpose,
672
808
  // same prefix-match semantics).
673
809
  this.resolvingFieldPath = [];
810
+ // S13a.12 recursion guard: field keys whose prior is currently being
811
+ // resolved through the prefix-self-ref branch. Saved priors are
812
+ // prefix-self-ref-free by the fold invariant, so re-entry only happens on
813
+ // a shape the fold could not see through (a live subst mid-navigation) —
814
+ // report it as a cycle instead of recursing forever.
815
+ this.resolvingPrefixPriors = /* @__PURE__ */ new Set();
674
816
  }
675
817
  /** Returns a prefix for error messages, including origin info when set. */
676
818
  originPrefix() {
@@ -685,46 +827,57 @@ var SubstitutionResolver = class {
685
827
  this.resolvingFieldPath.push(key);
686
828
  const fullCacheKey = stringSegmentsToKey(this.resolvingFieldPath);
687
829
  this.cache.delete(fullCacheKey);
688
- let resolved;
689
830
  try {
690
- resolved = this.resolveVal(val, obj);
691
- } finally {
692
- this.resolvingFieldPath.pop();
693
- }
694
- if (resolved !== void 0) {
695
- if (resolved.kind === "object") {
831
+ const resolved = this.resolveVal(val, obj);
832
+ if (resolved !== void 0) {
833
+ if (resolved.kind === "object") {
834
+ const prior = obj.priorValues.get(key);
835
+ if (prior !== void 0) {
836
+ const priorResolved = this.resolveVal(prior, obj);
837
+ if (priorResolved !== void 0 && priorResolved.kind === "object") {
838
+ const finalValue = deepMergeHoconValues(
839
+ priorResolved,
840
+ resolved
841
+ );
842
+ result.set(key, finalValue);
843
+ this.cache.set(fullCacheKey, finalValue);
844
+ this.cacheDescendants(fullCacheKey, finalValue);
845
+ continue;
846
+ }
847
+ }
848
+ }
849
+ result.set(key, resolved);
850
+ this.cache.set(fullCacheKey, resolved);
851
+ this.cacheDescendants(fullCacheKey, resolved);
852
+ } else {
696
853
  const prior = obj.priorValues.get(key);
697
854
  if (prior !== void 0) {
698
855
  const priorResolved = this.resolveVal(prior, obj);
699
- if (priorResolved !== void 0 && priorResolved.kind === "object") {
700
- const finalValue = deepMergeHoconValues(
701
- priorResolved,
702
- resolved
703
- );
704
- result.set(key, finalValue);
705
- this.cache.set(fullCacheKey, finalValue);
706
- this.cacheDescendants(fullCacheKey, finalValue);
707
- continue;
856
+ if (priorResolved !== void 0) {
857
+ result.set(key, priorResolved);
858
+ this.cache.set(fullCacheKey, priorResolved);
859
+ this.cacheDescendants(fullCacheKey, priorResolved);
708
860
  }
709
861
  }
710
862
  }
711
- result.set(key, resolved);
712
- this.cache.set(fullCacheKey, resolved);
713
- this.cacheDescendants(fullCacheKey, resolved);
714
- } else {
715
- const prior = obj.priorValues.get(key);
716
- if (prior !== void 0) {
717
- const priorResolved = this.resolveVal(prior, obj);
718
- if (priorResolved !== void 0) {
719
- result.set(key, priorResolved);
720
- this.cache.set(fullCacheKey, priorResolved);
721
- this.cacheDescendants(fullCacheKey, priorResolved);
722
- }
723
- }
863
+ } finally {
864
+ this.resolvingFieldPath.pop();
724
865
  }
725
866
  }
726
867
  return { kind: "object", fields: result };
727
868
  }
869
+ /** S13a.12: walk resolved-object fields by segment text. A missing segment
870
+ * or a walk into a scalar/array is path-absent → undefined. */
871
+ navigateResolved(v, remainder) {
872
+ let cur = v;
873
+ for (const seg of remainder) {
874
+ if (cur.kind !== "object") return void 0;
875
+ const next = cur.fields.get(seg);
876
+ if (next === void 0) return void 0;
877
+ cur = next;
878
+ }
879
+ return cur;
880
+ }
728
881
  cacheDescendants(prefix, value) {
729
882
  if (value.kind !== "object") return;
730
883
  for (const [key, child] of value.fields) {
@@ -747,17 +900,59 @@ var SubstitutionResolver = class {
747
900
  if (isResObj(v)) return this.resolveResObj(v);
748
901
  const hv = v;
749
902
  if (hv.kind === "array") {
750
- return {
751
- kind: "array",
752
- items: hv.items.map(
753
- (item) => this.resolveVal(item, scope) ?? { kind: "scalar", raw: "null", valueType: "null" }
754
- )
755
- };
903
+ const items = [];
904
+ for (const item of hv.items) {
905
+ const resolved = this.resolveVal(item, scope);
906
+ if (resolved === void 0) continue;
907
+ items.push(resolved);
908
+ }
909
+ return { kind: "array", items };
756
910
  }
757
911
  return hv;
758
912
  }
759
913
  resolveSubst(s, scope) {
760
- if (s.knownAbsent) return void 0;
914
+ if (s.knownAbsent) {
915
+ if (!s.optional) {
916
+ if (this.opts.allowUnresolved) return s;
917
+ const k = s.listSuffix ? `${segmentsToKey(s.segments)}[]` : segmentsToKey(s.segments);
918
+ throw new ResolveError(
919
+ `${this.originPrefix()}could not resolve substitution: \${${k}}`,
920
+ k,
921
+ s.line,
922
+ s.col
923
+ );
924
+ }
925
+ return void 0;
926
+ }
927
+ {
928
+ const rfp = this.resolvingFieldPath;
929
+ const sTexts = s.segments.map((seg) => seg.text);
930
+ if (rfp.length > 0 && rfp.length < sTexts.length && stringSegmentsToKey(sTexts.slice(0, rfp.length)) === stringSegmentsToKey(rfp)) {
931
+ const guardKey = stringSegmentsToKey(rfp);
932
+ const prior = scope.priorValues.get(rfp[rfp.length - 1]);
933
+ if (prior !== void 0 && !this.resolvingPrefixPriors.has(guardKey)) {
934
+ this.resolvingPrefixPriors.add(guardKey);
935
+ try {
936
+ const priorResolved = this.resolveVal(prior, scope);
937
+ if (priorResolved !== void 0) {
938
+ const navigated = this.navigateResolved(priorResolved, sTexts.slice(rfp.length));
939
+ if (navigated !== void 0) return navigated;
940
+ }
941
+ } finally {
942
+ this.resolvingPrefixPriors.delete(guardKey);
943
+ }
944
+ }
945
+ if (s.optional) return void 0;
946
+ if (this.opts.allowUnresolved) return s;
947
+ const k = s.listSuffix ? `${segmentsToKey(s.segments)}[]` : segmentsToKey(s.segments);
948
+ throw new ResolveError(
949
+ `${this.originPrefix()}could not resolve substitution: \${${k}}`,
950
+ k,
951
+ s.line,
952
+ s.col
953
+ );
954
+ }
955
+ }
761
956
  const key = s.listSuffix ? `${segmentsToKey(s.segments)}[]` : segmentsToKey(s.segments);
762
957
  if (this.cache.has(key)) return this.cache.get(key);
763
958
  if (this.resolving.has(key)) {
@@ -1077,6 +1272,121 @@ function hoconValueToResObj2(value) {
1077
1272
  return obj;
1078
1273
  }
1079
1274
 
1275
+ // src/render-hocon.ts
1276
+ function renderHoconFromRoot(root) {
1277
+ const out = [];
1278
+ renderObjectBody(out, root, 0);
1279
+ return out.join("");
1280
+ }
1281
+ function renderObjectBody(out, o, depth) {
1282
+ const indent = " ".repeat(depth);
1283
+ for (const [k, v] of o.fields) {
1284
+ out.push(indent, renderKey(k));
1285
+ if (v.kind === "object") {
1286
+ out.push(" {");
1287
+ if (v.fields.size === 0) {
1288
+ out.push("}\n");
1289
+ continue;
1290
+ }
1291
+ out.push("\n");
1292
+ renderObjectBody(out, v, depth + 1);
1293
+ out.push(indent, "}\n");
1294
+ } else {
1295
+ out.push(" = ");
1296
+ renderValue(out, v, depth);
1297
+ out.push("\n");
1298
+ }
1299
+ }
1300
+ }
1301
+ function renderValue(out, v, depth) {
1302
+ switch (v.kind) {
1303
+ case "object": {
1304
+ if (v.fields.size === 0) {
1305
+ out.push("{}");
1306
+ return;
1307
+ }
1308
+ out.push("{\n");
1309
+ renderObjectBody(out, v, depth + 1);
1310
+ out.push(" ".repeat(depth), "}");
1311
+ return;
1312
+ }
1313
+ case "array":
1314
+ renderArray(out, v, depth);
1315
+ return;
1316
+ case "scalar":
1317
+ out.push(renderScalar(v));
1318
+ return;
1319
+ default:
1320
+ throw new ConfigError(
1321
+ "renderHocon: unrenderable value (config must be resolved data)",
1322
+ ""
1323
+ );
1324
+ }
1325
+ }
1326
+ function renderArray(out, a, depth) {
1327
+ if (a.items.length === 0) {
1328
+ out.push("[]");
1329
+ return;
1330
+ }
1331
+ const inner = " ".repeat(depth + 1);
1332
+ out.push("[\n");
1333
+ for (const e of a.items) {
1334
+ out.push(inner);
1335
+ renderValue(out, e, depth + 1);
1336
+ out.push("\n");
1337
+ }
1338
+ out.push(" ".repeat(depth), "]");
1339
+ }
1340
+ function renderScalar(s) {
1341
+ switch (s.valueType) {
1342
+ case "null":
1343
+ return "null";
1344
+ case "boolean":
1345
+ case "number":
1346
+ return s.raw;
1347
+ case "string":
1348
+ return renderString(s.raw);
1349
+ }
1350
+ }
1351
+ var SAFE_UNQUOTED_KEY = /^[A-Za-z0-9_-]+$/;
1352
+ function renderKey(k) {
1353
+ if (k === "include") return quoteString(k);
1354
+ return SAFE_UNQUOTED_KEY.test(k) ? k : quoteString(k);
1355
+ }
1356
+ var SAFE_BARE_STRING = /^[A-Za-z][A-Za-z0-9_-]*$/;
1357
+ var STRING_KEYWORDS = /* @__PURE__ */ new Set(["true", "false", "null", "yes", "no", "on", "off"]);
1358
+ function renderString(s) {
1359
+ return SAFE_BARE_STRING.test(s) && !STRING_KEYWORDS.has(s.toLowerCase()) ? s : quoteString(s);
1360
+ }
1361
+ function quoteString(s) {
1362
+ if (s.includes("\n") && !s.includes("\r") && !s.includes('"""') && !s.endsWith('"')) {
1363
+ return `"""${s}"""`;
1364
+ }
1365
+ let b = '"';
1366
+ for (const ch of s) {
1367
+ switch (ch) {
1368
+ case '"':
1369
+ b += '\\"';
1370
+ break;
1371
+ case "\\":
1372
+ b += "\\\\";
1373
+ break;
1374
+ case "\n":
1375
+ b += "\\n";
1376
+ break;
1377
+ case "\r":
1378
+ b += "\\r";
1379
+ break;
1380
+ case " ":
1381
+ b += "\\t";
1382
+ break;
1383
+ default:
1384
+ b += ch;
1385
+ }
1386
+ }
1387
+ return b + '"';
1388
+ }
1389
+
1080
1390
  // src/config.ts
1081
1391
  var Config = class _Config {
1082
1392
  constructor(root, opts) {
@@ -1382,6 +1692,36 @@ var Config = class _Config {
1382
1692
  toObject(opts) {
1383
1693
  return hoconToJs(this.root, opts?.nullPrototype === true);
1384
1694
  }
1695
+ /**
1696
+ * Renders this resolved Config as HOCON text (E18; lockstep with go.hocon's
1697
+ * `Config.RenderHOCON`, v1.11.0).
1698
+ *
1699
+ * The output round-trips: parsing it back yields the same value tree. That
1700
+ * is the correctness contract, not byte-for-byte formatting — a scalar is
1701
+ * quoted whenever leaving it bare would re-parse as a different type (a
1702
+ * string `"8080"` becomes `"8080"`, not `8080`), and left bare only when it
1703
+ * provably cannot.
1704
+ *
1705
+ * The Config must be resolved and hold only data (objects, arrays, string /
1706
+ * number / boolean / null scalars) — exactly what {@link fromMap} and the
1707
+ * format adapters produce. An unresolved placeholder throws
1708
+ * {@link ConfigError}; substitutions have no textual round trip through a
1709
+ * value tree.
1710
+ *
1711
+ * The root object's fields are emitted without enclosing braces, nested
1712
+ * objects as `key { … }`, arrays as newline-separated `[ … ]`, indented two
1713
+ * spaces. Source comments are not represented — a value tree does not carry
1714
+ * them.
1715
+ */
1716
+ renderHocon() {
1717
+ if (!this._resolved) {
1718
+ throw new ConfigError(
1719
+ "renderHocon: unrenderable unresolved substitution (config must be resolved data)",
1720
+ ""
1721
+ );
1722
+ }
1723
+ return renderHoconFromRoot(this.root);
1724
+ }
1385
1725
  /**
1386
1726
  * @internal Test-only: renders this resolved Config's value tree as canonical
1387
1727
  * 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
  type EnvOptions = {
4
4
  /**
@@ -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
  type EnvOptions = {
4
4
  /**