@o3co/ts.hocon 1.6.0 → 1.7.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
@@ -241,55 +241,6 @@ var NotResolvedError = class extends ConfigError {
241
241
  }
242
242
  };
243
243
 
244
- // src/internal/resolver/types.ts
245
- var separatorValues = /* @__PURE__ */ new WeakSet();
246
- function isSubst(v) {
247
- return v._kind === "subst-placeholder";
248
- }
249
- function isConcat(v) {
250
- return v._kind === "concat-placeholder";
251
- }
252
- function isAppend(v) {
253
- return v._kind === "append-placeholder";
254
- }
255
- function isResObj(v) {
256
- return v._kind === "res-obj";
257
- }
258
- function makeResObj() {
259
- return { _kind: "res-obj", fields: /* @__PURE__ */ new Map(), priorValues: /* @__PURE__ */ new Map() };
260
- }
261
- function mergeUnresolved(receiver, fallback) {
262
- const result = makeResObj();
263
- for (const [k, v] of fallback.fields) {
264
- result.fields.set(k, v);
265
- }
266
- for (const [k, v] of fallback.priorValues) {
267
- result.priorValues.set(k, v);
268
- }
269
- for (const [k, rv] of receiver.fields) {
270
- const existing = result.fields.get(k);
271
- if (existing !== void 0) {
272
- const recPrior = receiver.priorValues.get(k);
273
- if (recPrior !== void 0 && !isResObj(recPrior)) {
274
- result.fields.set(k, rv);
275
- continue;
276
- }
277
- if (isResObj(rv) && isResObj(existing)) {
278
- result.fields.set(k, mergeUnresolved(rv, existing));
279
- continue;
280
- }
281
- result.priorValues.set(k, existing);
282
- }
283
- result.fields.set(k, rv);
284
- }
285
- for (const [k, v] of receiver.priorValues) {
286
- if (receiver.fields.has(k)) {
287
- result.priorValues.set(k, v);
288
- }
289
- }
290
- return result;
291
- }
292
-
293
244
  // src/internal/resolver/fold-self-ref.ts
294
245
  function substFullKey(s) {
295
246
  return stringSegmentsToKey(s.segments.map((seg) => seg.text));
@@ -305,9 +256,6 @@ function stringSegmentsToKey(segments) {
305
256
  function containsSelfRef(v, fullKey) {
306
257
  if (isSubst(v)) return !v.knownAbsent && substFullKey(v) === fullKey;
307
258
  if (isConcat(v)) return v.nodes.some((n) => containsSelfRef(n, fullKey));
308
- if (isAppend(v)) {
309
- return containsSelfRef(v.existing, fullKey) || containsSelfRef(v.elem, fullKey);
310
- }
311
259
  if (isResObj(v)) {
312
260
  for (const f of v.fields.values()) {
313
261
  if (containsSelfRef(f, fullKey)) return true;
@@ -338,19 +286,12 @@ function foldSelfRef(v, fullKey, replacement) {
338
286
  col: v.col
339
287
  };
340
288
  }
341
- if (isAppend(v)) {
342
- return {
343
- _kind: "append-placeholder",
344
- existing: foldSelfRef(v.existing, fullKey, replacement),
345
- elem: foldSelfRef(v.elem, fullKey, replacement)
346
- };
347
- }
348
289
  if (isResObj(v)) {
349
290
  const newFields = /* @__PURE__ */ new Map();
350
291
  for (const [k, val] of v.fields) {
351
292
  newFields.set(k, foldSelfRef(val, fullKey, replacement));
352
293
  }
353
- return { _kind: "res-obj", fields: newFields, priorValues: new Map(v.priorValues) };
294
+ return { _kind: "res-obj", fields: newFields, priorValues: new Map(v.priorValues), resetKeys: new Set(v.resetKeys) };
354
295
  }
355
296
  const hv = v;
356
297
  if (hv.kind === "array") {
@@ -368,6 +309,41 @@ function foldSelfRef(v, fullKey, replacement) {
368
309
  }
369
310
  return v;
370
311
  }
312
+ function foldKnownAbsentSelfRef(v, fullKey, replacement) {
313
+ if (isSubst(v)) {
314
+ return v.knownAbsent && substFullKey(v) === fullKey ? replacement : v;
315
+ }
316
+ if (isConcat(v)) {
317
+ return {
318
+ _kind: "concat-placeholder",
319
+ nodes: v.nodes.map((n) => foldKnownAbsentSelfRef(n, fullKey, replacement)),
320
+ line: v.line,
321
+ col: v.col
322
+ };
323
+ }
324
+ if (isResObj(v)) {
325
+ const newFields = /* @__PURE__ */ new Map();
326
+ for (const [k, val] of v.fields) {
327
+ newFields.set(k, foldKnownAbsentSelfRef(val, fullKey, replacement));
328
+ }
329
+ return { _kind: "res-obj", fields: newFields, priorValues: new Map(v.priorValues), resetKeys: new Set(v.resetKeys) };
330
+ }
331
+ const hv = v;
332
+ if (hv.kind === "array") {
333
+ return {
334
+ kind: "array",
335
+ items: hv.items.map((item) => foldKnownAbsentSelfRef(item, fullKey, replacement))
336
+ };
337
+ }
338
+ if (hv.kind === "object") {
339
+ const newFields = /* @__PURE__ */ new Map();
340
+ for (const [k, val] of hv.fields) {
341
+ newFields.set(k, foldKnownAbsentSelfRef(val, fullKey, replacement));
342
+ }
343
+ return { kind: "object", fields: newFields };
344
+ }
345
+ return v;
346
+ }
371
347
  function foldOrSkipPrior(prior, fullKey, old) {
372
348
  if (!containsSelfRef(prior, fullKey)) return cloneResolverValue(prior);
373
349
  if (old === void 0) return foldOptionalSelfRefAbsent(prior, fullKey);
@@ -396,12 +372,6 @@ function foldOptionalSelfRefAbsent(v, fullKey) {
396
372
  }
397
373
  return { _kind: "concat-placeholder", nodes, line: v.line, col: v.col };
398
374
  }
399
- if (isAppend(v)) {
400
- const existing = foldOptionalSelfRefAbsent(v.existing, fullKey);
401
- const elem = foldOptionalSelfRefAbsent(v.elem, fullKey);
402
- if (existing === void 0 || elem === void 0) return void 0;
403
- return { _kind: "append-placeholder", existing, elem };
404
- }
405
375
  if (isResObj(v)) {
406
376
  const fields = /* @__PURE__ */ new Map();
407
377
  for (const [key, value] of v.fields) {
@@ -409,7 +379,7 @@ function foldOptionalSelfRefAbsent(v, fullKey) {
409
379
  if (folded === void 0) return void 0;
410
380
  fields.set(key, folded);
411
381
  }
412
- return { _kind: "res-obj", fields, priorValues: new Map(v.priorValues) };
382
+ return { _kind: "res-obj", fields, priorValues: new Map(v.priorValues), resetKeys: new Set(v.resetKeys) };
413
383
  }
414
384
  const hv = v;
415
385
  if (hv.kind === "array") {
@@ -458,14 +428,7 @@ function cloneResolverValue(v) {
458
428
  for (const [k, val] of v.fields) newFields.set(k, cloneResolverValue(val));
459
429
  const newPriors = /* @__PURE__ */ new Map();
460
430
  for (const [k, val] of v.priorValues) newPriors.set(k, cloneResolverValue(val));
461
- return { _kind: "res-obj", fields: newFields, priorValues: newPriors };
462
- }
463
- if (isAppend(v)) {
464
- return {
465
- _kind: "append-placeholder",
466
- existing: cloneResolverValue(v.existing),
467
- elem: cloneResolverValue(v.elem)
468
- };
431
+ return { _kind: "res-obj", fields: newFields, priorValues: newPriors, resetKeys: new Set(v.resetKeys) };
469
432
  }
470
433
  const hv = v;
471
434
  if (hv.kind === "array") {
@@ -500,14 +463,11 @@ function foldNestedSelfRefs(v, pathPrefix) {
500
463
  }
501
464
  newFields.set(k, finalVal);
502
465
  }
503
- return { _kind: "res-obj", fields: newFields, priorValues: new Map(v.priorValues) };
466
+ return { _kind: "res-obj", fields: newFields, priorValues: new Map(v.priorValues), resetKeys: new Set(v.resetKeys) };
504
467
  }
505
468
  function containsSubstByPath(v, target) {
506
469
  if (isSubst(v)) return !v.knownAbsent && segmentsTextEqual(v.segments, target);
507
470
  if (isConcat(v)) return v.nodes.some((n) => containsSubstByPath(n, target));
508
- if (isAppend(v)) {
509
- return containsSubstByPath(v.existing, target) || containsSubstByPath(v.elem, target);
510
- }
511
471
  if (isResObj(v)) {
512
472
  for (const f of v.fields.values()) {
513
473
  if (containsSubstByPath(f, target)) return true;
@@ -536,6 +496,53 @@ function segmentsTextEqual(a, b) {
536
496
  return true;
537
497
  }
538
498
 
499
+ // src/internal/resolver/types.ts
500
+ var separatorValues = /* @__PURE__ */ new WeakSet();
501
+ function isSubst(v) {
502
+ return v._kind === "subst-placeholder";
503
+ }
504
+ function isConcat(v) {
505
+ return v._kind === "concat-placeholder";
506
+ }
507
+ function isResObj(v) {
508
+ return v._kind === "res-obj";
509
+ }
510
+ function makeResObj() {
511
+ return { _kind: "res-obj", fields: /* @__PURE__ */ new Map(), priorValues: /* @__PURE__ */ new Map(), resetKeys: /* @__PURE__ */ new Set() };
512
+ }
513
+ function mergeUnresolved(receiver, fallback) {
514
+ const result = makeResObj();
515
+ for (const [k, v] of fallback.fields) {
516
+ result.fields.set(k, v);
517
+ }
518
+ for (const [k, v] of fallback.priorValues) {
519
+ result.priorValues.set(k, v);
520
+ }
521
+ for (const [k, rv] of receiver.fields) {
522
+ const existing = result.fields.get(k);
523
+ if (existing !== void 0) {
524
+ const recPrior = receiver.priorValues.get(k);
525
+ if (recPrior !== void 0 && !isResObj(recPrior)) {
526
+ result.fields.set(k, rv);
527
+ continue;
528
+ }
529
+ if (isResObj(rv) && isResObj(existing)) {
530
+ result.fields.set(k, mergeUnresolved(rv, existing));
531
+ continue;
532
+ }
533
+ const foldedPrior = foldOrSkipPrior(existing, k, void 0);
534
+ if (foldedPrior !== void 0) result.priorValues.set(k, foldedPrior);
535
+ }
536
+ result.fields.set(k, rv);
537
+ }
538
+ for (const [k, v] of receiver.priorValues) {
539
+ if (receiver.fields.has(k)) {
540
+ result.priorValues.set(k, v);
541
+ }
542
+ }
543
+ return result;
544
+ }
545
+
539
546
  // src/internal/resolver/utils.ts
540
547
  function segmentsToKey(segments) {
541
548
  return segments.map((s) => {
@@ -589,9 +596,19 @@ function deepMergeResObjInto(dst, src, pathPrefix = []) {
589
596
  deepMergeResObjInto(dstVal, srcVal, childPrefix);
590
597
  } else {
591
598
  if (dstVal !== void 0) {
592
- const priorExisting = dst.priorValues.get(k);
593
- const prior = foldOrSkipPrior(dstVal, fullKey, priorExisting);
594
- if (prior !== void 0) dst.priorValues.set(k, prior);
599
+ if (src.resetKeys.has(k)) {
600
+ dst.priorValues.delete(k);
601
+ } else {
602
+ const dstFolded = foldOrSkipPrior(dstVal, fullKey, dst.priorValues.get(k));
603
+ if (dstFolded !== void 0) {
604
+ const srcPrior = src.priorValues.get(k);
605
+ if (srcPrior !== void 0) {
606
+ dst.priorValues.set(k, foldKnownAbsentSelfRef(srcPrior, fullKey, dstFolded));
607
+ } else {
608
+ dst.priorValues.set(k, dstFolded);
609
+ }
610
+ }
611
+ }
595
612
  }
596
613
  dst.fields.set(k, srcVal);
597
614
  }
@@ -601,6 +618,7 @@ function deepMergeResObjInto(dst, src, pathPrefix = []) {
601
618
  dst.priorValues.set(k, srcPrior);
602
619
  }
603
620
  }
621
+ for (const k of src.resetKeys) dst.resetKeys.add(k);
604
622
  }
605
623
  function hoconValueToResObj(hv) {
606
624
  const obj = makeResObj();
@@ -1856,31 +1874,15 @@ var StructureBuilder = class _StructureBuilder {
1856
1874
  const childPrefix = [...pathPrefix, head];
1857
1875
  const fullKey = stringSegmentsToKey(childPrefix);
1858
1876
  if (field.append) {
1859
- const existing2 = obj.fields.get(head) ?? { kind: "array", items: [] };
1860
- const oldPrior = obj.priorValues.get(head);
1861
- const folded = foldOrSkipPrior(existing2, fullKey, oldPrior);
1862
- if (folded !== void 0) obj.priorValues.set(head, folded);
1863
- obj.fields.set(head, {
1864
- _kind: "append-placeholder",
1865
- // Use the folded existing (self-ref-free) for the Append. When the
1866
- // fold returned undefined (self-ref present but no old prior),
1867
- // fall back to the original existing — resolveSubst will then
1868
- // raise the "self-ref with no prior value" error at resolve time,
1869
- // matching the pre-fix behaviour for that edge case.
1870
- //
1871
- // Clone `folded` again so the Append.existing and priorValues[head]
1872
- // do not share the same Subst/Concat references — relativizeResObj
1873
- // walks BOTH `fields` and `priorValues` and mutates Subst.segments
1874
- // in place, so a shared reference would get its prefix applied
1875
- // twice (resulting in e.g. `${outer.outer.base}` instead of
1876
- // `${outer.base}`). Codex review on PR #131.
1877
- existing: folded !== void 0 ? cloneResolverValue(folded) : existing2,
1878
- elem: this.astToResolverValue(field.value, childPrefix)
1879
- });
1877
+ const synthetic = this.desugarAppend(field, childPrefix);
1878
+ this.applyField(obj, synthetic, pathPrefix);
1880
1879
  return;
1881
1880
  }
1882
1881
  const existing = obj.fields.get(head);
1883
1882
  const newVal = this.astToResolverValue(field.value, childPrefix);
1883
+ if (!containsSelfRef(newVal, fullKey)) {
1884
+ obj.resetKeys.add(head);
1885
+ }
1884
1886
  if (existing !== void 0) {
1885
1887
  const oldPrior = obj.priorValues.get(head);
1886
1888
  const priorInput = isResObj(existing) ? foldNestedSelfRefs(existing, childPrefix) : existing;
@@ -1922,19 +1924,15 @@ var StructureBuilder = class _StructureBuilder {
1922
1924
  const childPrefix = [...pathPrefix, head];
1923
1925
  const fullKey = stringSegmentsToKey(childPrefix);
1924
1926
  if (field.append) {
1925
- const existing2 = obj.fields.get(head) ?? { kind: "array", items: [] };
1926
- const oldPrior = obj.priorValues.get(head);
1927
- const folded = foldOrSkipPrior(existing2, fullKey, oldPrior);
1928
- if (folded !== void 0) obj.priorValues.set(head, folded);
1929
- obj.fields.set(head, {
1930
- _kind: "append-placeholder",
1931
- existing: folded !== void 0 ? cloneResolverValue(folded) : existing2,
1932
- elem: await this.astToResolverValueAsync(field.value, childPrefix)
1933
- });
1927
+ const synthetic = this.desugarAppend(field, childPrefix);
1928
+ await this.applyFieldAsync(obj, synthetic, pathPrefix);
1934
1929
  return;
1935
1930
  }
1936
1931
  const existing = obj.fields.get(head);
1937
1932
  const newVal = await this.astToResolverValueAsync(field.value, childPrefix);
1933
+ if (!containsSelfRef(newVal, fullKey)) {
1934
+ obj.resetKeys.add(head);
1935
+ }
1938
1936
  if (existing !== void 0) {
1939
1937
  const oldPrior = obj.priorValues.get(head);
1940
1938
  const priorInput = isResObj(existing) ? foldNestedSelfRefs(existing, childPrefix) : existing;
@@ -1947,6 +1945,21 @@ var StructureBuilder = class _StructureBuilder {
1947
1945
  }
1948
1946
  obj.fields.set(head, newVal);
1949
1947
  }
1948
+ /** Builds the `key = ${?fullkey} [value]` field that `key += value` desugars
1949
+ * to (S13b.2, HOCON.md L732). `childPrefix` is the field's fully-qualified
1950
+ * path (pathPrefix + key), so a nested `srv { items += x }` references
1951
+ * `${?srv.items}`; include relativization rewrites it under a mount prefix.
1952
+ * `field.key` is already single-segment here (multi-segment keys are split
1953
+ * into nested objects upstream). Returns a NEW AstField (append=false);
1954
+ * positions inherit from the original field so resolve-time errors (the
1955
+ * S13b.2 non-array check, now via the scalar+array concat) keep a location. */
1956
+ desugarAppend(field, childPrefix) {
1957
+ const segments = childPrefix.map((text) => ({ text, line: field.pos.line, col: field.pos.col }));
1958
+ const subst = { kind: "subst", segments, optional: true, listSuffix: false, pos: field.pos };
1959
+ const elemArray = { kind: "array", items: [field.value], pos: field.pos };
1960
+ const synthetic = { kind: "concat", nodes: [subst, elemArray], pos: field.pos };
1961
+ return { key: field.key, value: synthetic, append: false, pos: field.pos };
1962
+ }
1950
1963
  astToResolverValue(ast, pathPrefix) {
1951
1964
  switch (ast.kind) {
1952
1965
  case "scalar": {
@@ -2011,11 +2024,6 @@ var StructureBuilder = class _StructureBuilder {
2011
2024
  }
2012
2025
  return;
2013
2026
  }
2014
- if (isAppend(val)) {
2015
- this.relativizeSubstPaths(val.existing, prefixSegments);
2016
- this.relativizeSubstPaths(val.elem, prefixSegments);
2017
- return;
2018
- }
2019
2027
  if (isResObj(val)) {
2020
2028
  this.relativizeResObj(val, prefixSegments);
2021
2029
  return;
@@ -2164,7 +2172,6 @@ var SubstitutionResolver = class {
2164
2172
  this.resolvingConcats.delete(v);
2165
2173
  }
2166
2174
  }
2167
- if (isAppend(v)) return this.resolveAppend(v, scope);
2168
2175
  if (isResObj(v)) return this.resolveResObj(v);
2169
2176
  const hv = v;
2170
2177
  if (hv.kind === "array") {
@@ -2449,38 +2456,6 @@ var SubstitutionResolver = class {
2449
2456
  }
2450
2457
  return folded;
2451
2458
  }
2452
- resolveAppend(a, scope) {
2453
- const existing = this.resolveVal(a.existing, scope) ?? { kind: "array", items: [] };
2454
- if (this.opts.allowUnresolved && (isSubst(existing) || isConcat(existing) || isAppend(existing))) {
2455
- return a;
2456
- }
2457
- const elem = this.resolveVal(a.elem, scope);
2458
- let priorItems;
2459
- if (existing.kind === "array") {
2460
- priorItems = existing.items;
2461
- } else if (existing.kind === "object") {
2462
- const converted = numericObjectToArray(existing);
2463
- if (converted === null) {
2464
- throw new ResolveError(
2465
- `'+=' on non-array value: prior value is object with non-numeric keys (spec L732)`,
2466
- "",
2467
- 0,
2468
- 0
2469
- );
2470
- }
2471
- priorItems = converted;
2472
- } else {
2473
- throw new ResolveError(
2474
- `'+=' on non-array value: prior value is ${existing.kind} (spec L732)`,
2475
- "",
2476
- 0,
2477
- 0
2478
- );
2479
- }
2480
- const items = [...priorItems];
2481
- if (elem !== void 0) items.push(elem);
2482
- return { kind: "array", items };
2483
- }
2484
2459
  };
2485
2460
 
2486
2461
  // src/internal/resolver/resolver.ts
@@ -2498,7 +2473,7 @@ function containsPlaceholders(tree) {
2498
2473
  }
2499
2474
  function valContainsPlaceholders(v) {
2500
2475
  const rv = v;
2501
- if (isSubst(rv) || isConcat(rv) || isAppend(rv)) return true;
2476
+ if (isSubst(rv) || isConcat(rv)) return true;
2502
2477
  if (isResObj(rv)) return containsPlaceholders(rv);
2503
2478
  const hv = v;
2504
2479
  if (hv?.kind === "array") {
@@ -2509,7 +2484,7 @@ function valContainsPlaceholders(v) {
2509
2484
  function buildPartialHoconFromResObj(tree) {
2510
2485
  const fields = /* @__PURE__ */ new Map();
2511
2486
  for (const [k, v] of tree.fields) {
2512
- if (!isSubst(v) && !isConcat(v) && !isAppend(v)) {
2487
+ if (!isSubst(v) && !isConcat(v)) {
2513
2488
  if (isResObj(v)) {
2514
2489
  fields.set(k, buildPartialHoconFromResObj(v));
2515
2490
  } else {
@@ -2898,7 +2873,7 @@ function hoconToJs(v) {
2898
2873
  function resObjToKeyShape(tree) {
2899
2874
  const fields = /* @__PURE__ */ new Map();
2900
2875
  for (const [k, v] of tree.fields) {
2901
- if (isSubst(v) || isConcat(v) || isAppend(v)) {
2876
+ if (isSubst(v) || isConcat(v)) {
2902
2877
  fields.set(k, { kind: "scalar", raw: "", valueType: "null" });
2903
2878
  } else if (isResObj(v)) {
2904
2879
  fields.set(k, resObjToKeyShape(v));
@@ -2912,7 +2887,7 @@ function stripPlaceholderFields(v) {
2912
2887
  const fields = /* @__PURE__ */ new Map();
2913
2888
  let hadPlaceholders = false;
2914
2889
  for (const [k, val] of v.fields) {
2915
- if (val._kind === "subst-placeholder" || val._kind === "concat-placeholder" || val._kind === "append-placeholder") {
2890
+ if (val._kind === "subst-placeholder" || val._kind === "concat-placeholder") {
2916
2891
  hadPlaceholders = true;
2917
2892
  } else if (val.kind === "object") {
2918
2893
  const inner = stripPlaceholderFields(val);
@@ -2946,9 +2921,8 @@ function renderHoconAsJSON(v) {
2946
2921
  case "boolean":
2947
2922
  return v.raw;
2948
2923
  case "number": {
2949
- const n = Number(v.raw);
2950
- if (Number.isFinite(n)) return v.raw;
2951
- return JSON.stringify(v.raw);
2924
+ const norm = normalizeLeadingZeroNumber(v.raw);
2925
+ return JSON_NUMBER_RE.test(norm) ? norm : JSON.stringify(v.raw);
2952
2926
  }
2953
2927
  case "string":
2954
2928
  return JSON.stringify(v.raw);
@@ -2970,6 +2944,10 @@ function renderHoconAsJSON(v) {
2970
2944
  }
2971
2945
  throw new Error(`renderHoconAsJSON: unsupported value kind (unresolved placeholder?)`);
2972
2946
  }
2947
+ var JSON_NUMBER_RE = /^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$/;
2948
+ function normalizeLeadingZeroNumber(raw) {
2949
+ return raw.replace(/^([+-]?)0+(\d)/, "$1$2");
2950
+ }
2973
2951
 
2974
2952
  // src/parse.ts
2975
2953
  function getEnv(opts) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@o3co/ts.hocon",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "description": "Full Lightbend HOCON spec-compliant parser and config library for TypeScript",
5
5
  "keywords": [
6
6
  "hocon",