@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.js CHANGED
@@ -41,13 +41,22 @@ function coerceNumber(value) {
41
41
  return Number(value);
42
42
  }
43
43
  var DURATION_UNITS = {
44
+ // S19.1–S19.3: the bare nano/micro/milli (+plural) aliases are part of the
45
+ // spec's unit lists (HOCON.md L1307–L1309) and accepted by Lightbend
46
+ // (typesafe-config 1.4.6 probe, 2026-08-18); they were missing here.
44
47
  ns: 1e-6,
48
+ nano: 1e-6,
49
+ nanos: 1e-6,
45
50
  nanosecond: 1e-6,
46
51
  nanoseconds: 1e-6,
47
52
  us: 1e-3,
53
+ micro: 1e-3,
54
+ micros: 1e-3,
48
55
  microsecond: 1e-3,
49
56
  microseconds: 1e-3,
50
57
  ms: 1,
58
+ milli: 1,
59
+ millis: 1,
51
60
  millisecond: 1,
52
61
  milliseconds: 1,
53
62
  s: 1e3,
@@ -99,37 +108,76 @@ function parseDuration(value, outputUnit = "ms") {
99
108
  return ms / divisor;
100
109
  }
101
110
  var BYTE_UNITS = {
111
+ // S21.1–S21.4 — the EXACT Lightbend/typesafe-config unit set (1.4.6 probe,
112
+ // 2026-08-18). Multi-letter units are case-sensitive: the SI decimal short
113
+ // form is `kB` (NOT `KB`/`kb` — Lightbend rejects both), higher decimals
114
+ // are `MB`…`YB`, binary prefixes are `Ki`/`KiB`…`Yi`/`YiB` with capital
115
+ // first letter, and long forms are lowercase only (`kilobyte`, never
116
+ // `Kilobyte`). Only the bare byte unit (`B`/`b`) and the single-letter
117
+ // -Xmx forms accept both cases. The old case-insensitive fallback and the
118
+ // lowercase alias rows accepted spellings Lightbend rejects — removed.
102
119
  B: 1,
120
+ b: 1,
103
121
  byte: 1,
104
122
  bytes: 1,
105
- KB: 1e3,
123
+ kB: 1e3,
106
124
  kilobyte: 1e3,
107
125
  kilobytes: 1e3,
108
- KiB: 1024,
109
- kibibyte: 1024,
110
- kibibytes: 1024,
111
126
  MB: 1e6,
112
127
  megabyte: 1e6,
113
128
  megabytes: 1e6,
114
- MiB: 1048576,
115
- mebibyte: 1048576,
116
- mebibytes: 1048576,
117
129
  GB: 1e9,
118
130
  gigabyte: 1e9,
119
131
  gigabytes: 1e9,
120
- GiB: 1073741824,
121
- gibibyte: 1073741824,
122
- gibibytes: 1073741824,
123
132
  TB: 1e12,
124
133
  terabyte: 1e12,
125
134
  terabytes: 1e12,
135
+ PB: 1e15,
136
+ petabyte: 1e15,
137
+ petabytes: 1e15,
138
+ EB: 1e18,
139
+ exabyte: 1e18,
140
+ exabytes: 1e18,
141
+ ZB: 1e21,
142
+ zettabyte: 1e21,
143
+ zettabytes: 1e21,
144
+ YB: 1e24,
145
+ yottabyte: 1e24,
146
+ yottabytes: 1e24,
147
+ Ki: 1024,
148
+ KiB: 1024,
149
+ kibibyte: 1024,
150
+ kibibytes: 1024,
151
+ Mi: 1048576,
152
+ MiB: 1048576,
153
+ mebibyte: 1048576,
154
+ mebibytes: 1048576,
155
+ Gi: 1073741824,
156
+ GiB: 1073741824,
157
+ gibibyte: 1073741824,
158
+ gibibytes: 1073741824,
159
+ Ti: 1099511627776,
126
160
  TiB: 1099511627776,
127
161
  tebibyte: 1099511627776,
128
162
  tebibytes: 1099511627776,
129
- // S21.4 — HOCON.md L1385: single-letter abbreviations → powers of two (java -Xmx convention).
130
- // Lightbend typesafe-config 1.4.3 verified: 1K=1024, 1M=1048576, etc.
131
- // Both upper- and lowercase accepted per Lightbend behaviour.
132
- // Z/Y (2^70/2^80) are deferred — they overflow MAX_SAFE_INTEGER and require BigInt accessor.
163
+ Pi: 1024 ** 5,
164
+ PiB: 1024 ** 5,
165
+ pebibyte: 1024 ** 5,
166
+ pebibytes: 1024 ** 5,
167
+ Ei: 1024 ** 6,
168
+ EiB: 1024 ** 6,
169
+ exbibyte: 1024 ** 6,
170
+ exbibytes: 1024 ** 6,
171
+ Zi: 1024 ** 7,
172
+ ZiB: 1024 ** 7,
173
+ zebibyte: 1024 ** 7,
174
+ zebibytes: 1024 ** 7,
175
+ Yi: 1024 ** 8,
176
+ YiB: 1024 ** 8,
177
+ yobibyte: 1024 ** 8,
178
+ yobibytes: 1024 ** 8,
179
+ // S21.4 — single-letter abbreviations → powers of two (java -Xmx
180
+ // convention); Lightbend accepts BOTH cases here, through the full ladder.
133
181
  K: 1024,
134
182
  k: 1024,
135
183
  M: 1024 ** 2,
@@ -142,16 +190,10 @@ var BYTE_UNITS = {
142
190
  p: 1024 ** 5,
143
191
  E: 1024 ** 6,
144
192
  e: 1024 ** 6,
145
- // lowercase short-form aliases
146
- b: 1,
147
- kb: 1e3,
148
- kib: 1024,
149
- mb: 1e6,
150
- mib: 1048576,
151
- gb: 1e9,
152
- gib: 1073741824,
153
- tb: 1e12,
154
- tib: 1099511627776
193
+ Z: 1024 ** 7,
194
+ z: 1024 ** 7,
195
+ Y: 1024 ** 8,
196
+ y: 1024 ** 8
155
197
  };
156
198
  var OUTPUT_BYTE_UNITS = {
157
199
  B: 1,
@@ -189,10 +231,7 @@ function parseBytes(value, outputUnit = "B") {
189
231
  }
190
232
  return bytes2 / divisor;
191
233
  }
192
- let mult = BYTE_UNITS[unit];
193
- if (mult === void 0) {
194
- mult = BYTE_UNITS[unit.toLowerCase()];
195
- }
234
+ const mult = BYTE_UNITS[unit];
196
235
  if (mult === void 0) return NaN;
197
236
  const bytes = num * mult;
198
237
  if (Math.abs(bytes) > Number.MAX_SAFE_INTEGER) {
@@ -260,35 +299,114 @@ function stringSegmentsToKey(segments) {
260
299
  return t;
261
300
  }).join(".");
262
301
  }
302
+ function prefixSelfRefRemainder(s, fullKey) {
303
+ const texts = s.segments.map((seg) => seg.text);
304
+ if (!stringSegmentsToKey(texts).startsWith(fullKey + ".")) return null;
305
+ for (let n = 1; n < texts.length; n++) {
306
+ if (stringSegmentsToKey(texts.slice(0, n)) === fullKey) return texts.slice(n);
307
+ }
308
+ return null;
309
+ }
310
+ function navigateResolverValue(v, remainder) {
311
+ let cur = v;
312
+ for (const seg of remainder) {
313
+ if (isSubst(cur) || isConcat(cur)) return null;
314
+ if (isResObj(cur)) {
315
+ const next = cur.fields.get(seg);
316
+ if (next === void 0) return void 0;
317
+ cur = next;
318
+ continue;
319
+ }
320
+ const hv = cur;
321
+ if (hv.kind === "object") {
322
+ const next = hv.fields.get(seg);
323
+ if (next === void 0) return void 0;
324
+ cur = next;
325
+ continue;
326
+ }
327
+ return void 0;
328
+ }
329
+ return cur;
330
+ }
331
+ function foldPrefixSelfRef(s, replacement, remainder) {
332
+ const navigated = navigateResolverValue(replacement, remainder);
333
+ if (navigated === null) return s;
334
+ if (navigated === void 0) {
335
+ return {
336
+ _kind: "subst-placeholder",
337
+ segments: s.segments.map((seg) => ({ text: seg.text, line: seg.line, col: seg.col })),
338
+ optional: s.optional,
339
+ knownAbsent: true,
340
+ listSuffix: s.listSuffix,
341
+ line: s.line,
342
+ col: s.col,
343
+ prefixLen: s.prefixLen
344
+ };
345
+ }
346
+ return cloneResolverValue(navigated);
347
+ }
348
+ function mergeResObjLayers(base, top) {
349
+ const fields = new Map(base.fields);
350
+ for (const [k, tv] of top.fields) {
351
+ const bv = fields.get(k);
352
+ if (bv !== void 0 && isResObj(bv) && isResObj(tv)) {
353
+ fields.set(k, mergeResObjLayers(bv, tv));
354
+ } else {
355
+ fields.set(k, tv);
356
+ }
357
+ }
358
+ const priorValues = new Map(base.priorValues);
359
+ for (const [k, pv] of top.priorValues) priorValues.set(k, pv);
360
+ const resetKeys = new Set(base.resetKeys);
361
+ for (const k of top.resetKeys) resetKeys.add(k);
362
+ return {
363
+ _kind: "res-obj",
364
+ fields,
365
+ priorValues,
366
+ resetKeys
367
+ };
368
+ }
263
369
  function containsSelfRef(v, fullKey) {
264
- if (isSubst(v)) return !v.knownAbsent && substFullKey(v) === fullKey;
265
- if (isConcat(v)) return v.nodes.some((n) => containsSelfRef(n, fullKey));
370
+ return containsSelfRefInner(v, fullKey, true);
371
+ }
372
+ function containsSelfRefInner(v, fullKey, allowPrefix) {
373
+ if (isSubst(v))
374
+ return !v.knownAbsent && (substFullKey(v) === fullKey || allowPrefix && prefixSelfRefRemainder(v, fullKey) !== null);
375
+ if (isConcat(v)) return v.nodes.some((n) => containsSelfRefInner(n, fullKey, allowPrefix));
266
376
  if (isResObj(v)) {
267
377
  for (const f of v.fields.values()) {
268
- if (containsSelfRef(f, fullKey)) return true;
378
+ if (containsSelfRefInner(f, fullKey, false)) return true;
269
379
  }
270
380
  return false;
271
381
  }
272
382
  const hv = v;
273
383
  if (hv.kind === "array") {
274
- return hv.items.some((item) => containsSelfRef(item, fullKey));
384
+ return hv.items.some((item) => containsSelfRefInner(item, fullKey, allowPrefix));
275
385
  }
276
386
  if (hv.kind === "object") {
277
387
  for (const f of hv.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
  return false;
283
393
  }
284
394
  function foldSelfRef(v, fullKey, replacement) {
395
+ return foldSelfRefInner(v, fullKey, replacement, true);
396
+ }
397
+ function foldSelfRefInner(v, fullKey, replacement, allowPrefix) {
285
398
  if (isSubst(v)) {
286
- return substFullKey(v) === fullKey ? replacement : v;
399
+ if (substFullKey(v) === fullKey) return replacement;
400
+ if (allowPrefix) {
401
+ const remainder = prefixSelfRefRemainder(v, fullKey);
402
+ if (remainder !== null) return foldPrefixSelfRef(v, replacement, remainder);
403
+ }
404
+ return v;
287
405
  }
288
406
  if (isConcat(v)) {
289
407
  return {
290
408
  _kind: "concat-placeholder",
291
- nodes: v.nodes.map((n) => foldSelfRef(n, fullKey, replacement)),
409
+ nodes: v.nodes.map((n) => foldSelfRefInner(n, fullKey, replacement, allowPrefix)),
292
410
  line: v.line,
293
411
  col: v.col
294
412
  };
@@ -296,7 +414,7 @@ function foldSelfRef(v, fullKey, replacement) {
296
414
  if (isResObj(v)) {
297
415
  const newFields = /* @__PURE__ */ new Map();
298
416
  for (const [k, val] of v.fields) {
299
- newFields.set(k, foldSelfRef(val, fullKey, replacement));
417
+ newFields.set(k, foldSelfRefInner(val, fullKey, replacement, false));
300
418
  }
301
419
  return { _kind: "res-obj", fields: newFields, priorValues: new Map(v.priorValues), resetKeys: new Set(v.resetKeys) };
302
420
  }
@@ -304,13 +422,15 @@ function foldSelfRef(v, fullKey, replacement) {
304
422
  if (hv.kind === "array") {
305
423
  return {
306
424
  kind: "array",
307
- items: hv.items.map((item) => foldSelfRef(item, fullKey, replacement))
425
+ items: hv.items.map(
426
+ (item) => foldSelfRefInner(item, fullKey, replacement, allowPrefix)
427
+ )
308
428
  };
309
429
  }
310
430
  if (hv.kind === "object") {
311
431
  const newFields = /* @__PURE__ */ new Map();
312
432
  for (const [k, val] of hv.fields) {
313
- newFields.set(k, foldSelfRef(val, fullKey, replacement));
433
+ newFields.set(k, foldSelfRefInner(val, fullKey, replacement, false));
314
434
  }
315
435
  return { kind: "object", fields: newFields };
316
436
  }
@@ -354,10 +474,26 @@ function foldKnownAbsentSelfRef(v, fullKey, replacement) {
354
474
  function foldOrSkipPrior(prior, fullKey, old) {
355
475
  if (!containsSelfRef(prior, fullKey)) return cloneResolverValue(prior);
356
476
  if (old === void 0) return foldOptionalSelfRefAbsent(prior, fullKey);
477
+ if (isSubst(prior)) {
478
+ const remainder = prefixSelfRefRemainder(prior, fullKey);
479
+ if (remainder !== null) {
480
+ const navigated = navigateResolverValue(old, remainder);
481
+ if (navigated === void 0 && prior.optional) return cloneResolverValue(old);
482
+ if (navigated !== null && navigated !== void 0 && isResObj(navigated) && isResObj(old)) {
483
+ return mergeResObjLayers(
484
+ cloneResolverValue(old),
485
+ cloneResolverValue(navigated)
486
+ );
487
+ }
488
+ }
489
+ }
357
490
  return foldSelfRef(prior, fullKey, old);
358
491
  }
359
492
  function foldOptionalSelfRefAbsent(v, fullKey) {
360
- if (isSubst(v) && substFullKey(v) === fullKey) {
493
+ return foldOptionalSelfRefAbsentInner(v, fullKey, true);
494
+ }
495
+ function foldOptionalSelfRefAbsentInner(v, fullKey, allowPrefix) {
496
+ if (isSubst(v) && (substFullKey(v) === fullKey || allowPrefix && prefixSelfRefRemainder(v, fullKey) !== null)) {
361
497
  if (!v.optional) return void 0;
362
498
  return {
363
499
  _kind: "subst-placeholder",
@@ -373,7 +509,7 @@ function foldOptionalSelfRefAbsent(v, fullKey) {
373
509
  if (isConcat(v)) {
374
510
  const nodes = [];
375
511
  for (const node of v.nodes) {
376
- const folded = foldOptionalSelfRefAbsent(node, fullKey);
512
+ const folded = foldOptionalSelfRefAbsentInner(node, fullKey, allowPrefix);
377
513
  if (folded === void 0) return void 0;
378
514
  nodes.push(folded);
379
515
  }
@@ -382,7 +518,7 @@ function foldOptionalSelfRefAbsent(v, fullKey) {
382
518
  if (isResObj(v)) {
383
519
  const fields = /* @__PURE__ */ new Map();
384
520
  for (const [key, value] of v.fields) {
385
- const folded = foldOptionalSelfRefAbsent(value, fullKey);
521
+ const folded = foldOptionalSelfRefAbsentInner(value, fullKey, false);
386
522
  if (folded === void 0) return void 0;
387
523
  fields.set(key, folded);
388
524
  }
@@ -392,7 +528,7 @@ function foldOptionalSelfRefAbsent(v, fullKey) {
392
528
  if (hv.kind === "array") {
393
529
  const items = [];
394
530
  for (const item of hv.items) {
395
- const folded = foldOptionalSelfRefAbsent(item, fullKey);
531
+ const folded = foldOptionalSelfRefAbsentInner(item, fullKey, allowPrefix);
396
532
  if (folded === void 0) return void 0;
397
533
  items.push(folded);
398
534
  }
@@ -401,7 +537,7 @@ function foldOptionalSelfRefAbsent(v, fullKey) {
401
537
  if (hv.kind === "object") {
402
538
  const fields = /* @__PURE__ */ new Map();
403
539
  for (const [key, value] of hv.fields) {
404
- const folded = foldOptionalSelfRefAbsent(value, fullKey);
540
+ const folded = foldOptionalSelfRefAbsentInner(value, fullKey, false);
405
541
  if (folded === void 0) return void 0;
406
542
  fields.set(key, folded);
407
543
  }
@@ -774,7 +910,6 @@ var Lexer = class {
774
910
  if (!closed) {
775
911
  throw new ParseError("unterminated triple-quoted string", sl, sc);
776
912
  }
777
- if (value.startsWith("\n")) value = value.slice(1);
778
913
  this.push("triple_string", value, sl, sc, true);
779
914
  continue;
780
915
  }
@@ -1091,9 +1226,9 @@ var Parser = class {
1091
1226
  }
1092
1227
  }
1093
1228
  this.skip("newline");
1094
- const remaining = this.peek();
1095
- if (remaining.kind !== "eof") {
1096
- throw new ParseError(`Unexpected token '${remaining.value}' after closing brace`, remaining.line, remaining.col);
1229
+ const remaining2 = this.peek();
1230
+ if (remaining2.kind !== "eof") {
1231
+ throw new ParseError(`Unexpected token '${remaining2.value}' after closing brace`, remaining2.line, remaining2.col);
1097
1232
  }
1098
1233
  return { kind: "object", fields: allFields, pos: first.pos };
1099
1234
  }
@@ -1101,13 +1236,19 @@ var Parser = class {
1101
1236
  this.advance();
1102
1237
  const arr = this.parseArray();
1103
1238
  this.skip("newline");
1104
- const remaining = this.peek();
1105
- if (remaining.kind !== "eof") {
1106
- throw new ParseError(`Unexpected token '${remaining.value}' after root array`, remaining.line, remaining.col);
1239
+ const remaining2 = this.peek();
1240
+ if (remaining2.kind !== "eof") {
1241
+ throw new ParseError(`Unexpected token '${remaining2.value}' after root array`, remaining2.line, remaining2.col);
1107
1242
  }
1108
1243
  return { ...arr, pos: { line: t.line, col: t.col } };
1109
1244
  }
1110
- return this.parseObject(false);
1245
+ const root = this.parseObject(false);
1246
+ this.skip("newline");
1247
+ const remaining = this.peek();
1248
+ if (remaining.kind !== "eof") {
1249
+ throw new ParseError(`Unexpected token '${remaining.value}' after root content`, remaining.line, remaining.col);
1250
+ }
1251
+ return root;
1111
1252
  }
1112
1253
  peek(offset = 0) {
1113
1254
  return this.tokens[this.pos + offset] ?? EOF_TOKEN;
@@ -2253,6 +2394,12 @@ var SubstitutionResolver = class {
2253
2394
  // Cross-impl with rs.hocon v1.5.1's resolving_field_path (same purpose,
2254
2395
  // same prefix-match semantics).
2255
2396
  this.resolvingFieldPath = [];
2397
+ // S13a.12 recursion guard: field keys whose prior is currently being
2398
+ // resolved through the prefix-self-ref branch. Saved priors are
2399
+ // prefix-self-ref-free by the fold invariant, so re-entry only happens on
2400
+ // a shape the fold could not see through (a live subst mid-navigation) —
2401
+ // report it as a cycle instead of recursing forever.
2402
+ this.resolvingPrefixPriors = /* @__PURE__ */ new Set();
2256
2403
  }
2257
2404
  /** Returns a prefix for error messages, including origin info when set. */
2258
2405
  originPrefix() {
@@ -2267,46 +2414,57 @@ var SubstitutionResolver = class {
2267
2414
  this.resolvingFieldPath.push(key);
2268
2415
  const fullCacheKey = stringSegmentsToKey(this.resolvingFieldPath);
2269
2416
  this.cache.delete(fullCacheKey);
2270
- let resolved;
2271
2417
  try {
2272
- resolved = this.resolveVal(val, obj);
2273
- } finally {
2274
- this.resolvingFieldPath.pop();
2275
- }
2276
- if (resolved !== void 0) {
2277
- if (resolved.kind === "object") {
2418
+ const resolved = this.resolveVal(val, obj);
2419
+ if (resolved !== void 0) {
2420
+ if (resolved.kind === "object") {
2421
+ const prior = obj.priorValues.get(key);
2422
+ if (prior !== void 0) {
2423
+ const priorResolved = this.resolveVal(prior, obj);
2424
+ if (priorResolved !== void 0 && priorResolved.kind === "object") {
2425
+ const finalValue = deepMergeHoconValues(
2426
+ priorResolved,
2427
+ resolved
2428
+ );
2429
+ result.set(key, finalValue);
2430
+ this.cache.set(fullCacheKey, finalValue);
2431
+ this.cacheDescendants(fullCacheKey, finalValue);
2432
+ continue;
2433
+ }
2434
+ }
2435
+ }
2436
+ result.set(key, resolved);
2437
+ this.cache.set(fullCacheKey, resolved);
2438
+ this.cacheDescendants(fullCacheKey, resolved);
2439
+ } else {
2278
2440
  const prior = obj.priorValues.get(key);
2279
2441
  if (prior !== void 0) {
2280
2442
  const priorResolved = this.resolveVal(prior, obj);
2281
- if (priorResolved !== void 0 && priorResolved.kind === "object") {
2282
- const finalValue = deepMergeHoconValues(
2283
- priorResolved,
2284
- resolved
2285
- );
2286
- result.set(key, finalValue);
2287
- this.cache.set(fullCacheKey, finalValue);
2288
- this.cacheDescendants(fullCacheKey, finalValue);
2289
- continue;
2443
+ if (priorResolved !== void 0) {
2444
+ result.set(key, priorResolved);
2445
+ this.cache.set(fullCacheKey, priorResolved);
2446
+ this.cacheDescendants(fullCacheKey, priorResolved);
2290
2447
  }
2291
2448
  }
2292
2449
  }
2293
- result.set(key, resolved);
2294
- this.cache.set(fullCacheKey, resolved);
2295
- this.cacheDescendants(fullCacheKey, resolved);
2296
- } else {
2297
- const prior = obj.priorValues.get(key);
2298
- if (prior !== void 0) {
2299
- const priorResolved = this.resolveVal(prior, obj);
2300
- if (priorResolved !== void 0) {
2301
- result.set(key, priorResolved);
2302
- this.cache.set(fullCacheKey, priorResolved);
2303
- this.cacheDescendants(fullCacheKey, priorResolved);
2304
- }
2305
- }
2450
+ } finally {
2451
+ this.resolvingFieldPath.pop();
2306
2452
  }
2307
2453
  }
2308
2454
  return { kind: "object", fields: result };
2309
2455
  }
2456
+ /** S13a.12: walk resolved-object fields by segment text. A missing segment
2457
+ * or a walk into a scalar/array is path-absent → undefined. */
2458
+ navigateResolved(v, remainder) {
2459
+ let cur = v;
2460
+ for (const seg of remainder) {
2461
+ if (cur.kind !== "object") return void 0;
2462
+ const next = cur.fields.get(seg);
2463
+ if (next === void 0) return void 0;
2464
+ cur = next;
2465
+ }
2466
+ return cur;
2467
+ }
2310
2468
  cacheDescendants(prefix, value) {
2311
2469
  if (value.kind !== "object") return;
2312
2470
  for (const [key, child] of value.fields) {
@@ -2329,17 +2487,59 @@ var SubstitutionResolver = class {
2329
2487
  if (isResObj(v)) return this.resolveResObj(v);
2330
2488
  const hv = v;
2331
2489
  if (hv.kind === "array") {
2332
- return {
2333
- kind: "array",
2334
- items: hv.items.map(
2335
- (item) => this.resolveVal(item, scope) ?? { kind: "scalar", raw: "null", valueType: "null" }
2336
- )
2337
- };
2490
+ const items = [];
2491
+ for (const item of hv.items) {
2492
+ const resolved = this.resolveVal(item, scope);
2493
+ if (resolved === void 0) continue;
2494
+ items.push(resolved);
2495
+ }
2496
+ return { kind: "array", items };
2338
2497
  }
2339
2498
  return hv;
2340
2499
  }
2341
2500
  resolveSubst(s, scope) {
2342
- if (s.knownAbsent) return void 0;
2501
+ if (s.knownAbsent) {
2502
+ if (!s.optional) {
2503
+ if (this.opts.allowUnresolved) return s;
2504
+ const k = s.listSuffix ? `${segmentsToKey(s.segments)}[]` : segmentsToKey(s.segments);
2505
+ throw new ResolveError(
2506
+ `${this.originPrefix()}could not resolve substitution: \${${k}}`,
2507
+ k,
2508
+ s.line,
2509
+ s.col
2510
+ );
2511
+ }
2512
+ return void 0;
2513
+ }
2514
+ {
2515
+ const rfp = this.resolvingFieldPath;
2516
+ const sTexts = s.segments.map((seg) => seg.text);
2517
+ if (rfp.length > 0 && rfp.length < sTexts.length && stringSegmentsToKey(sTexts.slice(0, rfp.length)) === stringSegmentsToKey(rfp)) {
2518
+ const guardKey = stringSegmentsToKey(rfp);
2519
+ const prior = scope.priorValues.get(rfp[rfp.length - 1]);
2520
+ if (prior !== void 0 && !this.resolvingPrefixPriors.has(guardKey)) {
2521
+ this.resolvingPrefixPriors.add(guardKey);
2522
+ try {
2523
+ const priorResolved = this.resolveVal(prior, scope);
2524
+ if (priorResolved !== void 0) {
2525
+ const navigated = this.navigateResolved(priorResolved, sTexts.slice(rfp.length));
2526
+ if (navigated !== void 0) return navigated;
2527
+ }
2528
+ } finally {
2529
+ this.resolvingPrefixPriors.delete(guardKey);
2530
+ }
2531
+ }
2532
+ if (s.optional) return void 0;
2533
+ if (this.opts.allowUnresolved) return s;
2534
+ const k = s.listSuffix ? `${segmentsToKey(s.segments)}[]` : segmentsToKey(s.segments);
2535
+ throw new ResolveError(
2536
+ `${this.originPrefix()}could not resolve substitution: \${${k}}`,
2537
+ k,
2538
+ s.line,
2539
+ s.col
2540
+ );
2541
+ }
2542
+ }
2343
2543
  const key = s.listSuffix ? `${segmentsToKey(s.segments)}[]` : segmentsToKey(s.segments);
2344
2544
  if (this.cache.has(key)) return this.cache.get(key);
2345
2545
  if (this.resolving.has(key)) {
@@ -2666,6 +2866,121 @@ function resolve2(ast, opts) {
2666
2866
  return resolveTree(tree, { ...opts, useSystemEnvironment: true, allowUnresolved: false });
2667
2867
  }
2668
2868
 
2869
+ // src/render-hocon.ts
2870
+ function renderHoconFromRoot(root) {
2871
+ const out = [];
2872
+ renderObjectBody(out, root, 0);
2873
+ return out.join("");
2874
+ }
2875
+ function renderObjectBody(out, o, depth) {
2876
+ const indent = " ".repeat(depth);
2877
+ for (const [k, v] of o.fields) {
2878
+ out.push(indent, renderKey(k));
2879
+ if (v.kind === "object") {
2880
+ out.push(" {");
2881
+ if (v.fields.size === 0) {
2882
+ out.push("}\n");
2883
+ continue;
2884
+ }
2885
+ out.push("\n");
2886
+ renderObjectBody(out, v, depth + 1);
2887
+ out.push(indent, "}\n");
2888
+ } else {
2889
+ out.push(" = ");
2890
+ renderValue(out, v, depth);
2891
+ out.push("\n");
2892
+ }
2893
+ }
2894
+ }
2895
+ function renderValue(out, v, depth) {
2896
+ switch (v.kind) {
2897
+ case "object": {
2898
+ if (v.fields.size === 0) {
2899
+ out.push("{}");
2900
+ return;
2901
+ }
2902
+ out.push("{\n");
2903
+ renderObjectBody(out, v, depth + 1);
2904
+ out.push(" ".repeat(depth), "}");
2905
+ return;
2906
+ }
2907
+ case "array":
2908
+ renderArray(out, v, depth);
2909
+ return;
2910
+ case "scalar":
2911
+ out.push(renderScalar(v));
2912
+ return;
2913
+ default:
2914
+ throw new ConfigError(
2915
+ "renderHocon: unrenderable value (config must be resolved data)",
2916
+ ""
2917
+ );
2918
+ }
2919
+ }
2920
+ function renderArray(out, a, depth) {
2921
+ if (a.items.length === 0) {
2922
+ out.push("[]");
2923
+ return;
2924
+ }
2925
+ const inner = " ".repeat(depth + 1);
2926
+ out.push("[\n");
2927
+ for (const e of a.items) {
2928
+ out.push(inner);
2929
+ renderValue(out, e, depth + 1);
2930
+ out.push("\n");
2931
+ }
2932
+ out.push(" ".repeat(depth), "]");
2933
+ }
2934
+ function renderScalar(s) {
2935
+ switch (s.valueType) {
2936
+ case "null":
2937
+ return "null";
2938
+ case "boolean":
2939
+ case "number":
2940
+ return s.raw;
2941
+ case "string":
2942
+ return renderString(s.raw);
2943
+ }
2944
+ }
2945
+ var SAFE_UNQUOTED_KEY = /^[A-Za-z0-9_-]+$/;
2946
+ function renderKey(k) {
2947
+ if (k === "include") return quoteString(k);
2948
+ return SAFE_UNQUOTED_KEY.test(k) ? k : quoteString(k);
2949
+ }
2950
+ var SAFE_BARE_STRING = /^[A-Za-z][A-Za-z0-9_-]*$/;
2951
+ var STRING_KEYWORDS = /* @__PURE__ */ new Set(["true", "false", "null", "yes", "no", "on", "off"]);
2952
+ function renderString(s) {
2953
+ return SAFE_BARE_STRING.test(s) && !STRING_KEYWORDS.has(s.toLowerCase()) ? s : quoteString(s);
2954
+ }
2955
+ function quoteString(s) {
2956
+ if (s.includes("\n") && !s.includes("\r") && !s.includes('"""') && !s.endsWith('"')) {
2957
+ return `"""${s}"""`;
2958
+ }
2959
+ let b = '"';
2960
+ for (const ch of s) {
2961
+ switch (ch) {
2962
+ case '"':
2963
+ b += '\\"';
2964
+ break;
2965
+ case "\\":
2966
+ b += "\\\\";
2967
+ break;
2968
+ case "\n":
2969
+ b += "\\n";
2970
+ break;
2971
+ case "\r":
2972
+ b += "\\r";
2973
+ break;
2974
+ case " ":
2975
+ b += "\\t";
2976
+ break;
2977
+ default:
2978
+ b += ch;
2979
+ }
2980
+ }
2981
+ return b + '"';
2982
+ }
2983
+
2669
2984
  // src/config.ts
2670
2985
  var Config = class _Config {
2671
2986
  constructor(root, opts) {
@@ -2971,6 +3286,36 @@ var Config = class _Config {
2971
3286
  toObject(opts) {
2972
3287
  return hoconToJs(this.root, opts?.nullPrototype === true);
2973
3288
  }
3289
+ /**
3290
+ * Renders this resolved Config as HOCON text (E18; lockstep with go.hocon's
3291
+ * `Config.RenderHOCON`, v1.11.0).
3292
+ *
3293
+ * The output round-trips: parsing it back yields the same value tree. That
3294
+ * is the correctness contract, not byte-for-byte formatting — a scalar is
3295
+ * quoted whenever leaving it bare would re-parse as a different type (a
3296
+ * string `"8080"` becomes `"8080"`, not `8080`), and left bare only when it
3297
+ * provably cannot.
3298
+ *
3299
+ * The Config must be resolved and hold only data (objects, arrays, string /
3300
+ * number / boolean / null scalars) — exactly what {@link fromMap} and the
3301
+ * format adapters produce. An unresolved placeholder throws
3302
+ * {@link ConfigError}; substitutions have no textual round trip through a
3303
+ * value tree.
3304
+ *
3305
+ * The root object's fields are emitted without enclosing braces, nested
3306
+ * objects as `key { … }`, arrays as newline-separated `[ … ]`, indented two
3307
+ * spaces. Source comments are not represented — a value tree does not carry
3308
+ * them.
3309
+ */
3310
+ renderHocon() {
3311
+ if (!this._resolved) {
3312
+ throw new ConfigError(
3313
+ "renderHocon: unrenderable unresolved substitution (config must be resolved data)",
3314
+ ""
3315
+ );
3316
+ }
3317
+ return renderHoconFromRoot(this.root);
3318
+ }
2974
3319
  /**
2975
3320
  * @internal Test-only: renders this resolved Config's value tree as canonical
2976
3321
  * JSON (sorted keys, no whitespace). Used by Layer-2 fixture tests to compare