@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/zod.cjs CHANGED
@@ -73,13 +73,22 @@ function coerceNumber(value) {
73
73
  return Number(value);
74
74
  }
75
75
  var DURATION_UNITS = {
76
+ // S19.1–S19.3: the bare nano/micro/milli (+plural) aliases are part of the
77
+ // spec's unit lists (HOCON.md L1307–L1309) and accepted by Lightbend
78
+ // (typesafe-config 1.4.6 probe, 2026-08-18); they were missing here.
76
79
  ns: 1e-6,
80
+ nano: 1e-6,
81
+ nanos: 1e-6,
77
82
  nanosecond: 1e-6,
78
83
  nanoseconds: 1e-6,
79
84
  us: 1e-3,
85
+ micro: 1e-3,
86
+ micros: 1e-3,
80
87
  microsecond: 1e-3,
81
88
  microseconds: 1e-3,
82
89
  ms: 1,
90
+ milli: 1,
91
+ millis: 1,
83
92
  millisecond: 1,
84
93
  milliseconds: 1,
85
94
  s: 1e3,
@@ -131,37 +140,76 @@ function parseDuration(value, outputUnit = "ms") {
131
140
  return ms / divisor;
132
141
  }
133
142
  var BYTE_UNITS = {
143
+ // S21.1–S21.4 — the EXACT Lightbend/typesafe-config unit set (1.4.6 probe,
144
+ // 2026-08-18). Multi-letter units are case-sensitive: the SI decimal short
145
+ // form is `kB` (NOT `KB`/`kb` — Lightbend rejects both), higher decimals
146
+ // are `MB`…`YB`, binary prefixes are `Ki`/`KiB`…`Yi`/`YiB` with capital
147
+ // first letter, and long forms are lowercase only (`kilobyte`, never
148
+ // `Kilobyte`). Only the bare byte unit (`B`/`b`) and the single-letter
149
+ // -Xmx forms accept both cases. The old case-insensitive fallback and the
150
+ // lowercase alias rows accepted spellings Lightbend rejects — removed.
134
151
  B: 1,
152
+ b: 1,
135
153
  byte: 1,
136
154
  bytes: 1,
137
- KB: 1e3,
155
+ kB: 1e3,
138
156
  kilobyte: 1e3,
139
157
  kilobytes: 1e3,
140
- KiB: 1024,
141
- kibibyte: 1024,
142
- kibibytes: 1024,
143
158
  MB: 1e6,
144
159
  megabyte: 1e6,
145
160
  megabytes: 1e6,
146
- MiB: 1048576,
147
- mebibyte: 1048576,
148
- mebibytes: 1048576,
149
161
  GB: 1e9,
150
162
  gigabyte: 1e9,
151
163
  gigabytes: 1e9,
152
- GiB: 1073741824,
153
- gibibyte: 1073741824,
154
- gibibytes: 1073741824,
155
164
  TB: 1e12,
156
165
  terabyte: 1e12,
157
166
  terabytes: 1e12,
167
+ PB: 1e15,
168
+ petabyte: 1e15,
169
+ petabytes: 1e15,
170
+ EB: 1e18,
171
+ exabyte: 1e18,
172
+ exabytes: 1e18,
173
+ ZB: 1e21,
174
+ zettabyte: 1e21,
175
+ zettabytes: 1e21,
176
+ YB: 1e24,
177
+ yottabyte: 1e24,
178
+ yottabytes: 1e24,
179
+ Ki: 1024,
180
+ KiB: 1024,
181
+ kibibyte: 1024,
182
+ kibibytes: 1024,
183
+ Mi: 1048576,
184
+ MiB: 1048576,
185
+ mebibyte: 1048576,
186
+ mebibytes: 1048576,
187
+ Gi: 1073741824,
188
+ GiB: 1073741824,
189
+ gibibyte: 1073741824,
190
+ gibibytes: 1073741824,
191
+ Ti: 1099511627776,
158
192
  TiB: 1099511627776,
159
193
  tebibyte: 1099511627776,
160
194
  tebibytes: 1099511627776,
161
- // S21.4 — HOCON.md L1385: single-letter abbreviations → powers of two (java -Xmx convention).
162
- // Lightbend typesafe-config 1.4.3 verified: 1K=1024, 1M=1048576, etc.
163
- // Both upper- and lowercase accepted per Lightbend behaviour.
164
- // Z/Y (2^70/2^80) are deferred — they overflow MAX_SAFE_INTEGER and require BigInt accessor.
195
+ Pi: 1024 ** 5,
196
+ PiB: 1024 ** 5,
197
+ pebibyte: 1024 ** 5,
198
+ pebibytes: 1024 ** 5,
199
+ Ei: 1024 ** 6,
200
+ EiB: 1024 ** 6,
201
+ exbibyte: 1024 ** 6,
202
+ exbibytes: 1024 ** 6,
203
+ Zi: 1024 ** 7,
204
+ ZiB: 1024 ** 7,
205
+ zebibyte: 1024 ** 7,
206
+ zebibytes: 1024 ** 7,
207
+ Yi: 1024 ** 8,
208
+ YiB: 1024 ** 8,
209
+ yobibyte: 1024 ** 8,
210
+ yobibytes: 1024 ** 8,
211
+ // S21.4 — single-letter abbreviations → powers of two (java -Xmx
212
+ // convention); Lightbend accepts BOTH cases here, through the full ladder.
165
213
  K: 1024,
166
214
  k: 1024,
167
215
  M: 1024 ** 2,
@@ -174,16 +222,10 @@ var BYTE_UNITS = {
174
222
  p: 1024 ** 5,
175
223
  E: 1024 ** 6,
176
224
  e: 1024 ** 6,
177
- // lowercase short-form aliases
178
- b: 1,
179
- kb: 1e3,
180
- kib: 1024,
181
- mb: 1e6,
182
- mib: 1048576,
183
- gb: 1e9,
184
- gib: 1073741824,
185
- tb: 1e12,
186
- tib: 1099511627776
225
+ Z: 1024 ** 7,
226
+ z: 1024 ** 7,
227
+ Y: 1024 ** 8,
228
+ y: 1024 ** 8
187
229
  };
188
230
  var OUTPUT_BYTE_UNITS = {
189
231
  B: 1,
@@ -221,10 +263,7 @@ function parseBytes(value, outputUnit = "B") {
221
263
  }
222
264
  return bytes2 / divisor;
223
265
  }
224
- let mult = BYTE_UNITS[unit];
225
- if (mult === void 0) {
226
- mult = BYTE_UNITS[unit.toLowerCase()];
227
- }
266
+ const mult = BYTE_UNITS[unit];
228
267
  if (mult === void 0) return NaN;
229
268
  const bytes = num * mult;
230
269
  if (Math.abs(bytes) > Number.MAX_SAFE_INTEGER) {
@@ -292,35 +331,114 @@ function stringSegmentsToKey(segments) {
292
331
  return t;
293
332
  }).join(".");
294
333
  }
334
+ function prefixSelfRefRemainder(s, fullKey) {
335
+ const texts = s.segments.map((seg) => seg.text);
336
+ if (!stringSegmentsToKey(texts).startsWith(fullKey + ".")) return null;
337
+ for (let n = 1; n < texts.length; n++) {
338
+ if (stringSegmentsToKey(texts.slice(0, n)) === fullKey) return texts.slice(n);
339
+ }
340
+ return null;
341
+ }
342
+ function navigateResolverValue(v, remainder) {
343
+ let cur = v;
344
+ for (const seg of remainder) {
345
+ if (isSubst(cur) || isConcat(cur)) return null;
346
+ if (isResObj(cur)) {
347
+ const next = cur.fields.get(seg);
348
+ if (next === void 0) return void 0;
349
+ cur = next;
350
+ continue;
351
+ }
352
+ const hv = cur;
353
+ if (hv.kind === "object") {
354
+ const next = hv.fields.get(seg);
355
+ if (next === void 0) return void 0;
356
+ cur = next;
357
+ continue;
358
+ }
359
+ return void 0;
360
+ }
361
+ return cur;
362
+ }
363
+ function foldPrefixSelfRef(s, replacement, remainder) {
364
+ const navigated = navigateResolverValue(replacement, remainder);
365
+ if (navigated === null) return s;
366
+ if (navigated === void 0) {
367
+ return {
368
+ _kind: "subst-placeholder",
369
+ segments: s.segments.map((seg) => ({ text: seg.text, line: seg.line, col: seg.col })),
370
+ optional: s.optional,
371
+ knownAbsent: true,
372
+ listSuffix: s.listSuffix,
373
+ line: s.line,
374
+ col: s.col,
375
+ prefixLen: s.prefixLen
376
+ };
377
+ }
378
+ return cloneResolverValue(navigated);
379
+ }
380
+ function mergeResObjLayers(base, top) {
381
+ const fields = new Map(base.fields);
382
+ for (const [k, tv] of top.fields) {
383
+ const bv = fields.get(k);
384
+ if (bv !== void 0 && isResObj(bv) && isResObj(tv)) {
385
+ fields.set(k, mergeResObjLayers(bv, tv));
386
+ } else {
387
+ fields.set(k, tv);
388
+ }
389
+ }
390
+ const priorValues = new Map(base.priorValues);
391
+ for (const [k, pv] of top.priorValues) priorValues.set(k, pv);
392
+ const resetKeys = new Set(base.resetKeys);
393
+ for (const k of top.resetKeys) resetKeys.add(k);
394
+ return {
395
+ _kind: "res-obj",
396
+ fields,
397
+ priorValues,
398
+ resetKeys
399
+ };
400
+ }
295
401
  function containsSelfRef(v, fullKey) {
296
- if (isSubst(v)) return !v.knownAbsent && substFullKey(v) === fullKey;
297
- if (isConcat(v)) return v.nodes.some((n) => containsSelfRef(n, fullKey));
402
+ return containsSelfRefInner(v, fullKey, true);
403
+ }
404
+ function containsSelfRefInner(v, fullKey, allowPrefix) {
405
+ if (isSubst(v))
406
+ return !v.knownAbsent && (substFullKey(v) === fullKey || allowPrefix && prefixSelfRefRemainder(v, fullKey) !== null);
407
+ if (isConcat(v)) return v.nodes.some((n) => containsSelfRefInner(n, fullKey, allowPrefix));
298
408
  if (isResObj(v)) {
299
409
  for (const f of v.fields.values()) {
300
- if (containsSelfRef(f, fullKey)) return true;
410
+ if (containsSelfRefInner(f, fullKey, false)) return true;
301
411
  }
302
412
  return false;
303
413
  }
304
414
  const hv = v;
305
415
  if (hv.kind === "array") {
306
- return hv.items.some((item) => containsSelfRef(item, fullKey));
416
+ return hv.items.some((item) => containsSelfRefInner(item, fullKey, allowPrefix));
307
417
  }
308
418
  if (hv.kind === "object") {
309
419
  for (const f of hv.fields.values()) {
310
- if (containsSelfRef(f, fullKey)) return true;
420
+ if (containsSelfRefInner(f, fullKey, false)) return true;
311
421
  }
312
422
  return false;
313
423
  }
314
424
  return false;
315
425
  }
316
426
  function foldSelfRef(v, fullKey, replacement) {
427
+ return foldSelfRefInner(v, fullKey, replacement, true);
428
+ }
429
+ function foldSelfRefInner(v, fullKey, replacement, allowPrefix) {
317
430
  if (isSubst(v)) {
318
- return substFullKey(v) === fullKey ? replacement : v;
431
+ if (substFullKey(v) === fullKey) return replacement;
432
+ if (allowPrefix) {
433
+ const remainder = prefixSelfRefRemainder(v, fullKey);
434
+ if (remainder !== null) return foldPrefixSelfRef(v, replacement, remainder);
435
+ }
436
+ return v;
319
437
  }
320
438
  if (isConcat(v)) {
321
439
  return {
322
440
  _kind: "concat-placeholder",
323
- nodes: v.nodes.map((n) => foldSelfRef(n, fullKey, replacement)),
441
+ nodes: v.nodes.map((n) => foldSelfRefInner(n, fullKey, replacement, allowPrefix)),
324
442
  line: v.line,
325
443
  col: v.col
326
444
  };
@@ -328,7 +446,7 @@ function foldSelfRef(v, fullKey, replacement) {
328
446
  if (isResObj(v)) {
329
447
  const newFields = /* @__PURE__ */ new Map();
330
448
  for (const [k, val] of v.fields) {
331
- newFields.set(k, foldSelfRef(val, fullKey, replacement));
449
+ newFields.set(k, foldSelfRefInner(val, fullKey, replacement, false));
332
450
  }
333
451
  return { _kind: "res-obj", fields: newFields, priorValues: new Map(v.priorValues), resetKeys: new Set(v.resetKeys) };
334
452
  }
@@ -336,13 +454,15 @@ function foldSelfRef(v, fullKey, replacement) {
336
454
  if (hv.kind === "array") {
337
455
  return {
338
456
  kind: "array",
339
- items: hv.items.map((item) => foldSelfRef(item, fullKey, replacement))
457
+ items: hv.items.map(
458
+ (item) => foldSelfRefInner(item, fullKey, replacement, allowPrefix)
459
+ )
340
460
  };
341
461
  }
342
462
  if (hv.kind === "object") {
343
463
  const newFields = /* @__PURE__ */ new Map();
344
464
  for (const [k, val] of hv.fields) {
345
- newFields.set(k, foldSelfRef(val, fullKey, replacement));
465
+ newFields.set(k, foldSelfRefInner(val, fullKey, replacement, false));
346
466
  }
347
467
  return { kind: "object", fields: newFields };
348
468
  }
@@ -386,10 +506,26 @@ function foldKnownAbsentSelfRef(v, fullKey, replacement) {
386
506
  function foldOrSkipPrior(prior, fullKey, old) {
387
507
  if (!containsSelfRef(prior, fullKey)) return cloneResolverValue(prior);
388
508
  if (old === void 0) return foldOptionalSelfRefAbsent(prior, fullKey);
509
+ if (isSubst(prior)) {
510
+ const remainder = prefixSelfRefRemainder(prior, fullKey);
511
+ if (remainder !== null) {
512
+ const navigated = navigateResolverValue(old, remainder);
513
+ if (navigated === void 0 && prior.optional) return cloneResolverValue(old);
514
+ if (navigated !== null && navigated !== void 0 && isResObj(navigated) && isResObj(old)) {
515
+ return mergeResObjLayers(
516
+ cloneResolverValue(old),
517
+ cloneResolverValue(navigated)
518
+ );
519
+ }
520
+ }
521
+ }
389
522
  return foldSelfRef(prior, fullKey, old);
390
523
  }
391
524
  function foldOptionalSelfRefAbsent(v, fullKey) {
392
- if (isSubst(v) && substFullKey(v) === fullKey) {
525
+ return foldOptionalSelfRefAbsentInner(v, fullKey, true);
526
+ }
527
+ function foldOptionalSelfRefAbsentInner(v, fullKey, allowPrefix) {
528
+ if (isSubst(v) && (substFullKey(v) === fullKey || allowPrefix && prefixSelfRefRemainder(v, fullKey) !== null)) {
393
529
  if (!v.optional) return void 0;
394
530
  return {
395
531
  _kind: "subst-placeholder",
@@ -405,7 +541,7 @@ function foldOptionalSelfRefAbsent(v, fullKey) {
405
541
  if (isConcat(v)) {
406
542
  const nodes = [];
407
543
  for (const node of v.nodes) {
408
- const folded = foldOptionalSelfRefAbsent(node, fullKey);
544
+ const folded = foldOptionalSelfRefAbsentInner(node, fullKey, allowPrefix);
409
545
  if (folded === void 0) return void 0;
410
546
  nodes.push(folded);
411
547
  }
@@ -414,7 +550,7 @@ function foldOptionalSelfRefAbsent(v, fullKey) {
414
550
  if (isResObj(v)) {
415
551
  const fields = /* @__PURE__ */ new Map();
416
552
  for (const [key, value] of v.fields) {
417
- const folded = foldOptionalSelfRefAbsent(value, fullKey);
553
+ const folded = foldOptionalSelfRefAbsentInner(value, fullKey, false);
418
554
  if (folded === void 0) return void 0;
419
555
  fields.set(key, folded);
420
556
  }
@@ -424,7 +560,7 @@ function foldOptionalSelfRefAbsent(v, fullKey) {
424
560
  if (hv.kind === "array") {
425
561
  const items = [];
426
562
  for (const item of hv.items) {
427
- const folded = foldOptionalSelfRefAbsent(item, fullKey);
563
+ const folded = foldOptionalSelfRefAbsentInner(item, fullKey, allowPrefix);
428
564
  if (folded === void 0) return void 0;
429
565
  items.push(folded);
430
566
  }
@@ -433,7 +569,7 @@ function foldOptionalSelfRefAbsent(v, fullKey) {
433
569
  if (hv.kind === "object") {
434
570
  const fields = /* @__PURE__ */ new Map();
435
571
  for (const [key, value] of hv.fields) {
436
- const folded = foldOptionalSelfRefAbsent(value, fullKey);
572
+ const folded = foldOptionalSelfRefAbsentInner(value, fullKey, false);
437
573
  if (folded === void 0) return void 0;
438
574
  fields.set(key, folded);
439
575
  }
@@ -806,7 +942,6 @@ var Lexer = class {
806
942
  if (!closed) {
807
943
  throw new ParseError("unterminated triple-quoted string", sl, sc);
808
944
  }
809
- if (value.startsWith("\n")) value = value.slice(1);
810
945
  this.push("triple_string", value, sl, sc, true);
811
946
  continue;
812
947
  }
@@ -1123,9 +1258,9 @@ var Parser = class {
1123
1258
  }
1124
1259
  }
1125
1260
  this.skip("newline");
1126
- const remaining = this.peek();
1127
- if (remaining.kind !== "eof") {
1128
- throw new ParseError(`Unexpected token '${remaining.value}' after closing brace`, remaining.line, remaining.col);
1261
+ const remaining2 = this.peek();
1262
+ if (remaining2.kind !== "eof") {
1263
+ throw new ParseError(`Unexpected token '${remaining2.value}' after closing brace`, remaining2.line, remaining2.col);
1129
1264
  }
1130
1265
  return { kind: "object", fields: allFields, pos: first.pos };
1131
1266
  }
@@ -1133,13 +1268,19 @@ var Parser = class {
1133
1268
  this.advance();
1134
1269
  const arr = this.parseArray();
1135
1270
  this.skip("newline");
1136
- const remaining = this.peek();
1137
- if (remaining.kind !== "eof") {
1138
- throw new ParseError(`Unexpected token '${remaining.value}' after root array`, remaining.line, remaining.col);
1271
+ const remaining2 = this.peek();
1272
+ if (remaining2.kind !== "eof") {
1273
+ throw new ParseError(`Unexpected token '${remaining2.value}' after root array`, remaining2.line, remaining2.col);
1139
1274
  }
1140
1275
  return { ...arr, pos: { line: t.line, col: t.col } };
1141
1276
  }
1142
- return this.parseObject(false);
1277
+ const root = this.parseObject(false);
1278
+ this.skip("newline");
1279
+ const remaining = this.peek();
1280
+ if (remaining.kind !== "eof") {
1281
+ throw new ParseError(`Unexpected token '${remaining.value}' after root content`, remaining.line, remaining.col);
1282
+ }
1283
+ return root;
1143
1284
  }
1144
1285
  peek(offset = 0) {
1145
1286
  return this.tokens[this.pos + offset] ?? EOF_TOKEN;
@@ -2286,6 +2427,12 @@ var SubstitutionResolver = class {
2286
2427
  // Cross-impl with rs.hocon v1.5.1's resolving_field_path (same purpose,
2287
2428
  // same prefix-match semantics).
2288
2429
  this.resolvingFieldPath = [];
2430
+ // S13a.12 recursion guard: field keys whose prior is currently being
2431
+ // resolved through the prefix-self-ref branch. Saved priors are
2432
+ // prefix-self-ref-free by the fold invariant, so re-entry only happens on
2433
+ // a shape the fold could not see through (a live subst mid-navigation) —
2434
+ // report it as a cycle instead of recursing forever.
2435
+ this.resolvingPrefixPriors = /* @__PURE__ */ new Set();
2289
2436
  }
2290
2437
  /** Returns a prefix for error messages, including origin info when set. */
2291
2438
  originPrefix() {
@@ -2300,46 +2447,57 @@ var SubstitutionResolver = class {
2300
2447
  this.resolvingFieldPath.push(key);
2301
2448
  const fullCacheKey = stringSegmentsToKey(this.resolvingFieldPath);
2302
2449
  this.cache.delete(fullCacheKey);
2303
- let resolved;
2304
2450
  try {
2305
- resolved = this.resolveVal(val, obj);
2306
- } finally {
2307
- this.resolvingFieldPath.pop();
2308
- }
2309
- if (resolved !== void 0) {
2310
- if (resolved.kind === "object") {
2451
+ const resolved = this.resolveVal(val, obj);
2452
+ if (resolved !== void 0) {
2453
+ if (resolved.kind === "object") {
2454
+ const prior = obj.priorValues.get(key);
2455
+ if (prior !== void 0) {
2456
+ const priorResolved = this.resolveVal(prior, obj);
2457
+ if (priorResolved !== void 0 && priorResolved.kind === "object") {
2458
+ const finalValue = deepMergeHoconValues(
2459
+ priorResolved,
2460
+ resolved
2461
+ );
2462
+ result.set(key, finalValue);
2463
+ this.cache.set(fullCacheKey, finalValue);
2464
+ this.cacheDescendants(fullCacheKey, finalValue);
2465
+ continue;
2466
+ }
2467
+ }
2468
+ }
2469
+ result.set(key, resolved);
2470
+ this.cache.set(fullCacheKey, resolved);
2471
+ this.cacheDescendants(fullCacheKey, resolved);
2472
+ } else {
2311
2473
  const prior = obj.priorValues.get(key);
2312
2474
  if (prior !== void 0) {
2313
2475
  const priorResolved = this.resolveVal(prior, obj);
2314
- if (priorResolved !== void 0 && priorResolved.kind === "object") {
2315
- const finalValue = deepMergeHoconValues(
2316
- priorResolved,
2317
- resolved
2318
- );
2319
- result.set(key, finalValue);
2320
- this.cache.set(fullCacheKey, finalValue);
2321
- this.cacheDescendants(fullCacheKey, finalValue);
2322
- continue;
2476
+ if (priorResolved !== void 0) {
2477
+ result.set(key, priorResolved);
2478
+ this.cache.set(fullCacheKey, priorResolved);
2479
+ this.cacheDescendants(fullCacheKey, priorResolved);
2323
2480
  }
2324
2481
  }
2325
2482
  }
2326
- result.set(key, resolved);
2327
- this.cache.set(fullCacheKey, resolved);
2328
- this.cacheDescendants(fullCacheKey, resolved);
2329
- } else {
2330
- const prior = obj.priorValues.get(key);
2331
- if (prior !== void 0) {
2332
- const priorResolved = this.resolveVal(prior, obj);
2333
- if (priorResolved !== void 0) {
2334
- result.set(key, priorResolved);
2335
- this.cache.set(fullCacheKey, priorResolved);
2336
- this.cacheDescendants(fullCacheKey, priorResolved);
2337
- }
2338
- }
2483
+ } finally {
2484
+ this.resolvingFieldPath.pop();
2339
2485
  }
2340
2486
  }
2341
2487
  return { kind: "object", fields: result };
2342
2488
  }
2489
+ /** S13a.12: walk resolved-object fields by segment text. A missing segment
2490
+ * or a walk into a scalar/array is path-absent → undefined. */
2491
+ navigateResolved(v, remainder) {
2492
+ let cur = v;
2493
+ for (const seg of remainder) {
2494
+ if (cur.kind !== "object") return void 0;
2495
+ const next = cur.fields.get(seg);
2496
+ if (next === void 0) return void 0;
2497
+ cur = next;
2498
+ }
2499
+ return cur;
2500
+ }
2343
2501
  cacheDescendants(prefix, value) {
2344
2502
  if (value.kind !== "object") return;
2345
2503
  for (const [key, child] of value.fields) {
@@ -2362,17 +2520,59 @@ var SubstitutionResolver = class {
2362
2520
  if (isResObj(v)) return this.resolveResObj(v);
2363
2521
  const hv = v;
2364
2522
  if (hv.kind === "array") {
2365
- return {
2366
- kind: "array",
2367
- items: hv.items.map(
2368
- (item) => this.resolveVal(item, scope) ?? { kind: "scalar", raw: "null", valueType: "null" }
2369
- )
2370
- };
2523
+ const items = [];
2524
+ for (const item of hv.items) {
2525
+ const resolved = this.resolveVal(item, scope);
2526
+ if (resolved === void 0) continue;
2527
+ items.push(resolved);
2528
+ }
2529
+ return { kind: "array", items };
2371
2530
  }
2372
2531
  return hv;
2373
2532
  }
2374
2533
  resolveSubst(s, scope) {
2375
- if (s.knownAbsent) return void 0;
2534
+ if (s.knownAbsent) {
2535
+ if (!s.optional) {
2536
+ if (this.opts.allowUnresolved) return s;
2537
+ const k = s.listSuffix ? `${segmentsToKey(s.segments)}[]` : segmentsToKey(s.segments);
2538
+ throw new ResolveError(
2539
+ `${this.originPrefix()}could not resolve substitution: \${${k}}`,
2540
+ k,
2541
+ s.line,
2542
+ s.col
2543
+ );
2544
+ }
2545
+ return void 0;
2546
+ }
2547
+ {
2548
+ const rfp = this.resolvingFieldPath;
2549
+ const sTexts = s.segments.map((seg) => seg.text);
2550
+ if (rfp.length > 0 && rfp.length < sTexts.length && stringSegmentsToKey(sTexts.slice(0, rfp.length)) === stringSegmentsToKey(rfp)) {
2551
+ const guardKey = stringSegmentsToKey(rfp);
2552
+ const prior = scope.priorValues.get(rfp[rfp.length - 1]);
2553
+ if (prior !== void 0 && !this.resolvingPrefixPriors.has(guardKey)) {
2554
+ this.resolvingPrefixPriors.add(guardKey);
2555
+ try {
2556
+ const priorResolved = this.resolveVal(prior, scope);
2557
+ if (priorResolved !== void 0) {
2558
+ const navigated = this.navigateResolved(priorResolved, sTexts.slice(rfp.length));
2559
+ if (navigated !== void 0) return navigated;
2560
+ }
2561
+ } finally {
2562
+ this.resolvingPrefixPriors.delete(guardKey);
2563
+ }
2564
+ }
2565
+ if (s.optional) return void 0;
2566
+ if (this.opts.allowUnresolved) return s;
2567
+ const k = s.listSuffix ? `${segmentsToKey(s.segments)}[]` : segmentsToKey(s.segments);
2568
+ throw new ResolveError(
2569
+ `${this.originPrefix()}could not resolve substitution: \${${k}}`,
2570
+ k,
2571
+ s.line,
2572
+ s.col
2573
+ );
2574
+ }
2575
+ }
2376
2576
  const key = s.listSuffix ? `${segmentsToKey(s.segments)}[]` : segmentsToKey(s.segments);
2377
2577
  if (this.cache.has(key)) return this.cache.get(key);
2378
2578
  if (this.resolving.has(key)) {
@@ -2699,6 +2899,121 @@ function resolve2(ast, opts) {
2699
2899
  return resolveTree(tree, { ...opts, useSystemEnvironment: true, allowUnresolved: false });
2700
2900
  }
2701
2901
 
2902
+ // src/render-hocon.ts
2903
+ function renderHoconFromRoot(root) {
2904
+ const out = [];
2905
+ renderObjectBody(out, root, 0);
2906
+ return out.join("");
2907
+ }
2908
+ function renderObjectBody(out, o, depth) {
2909
+ const indent = " ".repeat(depth);
2910
+ for (const [k, v] of o.fields) {
2911
+ out.push(indent, renderKey(k));
2912
+ if (v.kind === "object") {
2913
+ out.push(" {");
2914
+ if (v.fields.size === 0) {
2915
+ out.push("}\n");
2916
+ continue;
2917
+ }
2918
+ out.push("\n");
2919
+ renderObjectBody(out, v, depth + 1);
2920
+ out.push(indent, "}\n");
2921
+ } else {
2922
+ out.push(" = ");
2923
+ renderValue(out, v, depth);
2924
+ out.push("\n");
2925
+ }
2926
+ }
2927
+ }
2928
+ function renderValue(out, v, depth) {
2929
+ switch (v.kind) {
2930
+ case "object": {
2931
+ if (v.fields.size === 0) {
2932
+ out.push("{}");
2933
+ return;
2934
+ }
2935
+ out.push("{\n");
2936
+ renderObjectBody(out, v, depth + 1);
2937
+ out.push(" ".repeat(depth), "}");
2938
+ return;
2939
+ }
2940
+ case "array":
2941
+ renderArray(out, v, depth);
2942
+ return;
2943
+ case "scalar":
2944
+ out.push(renderScalar(v));
2945
+ return;
2946
+ default:
2947
+ throw new ConfigError(
2948
+ "renderHocon: unrenderable value (config must be resolved data)",
2949
+ ""
2950
+ );
2951
+ }
2952
+ }
2953
+ function renderArray(out, a, depth) {
2954
+ if (a.items.length === 0) {
2955
+ out.push("[]");
2956
+ return;
2957
+ }
2958
+ const inner = " ".repeat(depth + 1);
2959
+ out.push("[\n");
2960
+ for (const e of a.items) {
2961
+ out.push(inner);
2962
+ renderValue(out, e, depth + 1);
2963
+ out.push("\n");
2964
+ }
2965
+ out.push(" ".repeat(depth), "]");
2966
+ }
2967
+ function renderScalar(s) {
2968
+ switch (s.valueType) {
2969
+ case "null":
2970
+ return "null";
2971
+ case "boolean":
2972
+ case "number":
2973
+ return s.raw;
2974
+ case "string":
2975
+ return renderString(s.raw);
2976
+ }
2977
+ }
2978
+ var SAFE_UNQUOTED_KEY = /^[A-Za-z0-9_-]+$/;
2979
+ function renderKey(k) {
2980
+ if (k === "include") return quoteString(k);
2981
+ return SAFE_UNQUOTED_KEY.test(k) ? k : quoteString(k);
2982
+ }
2983
+ var SAFE_BARE_STRING = /^[A-Za-z][A-Za-z0-9_-]*$/;
2984
+ var STRING_KEYWORDS = /* @__PURE__ */ new Set(["true", "false", "null", "yes", "no", "on", "off"]);
2985
+ function renderString(s) {
2986
+ return SAFE_BARE_STRING.test(s) && !STRING_KEYWORDS.has(s.toLowerCase()) ? s : quoteString(s);
2987
+ }
2988
+ function quoteString(s) {
2989
+ if (s.includes("\n") && !s.includes("\r") && !s.includes('"""') && !s.endsWith('"')) {
2990
+ return `"""${s}"""`;
2991
+ }
2992
+ let b = '"';
2993
+ for (const ch of s) {
2994
+ switch (ch) {
2995
+ case '"':
2996
+ b += '\\"';
2997
+ break;
2998
+ case "\\":
2999
+ b += "\\\\";
3000
+ break;
3001
+ case "\n":
3002
+ b += "\\n";
3003
+ break;
3004
+ case "\r":
3005
+ b += "\\r";
3006
+ break;
3007
+ case " ":
3008
+ b += "\\t";
3009
+ break;
3010
+ default:
3011
+ b += ch;
3012
+ }
3013
+ }
3014
+ return b + '"';
3015
+ }
3016
+
2702
3017
  // src/config.ts
2703
3018
  var Config = class _Config {
2704
3019
  constructor(root, opts) {
@@ -3004,6 +3319,36 @@ var Config = class _Config {
3004
3319
  toObject(opts) {
3005
3320
  return hoconToJs(this.root, opts?.nullPrototype === true);
3006
3321
  }
3322
+ /**
3323
+ * Renders this resolved Config as HOCON text (E18; lockstep with go.hocon's
3324
+ * `Config.RenderHOCON`, v1.11.0).
3325
+ *
3326
+ * The output round-trips: parsing it back yields the same value tree. That
3327
+ * is the correctness contract, not byte-for-byte formatting — a scalar is
3328
+ * quoted whenever leaving it bare would re-parse as a different type (a
3329
+ * string `"8080"` becomes `"8080"`, not `8080`), and left bare only when it
3330
+ * provably cannot.
3331
+ *
3332
+ * The Config must be resolved and hold only data (objects, arrays, string /
3333
+ * number / boolean / null scalars) — exactly what {@link fromMap} and the
3334
+ * format adapters produce. An unresolved placeholder throws
3335
+ * {@link ConfigError}; substitutions have no textual round trip through a
3336
+ * value tree.
3337
+ *
3338
+ * The root object's fields are emitted without enclosing braces, nested
3339
+ * objects as `key { … }`, arrays as newline-separated `[ … ]`, indented two
3340
+ * spaces. Source comments are not represented — a value tree does not carry
3341
+ * them.
3342
+ */
3343
+ renderHocon() {
3344
+ if (!this._resolved) {
3345
+ throw new ConfigError(
3346
+ "renderHocon: unrenderable unresolved substitution (config must be resolved data)",
3347
+ ""
3348
+ );
3349
+ }
3350
+ return renderHoconFromRoot(this.root);
3351
+ }
3007
3352
  /**
3008
3353
  * @internal Test-only: renders this resolved Config's value tree as canonical
3009
3354
  * JSON (sorted keys, no whitespace). Used by Layer-2 fixture tests to compare