@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.
package/dist/index.cjs CHANGED
@@ -95,13 +95,22 @@ function coerceNumber(value) {
95
95
  return Number(value);
96
96
  }
97
97
  var DURATION_UNITS = {
98
+ // S19.1–S19.3: the bare nano/micro/milli (+plural) aliases are part of the
99
+ // spec's unit lists (HOCON.md L1307–L1309) and accepted by Lightbend
100
+ // (typesafe-config 1.4.6 probe, 2026-08-18); they were missing here.
98
101
  ns: 1e-6,
102
+ nano: 1e-6,
103
+ nanos: 1e-6,
99
104
  nanosecond: 1e-6,
100
105
  nanoseconds: 1e-6,
101
106
  us: 1e-3,
107
+ micro: 1e-3,
108
+ micros: 1e-3,
102
109
  microsecond: 1e-3,
103
110
  microseconds: 1e-3,
104
111
  ms: 1,
112
+ milli: 1,
113
+ millis: 1,
105
114
  millisecond: 1,
106
115
  milliseconds: 1,
107
116
  s: 1e3,
@@ -153,37 +162,76 @@ function parseDuration(value, outputUnit = "ms") {
153
162
  return ms / divisor;
154
163
  }
155
164
  var BYTE_UNITS = {
165
+ // S21.1–S21.4 — the EXACT Lightbend/typesafe-config unit set (1.4.6 probe,
166
+ // 2026-08-18). Multi-letter units are case-sensitive: the SI decimal short
167
+ // form is `kB` (NOT `KB`/`kb` — Lightbend rejects both), higher decimals
168
+ // are `MB`…`YB`, binary prefixes are `Ki`/`KiB`…`Yi`/`YiB` with capital
169
+ // first letter, and long forms are lowercase only (`kilobyte`, never
170
+ // `Kilobyte`). Only the bare byte unit (`B`/`b`) and the single-letter
171
+ // -Xmx forms accept both cases. The old case-insensitive fallback and the
172
+ // lowercase alias rows accepted spellings Lightbend rejects — removed.
156
173
  B: 1,
174
+ b: 1,
157
175
  byte: 1,
158
176
  bytes: 1,
159
- KB: 1e3,
177
+ kB: 1e3,
160
178
  kilobyte: 1e3,
161
179
  kilobytes: 1e3,
162
- KiB: 1024,
163
- kibibyte: 1024,
164
- kibibytes: 1024,
165
180
  MB: 1e6,
166
181
  megabyte: 1e6,
167
182
  megabytes: 1e6,
168
- MiB: 1048576,
169
- mebibyte: 1048576,
170
- mebibytes: 1048576,
171
183
  GB: 1e9,
172
184
  gigabyte: 1e9,
173
185
  gigabytes: 1e9,
174
- GiB: 1073741824,
175
- gibibyte: 1073741824,
176
- gibibytes: 1073741824,
177
186
  TB: 1e12,
178
187
  terabyte: 1e12,
179
188
  terabytes: 1e12,
189
+ PB: 1e15,
190
+ petabyte: 1e15,
191
+ petabytes: 1e15,
192
+ EB: 1e18,
193
+ exabyte: 1e18,
194
+ exabytes: 1e18,
195
+ ZB: 1e21,
196
+ zettabyte: 1e21,
197
+ zettabytes: 1e21,
198
+ YB: 1e24,
199
+ yottabyte: 1e24,
200
+ yottabytes: 1e24,
201
+ Ki: 1024,
202
+ KiB: 1024,
203
+ kibibyte: 1024,
204
+ kibibytes: 1024,
205
+ Mi: 1048576,
206
+ MiB: 1048576,
207
+ mebibyte: 1048576,
208
+ mebibytes: 1048576,
209
+ Gi: 1073741824,
210
+ GiB: 1073741824,
211
+ gibibyte: 1073741824,
212
+ gibibytes: 1073741824,
213
+ Ti: 1099511627776,
180
214
  TiB: 1099511627776,
181
215
  tebibyte: 1099511627776,
182
216
  tebibytes: 1099511627776,
183
- // S21.4 — HOCON.md L1385: single-letter abbreviations → powers of two (java -Xmx convention).
184
- // Lightbend typesafe-config 1.4.3 verified: 1K=1024, 1M=1048576, etc.
185
- // Both upper- and lowercase accepted per Lightbend behaviour.
186
- // Z/Y (2^70/2^80) are deferred — they overflow MAX_SAFE_INTEGER and require BigInt accessor.
217
+ Pi: 1024 ** 5,
218
+ PiB: 1024 ** 5,
219
+ pebibyte: 1024 ** 5,
220
+ pebibytes: 1024 ** 5,
221
+ Ei: 1024 ** 6,
222
+ EiB: 1024 ** 6,
223
+ exbibyte: 1024 ** 6,
224
+ exbibytes: 1024 ** 6,
225
+ Zi: 1024 ** 7,
226
+ ZiB: 1024 ** 7,
227
+ zebibyte: 1024 ** 7,
228
+ zebibytes: 1024 ** 7,
229
+ Yi: 1024 ** 8,
230
+ YiB: 1024 ** 8,
231
+ yobibyte: 1024 ** 8,
232
+ yobibytes: 1024 ** 8,
233
+ // S21.4 — single-letter abbreviations → powers of two (java -Xmx
234
+ // convention); Lightbend accepts BOTH cases here, through the full ladder.
187
235
  K: 1024,
188
236
  k: 1024,
189
237
  M: 1024 ** 2,
@@ -196,16 +244,10 @@ var BYTE_UNITS = {
196
244
  p: 1024 ** 5,
197
245
  E: 1024 ** 6,
198
246
  e: 1024 ** 6,
199
- // lowercase short-form aliases
200
- b: 1,
201
- kb: 1e3,
202
- kib: 1024,
203
- mb: 1e6,
204
- mib: 1048576,
205
- gb: 1e9,
206
- gib: 1073741824,
207
- tb: 1e12,
208
- tib: 1099511627776
247
+ Z: 1024 ** 7,
248
+ z: 1024 ** 7,
249
+ Y: 1024 ** 8,
250
+ y: 1024 ** 8
209
251
  };
210
252
  var OUTPUT_BYTE_UNITS = {
211
253
  B: 1,
@@ -243,10 +285,7 @@ function parseBytes(value, outputUnit = "B") {
243
285
  }
244
286
  return bytes2 / divisor;
245
287
  }
246
- let mult = BYTE_UNITS[unit];
247
- if (mult === void 0) {
248
- mult = BYTE_UNITS[unit.toLowerCase()];
249
- }
288
+ const mult = BYTE_UNITS[unit];
250
289
  if (mult === void 0) return NaN;
251
290
  const bytes = num * mult;
252
291
  if (Math.abs(bytes) > Number.MAX_SAFE_INTEGER) {
@@ -310,35 +349,114 @@ function stringSegmentsToKey(segments) {
310
349
  return t;
311
350
  }).join(".");
312
351
  }
352
+ function prefixSelfRefRemainder(s, fullKey) {
353
+ const texts = s.segments.map((seg) => seg.text);
354
+ if (!stringSegmentsToKey(texts).startsWith(fullKey + ".")) return null;
355
+ for (let n = 1; n < texts.length; n++) {
356
+ if (stringSegmentsToKey(texts.slice(0, n)) === fullKey) return texts.slice(n);
357
+ }
358
+ return null;
359
+ }
360
+ function navigateResolverValue(v, remainder) {
361
+ let cur = v;
362
+ for (const seg of remainder) {
363
+ if (isSubst(cur) || isConcat(cur)) return null;
364
+ if (isResObj(cur)) {
365
+ const next = cur.fields.get(seg);
366
+ if (next === void 0) return void 0;
367
+ cur = next;
368
+ continue;
369
+ }
370
+ const hv = cur;
371
+ if (hv.kind === "object") {
372
+ const next = hv.fields.get(seg);
373
+ if (next === void 0) return void 0;
374
+ cur = next;
375
+ continue;
376
+ }
377
+ return void 0;
378
+ }
379
+ return cur;
380
+ }
381
+ function foldPrefixSelfRef(s, replacement, remainder) {
382
+ const navigated = navigateResolverValue(replacement, remainder);
383
+ if (navigated === null) return s;
384
+ if (navigated === void 0) {
385
+ return {
386
+ _kind: "subst-placeholder",
387
+ segments: s.segments.map((seg) => ({ text: seg.text, line: seg.line, col: seg.col })),
388
+ optional: s.optional,
389
+ knownAbsent: true,
390
+ listSuffix: s.listSuffix,
391
+ line: s.line,
392
+ col: s.col,
393
+ prefixLen: s.prefixLen
394
+ };
395
+ }
396
+ return cloneResolverValue(navigated);
397
+ }
398
+ function mergeResObjLayers(base, top) {
399
+ const fields = new Map(base.fields);
400
+ for (const [k, tv] of top.fields) {
401
+ const bv = fields.get(k);
402
+ if (bv !== void 0 && isResObj(bv) && isResObj(tv)) {
403
+ fields.set(k, mergeResObjLayers(bv, tv));
404
+ } else {
405
+ fields.set(k, tv);
406
+ }
407
+ }
408
+ const priorValues = new Map(base.priorValues);
409
+ for (const [k, pv] of top.priorValues) priorValues.set(k, pv);
410
+ const resetKeys = new Set(base.resetKeys);
411
+ for (const k of top.resetKeys) resetKeys.add(k);
412
+ return {
413
+ _kind: "res-obj",
414
+ fields,
415
+ priorValues,
416
+ resetKeys
417
+ };
418
+ }
313
419
  function containsSelfRef(v, fullKey) {
314
- if (isSubst(v)) return !v.knownAbsent && substFullKey(v) === fullKey;
315
- if (isConcat(v)) return v.nodes.some((n) => containsSelfRef(n, fullKey));
420
+ return containsSelfRefInner(v, fullKey, true);
421
+ }
422
+ function containsSelfRefInner(v, fullKey, allowPrefix) {
423
+ if (isSubst(v))
424
+ return !v.knownAbsent && (substFullKey(v) === fullKey || allowPrefix && prefixSelfRefRemainder(v, fullKey) !== null);
425
+ if (isConcat(v)) return v.nodes.some((n) => containsSelfRefInner(n, fullKey, allowPrefix));
316
426
  if (isResObj(v)) {
317
427
  for (const f of v.fields.values()) {
318
- if (containsSelfRef(f, fullKey)) return true;
428
+ if (containsSelfRefInner(f, fullKey, false)) return true;
319
429
  }
320
430
  return false;
321
431
  }
322
432
  const hv = v;
323
433
  if (hv.kind === "array") {
324
- return hv.items.some((item) => containsSelfRef(item, fullKey));
434
+ return hv.items.some((item) => containsSelfRefInner(item, fullKey, allowPrefix));
325
435
  }
326
436
  if (hv.kind === "object") {
327
437
  for (const f of hv.fields.values()) {
328
- if (containsSelfRef(f, fullKey)) return true;
438
+ if (containsSelfRefInner(f, fullKey, false)) return true;
329
439
  }
330
440
  return false;
331
441
  }
332
442
  return false;
333
443
  }
334
444
  function foldSelfRef(v, fullKey, replacement) {
445
+ return foldSelfRefInner(v, fullKey, replacement, true);
446
+ }
447
+ function foldSelfRefInner(v, fullKey, replacement, allowPrefix) {
335
448
  if (isSubst(v)) {
336
- return substFullKey(v) === fullKey ? replacement : v;
449
+ if (substFullKey(v) === fullKey) return replacement;
450
+ if (allowPrefix) {
451
+ const remainder = prefixSelfRefRemainder(v, fullKey);
452
+ if (remainder !== null) return foldPrefixSelfRef(v, replacement, remainder);
453
+ }
454
+ return v;
337
455
  }
338
456
  if (isConcat(v)) {
339
457
  return {
340
458
  _kind: "concat-placeholder",
341
- nodes: v.nodes.map((n) => foldSelfRef(n, fullKey, replacement)),
459
+ nodes: v.nodes.map((n) => foldSelfRefInner(n, fullKey, replacement, allowPrefix)),
342
460
  line: v.line,
343
461
  col: v.col
344
462
  };
@@ -346,7 +464,7 @@ function foldSelfRef(v, fullKey, replacement) {
346
464
  if (isResObj(v)) {
347
465
  const newFields = /* @__PURE__ */ new Map();
348
466
  for (const [k, val] of v.fields) {
349
- newFields.set(k, foldSelfRef(val, fullKey, replacement));
467
+ newFields.set(k, foldSelfRefInner(val, fullKey, replacement, false));
350
468
  }
351
469
  return { _kind: "res-obj", fields: newFields, priorValues: new Map(v.priorValues), resetKeys: new Set(v.resetKeys) };
352
470
  }
@@ -354,13 +472,15 @@ function foldSelfRef(v, fullKey, replacement) {
354
472
  if (hv.kind === "array") {
355
473
  return {
356
474
  kind: "array",
357
- items: hv.items.map((item) => foldSelfRef(item, fullKey, replacement))
475
+ items: hv.items.map(
476
+ (item) => foldSelfRefInner(item, fullKey, replacement, allowPrefix)
477
+ )
358
478
  };
359
479
  }
360
480
  if (hv.kind === "object") {
361
481
  const newFields = /* @__PURE__ */ new Map();
362
482
  for (const [k, val] of hv.fields) {
363
- newFields.set(k, foldSelfRef(val, fullKey, replacement));
483
+ newFields.set(k, foldSelfRefInner(val, fullKey, replacement, false));
364
484
  }
365
485
  return { kind: "object", fields: newFields };
366
486
  }
@@ -404,10 +524,26 @@ function foldKnownAbsentSelfRef(v, fullKey, replacement) {
404
524
  function foldOrSkipPrior(prior, fullKey, old) {
405
525
  if (!containsSelfRef(prior, fullKey)) return cloneResolverValue(prior);
406
526
  if (old === void 0) return foldOptionalSelfRefAbsent(prior, fullKey);
527
+ if (isSubst(prior)) {
528
+ const remainder = prefixSelfRefRemainder(prior, fullKey);
529
+ if (remainder !== null) {
530
+ const navigated = navigateResolverValue(old, remainder);
531
+ if (navigated === void 0 && prior.optional) return cloneResolverValue(old);
532
+ if (navigated !== null && navigated !== void 0 && isResObj(navigated) && isResObj(old)) {
533
+ return mergeResObjLayers(
534
+ cloneResolverValue(old),
535
+ cloneResolverValue(navigated)
536
+ );
537
+ }
538
+ }
539
+ }
407
540
  return foldSelfRef(prior, fullKey, old);
408
541
  }
409
542
  function foldOptionalSelfRefAbsent(v, fullKey) {
410
- if (isSubst(v) && substFullKey(v) === fullKey) {
543
+ return foldOptionalSelfRefAbsentInner(v, fullKey, true);
544
+ }
545
+ function foldOptionalSelfRefAbsentInner(v, fullKey, allowPrefix) {
546
+ if (isSubst(v) && (substFullKey(v) === fullKey || allowPrefix && prefixSelfRefRemainder(v, fullKey) !== null)) {
411
547
  if (!v.optional) return void 0;
412
548
  return {
413
549
  _kind: "subst-placeholder",
@@ -423,7 +559,7 @@ function foldOptionalSelfRefAbsent(v, fullKey) {
423
559
  if (isConcat(v)) {
424
560
  const nodes = [];
425
561
  for (const node of v.nodes) {
426
- const folded = foldOptionalSelfRefAbsent(node, fullKey);
562
+ const folded = foldOptionalSelfRefAbsentInner(node, fullKey, allowPrefix);
427
563
  if (folded === void 0) return void 0;
428
564
  nodes.push(folded);
429
565
  }
@@ -432,7 +568,7 @@ function foldOptionalSelfRefAbsent(v, fullKey) {
432
568
  if (isResObj(v)) {
433
569
  const fields = /* @__PURE__ */ new Map();
434
570
  for (const [key, value] of v.fields) {
435
- const folded = foldOptionalSelfRefAbsent(value, fullKey);
571
+ const folded = foldOptionalSelfRefAbsentInner(value, fullKey, false);
436
572
  if (folded === void 0) return void 0;
437
573
  fields.set(key, folded);
438
574
  }
@@ -442,7 +578,7 @@ function foldOptionalSelfRefAbsent(v, fullKey) {
442
578
  if (hv.kind === "array") {
443
579
  const items = [];
444
580
  for (const item of hv.items) {
445
- const folded = foldOptionalSelfRefAbsent(item, fullKey);
581
+ const folded = foldOptionalSelfRefAbsentInner(item, fullKey, allowPrefix);
446
582
  if (folded === void 0) return void 0;
447
583
  items.push(folded);
448
584
  }
@@ -451,7 +587,7 @@ function foldOptionalSelfRefAbsent(v, fullKey) {
451
587
  if (hv.kind === "object") {
452
588
  const fields = /* @__PURE__ */ new Map();
453
589
  for (const [key, value] of hv.fields) {
454
- const folded = foldOptionalSelfRefAbsent(value, fullKey);
590
+ const folded = foldOptionalSelfRefAbsentInner(value, fullKey, false);
455
591
  if (folded === void 0) return void 0;
456
592
  fields.set(key, folded);
457
593
  }
@@ -824,7 +960,6 @@ var Lexer = class {
824
960
  if (!closed) {
825
961
  throw new ParseError("unterminated triple-quoted string", sl, sc);
826
962
  }
827
- if (value.startsWith("\n")) value = value.slice(1);
828
963
  this.push("triple_string", value, sl, sc, true);
829
964
  continue;
830
965
  }
@@ -1141,9 +1276,9 @@ var Parser = class {
1141
1276
  }
1142
1277
  }
1143
1278
  this.skip("newline");
1144
- const remaining = this.peek();
1145
- if (remaining.kind !== "eof") {
1146
- throw new ParseError(`Unexpected token '${remaining.value}' after closing brace`, remaining.line, remaining.col);
1279
+ const remaining2 = this.peek();
1280
+ if (remaining2.kind !== "eof") {
1281
+ throw new ParseError(`Unexpected token '${remaining2.value}' after closing brace`, remaining2.line, remaining2.col);
1147
1282
  }
1148
1283
  return { kind: "object", fields: allFields, pos: first.pos };
1149
1284
  }
@@ -1151,13 +1286,19 @@ var Parser = class {
1151
1286
  this.advance();
1152
1287
  const arr = this.parseArray();
1153
1288
  this.skip("newline");
1154
- const remaining = this.peek();
1155
- if (remaining.kind !== "eof") {
1156
- throw new ParseError(`Unexpected token '${remaining.value}' after root array`, remaining.line, remaining.col);
1289
+ const remaining2 = this.peek();
1290
+ if (remaining2.kind !== "eof") {
1291
+ throw new ParseError(`Unexpected token '${remaining2.value}' after root array`, remaining2.line, remaining2.col);
1157
1292
  }
1158
1293
  return { ...arr, pos: { line: t.line, col: t.col } };
1159
1294
  }
1160
- return this.parseObject(false);
1295
+ const root = this.parseObject(false);
1296
+ this.skip("newline");
1297
+ const remaining = this.peek();
1298
+ if (remaining.kind !== "eof") {
1299
+ throw new ParseError(`Unexpected token '${remaining.value}' after root content`, remaining.line, remaining.col);
1300
+ }
1301
+ return root;
1161
1302
  }
1162
1303
  peek(offset = 0) {
1163
1304
  return this.tokens[this.pos + offset] ?? EOF_TOKEN;
@@ -2315,6 +2456,12 @@ var SubstitutionResolver = class {
2315
2456
  // Cross-impl with rs.hocon v1.5.1's resolving_field_path (same purpose,
2316
2457
  // same prefix-match semantics).
2317
2458
  this.resolvingFieldPath = [];
2459
+ // S13a.12 recursion guard: field keys whose prior is currently being
2460
+ // resolved through the prefix-self-ref branch. Saved priors are
2461
+ // prefix-self-ref-free by the fold invariant, so re-entry only happens on
2462
+ // a shape the fold could not see through (a live subst mid-navigation) —
2463
+ // report it as a cycle instead of recursing forever.
2464
+ this.resolvingPrefixPriors = /* @__PURE__ */ new Set();
2318
2465
  }
2319
2466
  /** Returns a prefix for error messages, including origin info when set. */
2320
2467
  originPrefix() {
@@ -2329,46 +2476,57 @@ var SubstitutionResolver = class {
2329
2476
  this.resolvingFieldPath.push(key);
2330
2477
  const fullCacheKey = stringSegmentsToKey(this.resolvingFieldPath);
2331
2478
  this.cache.delete(fullCacheKey);
2332
- let resolved;
2333
2479
  try {
2334
- resolved = this.resolveVal(val, obj);
2335
- } finally {
2336
- this.resolvingFieldPath.pop();
2337
- }
2338
- if (resolved !== void 0) {
2339
- if (resolved.kind === "object") {
2480
+ const resolved = this.resolveVal(val, obj);
2481
+ if (resolved !== void 0) {
2482
+ if (resolved.kind === "object") {
2483
+ const prior = obj.priorValues.get(key);
2484
+ if (prior !== void 0) {
2485
+ const priorResolved = this.resolveVal(prior, obj);
2486
+ if (priorResolved !== void 0 && priorResolved.kind === "object") {
2487
+ const finalValue = deepMergeHoconValues(
2488
+ priorResolved,
2489
+ resolved
2490
+ );
2491
+ result.set(key, finalValue);
2492
+ this.cache.set(fullCacheKey, finalValue);
2493
+ this.cacheDescendants(fullCacheKey, finalValue);
2494
+ continue;
2495
+ }
2496
+ }
2497
+ }
2498
+ result.set(key, resolved);
2499
+ this.cache.set(fullCacheKey, resolved);
2500
+ this.cacheDescendants(fullCacheKey, resolved);
2501
+ } else {
2340
2502
  const prior = obj.priorValues.get(key);
2341
2503
  if (prior !== void 0) {
2342
2504
  const priorResolved = this.resolveVal(prior, obj);
2343
- if (priorResolved !== void 0 && priorResolved.kind === "object") {
2344
- const finalValue = deepMergeHoconValues(
2345
- priorResolved,
2346
- resolved
2347
- );
2348
- result.set(key, finalValue);
2349
- this.cache.set(fullCacheKey, finalValue);
2350
- this.cacheDescendants(fullCacheKey, finalValue);
2351
- continue;
2505
+ if (priorResolved !== void 0) {
2506
+ result.set(key, priorResolved);
2507
+ this.cache.set(fullCacheKey, priorResolved);
2508
+ this.cacheDescendants(fullCacheKey, priorResolved);
2352
2509
  }
2353
2510
  }
2354
2511
  }
2355
- result.set(key, resolved);
2356
- this.cache.set(fullCacheKey, resolved);
2357
- this.cacheDescendants(fullCacheKey, resolved);
2358
- } else {
2359
- const prior = obj.priorValues.get(key);
2360
- if (prior !== void 0) {
2361
- const priorResolved = this.resolveVal(prior, obj);
2362
- if (priorResolved !== void 0) {
2363
- result.set(key, priorResolved);
2364
- this.cache.set(fullCacheKey, priorResolved);
2365
- this.cacheDescendants(fullCacheKey, priorResolved);
2366
- }
2367
- }
2512
+ } finally {
2513
+ this.resolvingFieldPath.pop();
2368
2514
  }
2369
2515
  }
2370
2516
  return { kind: "object", fields: result };
2371
2517
  }
2518
+ /** S13a.12: walk resolved-object fields by segment text. A missing segment
2519
+ * or a walk into a scalar/array is path-absent → undefined. */
2520
+ navigateResolved(v, remainder) {
2521
+ let cur = v;
2522
+ for (const seg of remainder) {
2523
+ if (cur.kind !== "object") return void 0;
2524
+ const next = cur.fields.get(seg);
2525
+ if (next === void 0) return void 0;
2526
+ cur = next;
2527
+ }
2528
+ return cur;
2529
+ }
2372
2530
  cacheDescendants(prefix, value) {
2373
2531
  if (value.kind !== "object") return;
2374
2532
  for (const [key, child] of value.fields) {
@@ -2391,17 +2549,59 @@ var SubstitutionResolver = class {
2391
2549
  if (isResObj(v)) return this.resolveResObj(v);
2392
2550
  const hv = v;
2393
2551
  if (hv.kind === "array") {
2394
- return {
2395
- kind: "array",
2396
- items: hv.items.map(
2397
- (item) => this.resolveVal(item, scope) ?? { kind: "scalar", raw: "null", valueType: "null" }
2398
- )
2399
- };
2552
+ const items = [];
2553
+ for (const item of hv.items) {
2554
+ const resolved = this.resolveVal(item, scope);
2555
+ if (resolved === void 0) continue;
2556
+ items.push(resolved);
2557
+ }
2558
+ return { kind: "array", items };
2400
2559
  }
2401
2560
  return hv;
2402
2561
  }
2403
2562
  resolveSubst(s, scope) {
2404
- if (s.knownAbsent) return void 0;
2563
+ if (s.knownAbsent) {
2564
+ if (!s.optional) {
2565
+ if (this.opts.allowUnresolved) return s;
2566
+ const k = s.listSuffix ? `${segmentsToKey(s.segments)}[]` : segmentsToKey(s.segments);
2567
+ throw new ResolveError(
2568
+ `${this.originPrefix()}could not resolve substitution: \${${k}}`,
2569
+ k,
2570
+ s.line,
2571
+ s.col
2572
+ );
2573
+ }
2574
+ return void 0;
2575
+ }
2576
+ {
2577
+ const rfp = this.resolvingFieldPath;
2578
+ const sTexts = s.segments.map((seg) => seg.text);
2579
+ if (rfp.length > 0 && rfp.length < sTexts.length && stringSegmentsToKey(sTexts.slice(0, rfp.length)) === stringSegmentsToKey(rfp)) {
2580
+ const guardKey = stringSegmentsToKey(rfp);
2581
+ const prior = scope.priorValues.get(rfp[rfp.length - 1]);
2582
+ if (prior !== void 0 && !this.resolvingPrefixPriors.has(guardKey)) {
2583
+ this.resolvingPrefixPriors.add(guardKey);
2584
+ try {
2585
+ const priorResolved = this.resolveVal(prior, scope);
2586
+ if (priorResolved !== void 0) {
2587
+ const navigated = this.navigateResolved(priorResolved, sTexts.slice(rfp.length));
2588
+ if (navigated !== void 0) return navigated;
2589
+ }
2590
+ } finally {
2591
+ this.resolvingPrefixPriors.delete(guardKey);
2592
+ }
2593
+ }
2594
+ if (s.optional) return void 0;
2595
+ if (this.opts.allowUnresolved) return s;
2596
+ const k = s.listSuffix ? `${segmentsToKey(s.segments)}[]` : segmentsToKey(s.segments);
2597
+ throw new ResolveError(
2598
+ `${this.originPrefix()}could not resolve substitution: \${${k}}`,
2599
+ k,
2600
+ s.line,
2601
+ s.col
2602
+ );
2603
+ }
2604
+ }
2405
2605
  const key = s.listSuffix ? `${segmentsToKey(s.segments)}[]` : segmentsToKey(s.segments);
2406
2606
  if (this.cache.has(key)) return this.cache.get(key);
2407
2607
  if (this.resolving.has(key)) {
@@ -2732,6 +2932,121 @@ async function resolveAsync(ast, opts) {
2732
2932
  return resolveTree(root, { ...opts, useSystemEnvironment: true, allowUnresolved: false });
2733
2933
  }
2734
2934
 
2935
+ // src/render-hocon.ts
2936
+ function renderHoconFromRoot(root) {
2937
+ const out = [];
2938
+ renderObjectBody(out, root, 0);
2939
+ return out.join("");
2940
+ }
2941
+ function renderObjectBody(out, o, depth) {
2942
+ const indent = " ".repeat(depth);
2943
+ for (const [k, v] of o.fields) {
2944
+ out.push(indent, renderKey(k));
2945
+ if (v.kind === "object") {
2946
+ out.push(" {");
2947
+ if (v.fields.size === 0) {
2948
+ out.push("}\n");
2949
+ continue;
2950
+ }
2951
+ out.push("\n");
2952
+ renderObjectBody(out, v, depth + 1);
2953
+ out.push(indent, "}\n");
2954
+ } else {
2955
+ out.push(" = ");
2956
+ renderValue(out, v, depth);
2957
+ out.push("\n");
2958
+ }
2959
+ }
2960
+ }
2961
+ function renderValue(out, v, depth) {
2962
+ switch (v.kind) {
2963
+ case "object": {
2964
+ if (v.fields.size === 0) {
2965
+ out.push("{}");
2966
+ return;
2967
+ }
2968
+ out.push("{\n");
2969
+ renderObjectBody(out, v, depth + 1);
2970
+ out.push(" ".repeat(depth), "}");
2971
+ return;
2972
+ }
2973
+ case "array":
2974
+ renderArray(out, v, depth);
2975
+ return;
2976
+ case "scalar":
2977
+ out.push(renderScalar(v));
2978
+ return;
2979
+ default:
2980
+ throw new ConfigError(
2981
+ "renderHocon: unrenderable value (config must be resolved data)",
2982
+ ""
2983
+ );
2984
+ }
2985
+ }
2986
+ function renderArray(out, a, depth) {
2987
+ if (a.items.length === 0) {
2988
+ out.push("[]");
2989
+ return;
2990
+ }
2991
+ const inner = " ".repeat(depth + 1);
2992
+ out.push("[\n");
2993
+ for (const e of a.items) {
2994
+ out.push(inner);
2995
+ renderValue(out, e, depth + 1);
2996
+ out.push("\n");
2997
+ }
2998
+ out.push(" ".repeat(depth), "]");
2999
+ }
3000
+ function renderScalar(s) {
3001
+ switch (s.valueType) {
3002
+ case "null":
3003
+ return "null";
3004
+ case "boolean":
3005
+ case "number":
3006
+ return s.raw;
3007
+ case "string":
3008
+ return renderString(s.raw);
3009
+ }
3010
+ }
3011
+ var SAFE_UNQUOTED_KEY = /^[A-Za-z0-9_-]+$/;
3012
+ function renderKey(k) {
3013
+ if (k === "include") return quoteString(k);
3014
+ return SAFE_UNQUOTED_KEY.test(k) ? k : quoteString(k);
3015
+ }
3016
+ var SAFE_BARE_STRING = /^[A-Za-z][A-Za-z0-9_-]*$/;
3017
+ var STRING_KEYWORDS = /* @__PURE__ */ new Set(["true", "false", "null", "yes", "no", "on", "off"]);
3018
+ function renderString(s) {
3019
+ return SAFE_BARE_STRING.test(s) && !STRING_KEYWORDS.has(s.toLowerCase()) ? s : quoteString(s);
3020
+ }
3021
+ function quoteString(s) {
3022
+ if (s.includes("\n") && !s.includes("\r") && !s.includes('"""') && !s.endsWith('"')) {
3023
+ return `"""${s}"""`;
3024
+ }
3025
+ let b = '"';
3026
+ for (const ch of s) {
3027
+ switch (ch) {
3028
+ case '"':
3029
+ b += '\\"';
3030
+ break;
3031
+ case "\\":
3032
+ b += "\\\\";
3033
+ break;
3034
+ case "\n":
3035
+ b += "\\n";
3036
+ break;
3037
+ case "\r":
3038
+ b += "\\r";
3039
+ break;
3040
+ case " ":
3041
+ b += "\\t";
3042
+ break;
3043
+ default:
3044
+ b += ch;
3045
+ }
3046
+ }
3047
+ return b + '"';
3048
+ }
3049
+
2735
3050
  // src/config.ts
2736
3051
  var Config = class _Config {
2737
3052
  constructor(root, opts) {
@@ -3037,6 +3352,36 @@ var Config = class _Config {
3037
3352
  toObject(opts) {
3038
3353
  return hoconToJs(this.root, opts?.nullPrototype === true);
3039
3354
  }
3355
+ /**
3356
+ * Renders this resolved Config as HOCON text (E18; lockstep with go.hocon's
3357
+ * `Config.RenderHOCON`, v1.11.0).
3358
+ *
3359
+ * The output round-trips: parsing it back yields the same value tree. That
3360
+ * is the correctness contract, not byte-for-byte formatting — a scalar is
3361
+ * quoted whenever leaving it bare would re-parse as a different type (a
3362
+ * string `"8080"` becomes `"8080"`, not `8080`), and left bare only when it
3363
+ * provably cannot.
3364
+ *
3365
+ * The Config must be resolved and hold only data (objects, arrays, string /
3366
+ * number / boolean / null scalars) — exactly what {@link fromMap} and the
3367
+ * format adapters produce. An unresolved placeholder throws
3368
+ * {@link ConfigError}; substitutions have no textual round trip through a
3369
+ * value tree.
3370
+ *
3371
+ * The root object's fields are emitted without enclosing braces, nested
3372
+ * objects as `key { … }`, arrays as newline-separated `[ … ]`, indented two
3373
+ * spaces. Source comments are not represented — a value tree does not carry
3374
+ * them.
3375
+ */
3376
+ renderHocon() {
3377
+ if (!this._resolved) {
3378
+ throw new ConfigError(
3379
+ "renderHocon: unrenderable unresolved substitution (config must be resolved data)",
3380
+ ""
3381
+ );
3382
+ }
3383
+ return renderHoconFromRoot(this.root);
3384
+ }
3040
3385
  /**
3041
3386
  * @internal Test-only: renders this resolved Config's value tree as canonical
3042
3387
  * JSON (sorted keys, no whitespace). Used by Layer-2 fixture tests to compare