@o3co/ts.hocon 1.8.0 → 1.10.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
@@ -119,7 +119,7 @@ function parseDuration(value, outputUnit = "ms") {
119
119
  if (i === 0) return NaN;
120
120
  const num = Number(trimmed.slice(0, i));
121
121
  if (Number.isNaN(num)) return NaN;
122
- const unit = trimHoconWs(trimmed.slice(i)).toLowerCase();
122
+ const unit = trimHoconWs(trimmed.slice(i));
123
123
  const divisor = OUTPUT_DURATION_UNITS[outputUnit];
124
124
  if (divisor === void 0) return NaN;
125
125
  if (unit === "") {
@@ -683,20 +683,6 @@ function isFileNotFoundError(e) {
683
683
  var import_node_module = require("module");
684
684
  var nodePath = __toESM(require("path"), 1);
685
685
 
686
- // src/internal/parser/empty-check.ts
687
- function hasContentTokens(tokens) {
688
- return tokens.some((t) => t.kind !== "eof" && t.kind !== "newline");
689
- }
690
- function assertNonEmptyDocument(tokens, sourceDescriptor) {
691
- if (!hasContentTokens(tokens)) {
692
- throw new ParseError(
693
- `empty file is not a valid HOCON document (HOCON.md L130): ${sourceDescriptor}`,
694
- 1,
695
- 1
696
- );
697
- }
698
- }
699
-
700
686
  // src/internal/lexer/lexer.ts
701
687
  function isHoconWhitespace(ch) {
702
688
  const cp = ch.codePointAt(0) ?? -1;
@@ -1097,12 +1083,12 @@ function isDecimalDigit(ch) {
1097
1083
  }
1098
1084
  function isUnquotedStart(ch) {
1099
1085
  if (ch === "" || isHoconWhitespace(ch)) return false;
1100
- if ('{}[],:=+#"$?!@*&^\\'.includes(ch)) return false;
1086
+ if ('{}[],:=+#"$?!@*&^`\\'.includes(ch)) return false;
1101
1087
  return true;
1102
1088
  }
1103
1089
  function isUnquotedContinue(ch, nextFn) {
1104
1090
  if (ch === "" || isHoconWhitespace(ch)) return false;
1105
- if ('{}[],:=#"$?!@*&^\\'.includes(ch)) return false;
1091
+ if ('{}[],:=#"$?!@*&^`\\'.includes(ch)) return false;
1106
1092
  if (ch === "+" && nextFn() === "=") return false;
1107
1093
  if (ch === "/" && nextFn() === "/") return false;
1108
1094
  return true;
@@ -1142,6 +1128,16 @@ var Parser = class {
1142
1128
  }
1143
1129
  return { kind: "object", fields: allFields, pos: first.pos };
1144
1130
  }
1131
+ if (t.kind === "lbracket") {
1132
+ this.advance();
1133
+ const arr = this.parseArray();
1134
+ this.skip("newline");
1135
+ const remaining = this.peek();
1136
+ if (remaining.kind !== "eof") {
1137
+ throw new ParseError(`Unexpected token '${remaining.value}' after root array`, remaining.line, remaining.col);
1138
+ }
1139
+ return { ...arr, pos: { line: t.line, col: t.col } };
1140
+ }
1145
1141
  return this.parseObject(false);
1146
1142
  }
1147
1143
  peek(offset = 0) {
@@ -1241,6 +1237,17 @@ var Parser = class {
1241
1237
  this.advance();
1242
1238
  const raw = t.value.startsWith(".") && segments.length > 0 && !t.precedingSpace ? t.value.slice(1) : t.value;
1243
1239
  const parts = raw.split(".");
1240
+ const leadingDotIsSeparator = spaceConcat || postDotPrefix !== "";
1241
+ for (let i = 0; i < parts.length; i++) {
1242
+ if (parts[i] !== "") continue;
1243
+ if (i === parts.length - 1) continue;
1244
+ if (i === 0 && leadingDotIsSeparator) continue;
1245
+ throw new ParseError(
1246
+ `path has ${i === 0 ? "a leading period '.'" : "two adjacent periods '.'"} \u2014 empty key segment not allowed, use quoted "" (HOCON.md path rules)`,
1247
+ t.line,
1248
+ t.col
1249
+ );
1250
+ }
1244
1251
  const filtered = parts.filter((s) => s.length > 0);
1245
1252
  if (spaceConcat) {
1246
1253
  segments[segments.length - 1] = `${segments[segments.length - 1]}${t.precedingWhitespace}`;
@@ -1519,29 +1526,108 @@ function parseTokens(tokens) {
1519
1526
 
1520
1527
  // src/internal/properties/properties.ts
1521
1528
  function parseProperties(input) {
1522
- const root = /* @__PURE__ */ Object.create(null);
1523
1529
  const pairs = [];
1524
- for (const line of input.split("\n")) {
1525
- const trimmed = line.trim();
1526
- if (trimmed === "" || trimmed.startsWith("#") || trimmed.startsWith("!")) continue;
1527
- const sepIdx = findSeparator(trimmed);
1528
- if (sepIdx === -1) continue;
1529
- const key = trimmed.slice(0, sepIdx).trim();
1530
- const value = trimmed.slice(sepIdx + 1).trim();
1530
+ for (const { text, line } of logicalLines(input)) {
1531
+ const [rawKey, rawValue] = splitKeyValue(text);
1532
+ const key = unescapeProps(rawKey, line);
1531
1533
  if (key === "") continue;
1532
- pairs.push([key, value]);
1534
+ pairs.push([key, unescapeProps(rawValue, line)]);
1533
1535
  }
1534
- pairs.sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
1535
- for (const [key, value] of pairs) {
1536
+ return nestPairs(pairs);
1537
+ }
1538
+ function nestPairs(pairs) {
1539
+ const root = /* @__PURE__ */ Object.create(null);
1540
+ const sorted = [...pairs].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
1541
+ for (const [key, value] of sorted) {
1536
1542
  setNested(root, key.split("."), value);
1537
1543
  }
1538
1544
  return root;
1539
1545
  }
1540
- function findSeparator(line) {
1541
- for (let i = 0; i < line.length; i++) {
1542
- if (line[i] === "=" || line[i] === ":") return i;
1546
+ function logicalLines(input) {
1547
+ const natural = input.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
1548
+ const out = [];
1549
+ for (let i = 0; i < natural.length; i++) {
1550
+ let text = trimLeadingSpace(natural[i] ?? "");
1551
+ if (text === "" || text.startsWith("#") || text.startsWith("!")) continue;
1552
+ const start = i + 1;
1553
+ while (endsWithContinuation(text)) {
1554
+ text = text.slice(0, -1);
1555
+ if (i + 1 >= natural.length) break;
1556
+ i++;
1557
+ text += trimLeadingSpace(natural[i] ?? "");
1558
+ }
1559
+ out.push({ text, line: start });
1543
1560
  }
1544
- return -1;
1561
+ return out;
1562
+ }
1563
+ function trimLeadingSpace(s) {
1564
+ return s.replace(/^[ \t\f]+/, "");
1565
+ }
1566
+ function endsWithContinuation(line) {
1567
+ let n = 0;
1568
+ for (let i = line.length - 1; i >= 0 && line[i] === "\\"; i--) n++;
1569
+ return n % 2 === 1;
1570
+ }
1571
+ function splitKeyValue(line) {
1572
+ let key = "";
1573
+ let i = 0;
1574
+ for (; i < line.length; i++) {
1575
+ const c = line[i];
1576
+ if (c === "\\" && i + 1 < line.length) {
1577
+ key += c + line[i + 1];
1578
+ i++;
1579
+ continue;
1580
+ }
1581
+ if (c === "=" || c === ":" || isPropsSpace(c)) break;
1582
+ key += c;
1583
+ }
1584
+ while (i < line.length && isPropsSpace(line[i])) i++;
1585
+ if (i < line.length && (line[i] === "=" || line[i] === ":")) {
1586
+ i++;
1587
+ while (i < line.length && isPropsSpace(line[i])) i++;
1588
+ }
1589
+ return [key, line.slice(i)];
1590
+ }
1591
+ function isPropsSpace(c) {
1592
+ return c === " " || c === " " || c === "\f";
1593
+ }
1594
+ function unescapeProps(s, line) {
1595
+ let out = "";
1596
+ for (let i = 0; i < s.length; i++) {
1597
+ if (s[i] !== "\\") {
1598
+ out += s[i];
1599
+ continue;
1600
+ }
1601
+ i++;
1602
+ if (i >= s.length) break;
1603
+ const e = s[i];
1604
+ switch (e) {
1605
+ case "t":
1606
+ out += " ";
1607
+ break;
1608
+ case "n":
1609
+ out += "\n";
1610
+ break;
1611
+ case "r":
1612
+ out += "\r";
1613
+ break;
1614
+ case "f":
1615
+ out += "\f";
1616
+ break;
1617
+ case "u": {
1618
+ const hex = s.slice(i + 1, i + 5);
1619
+ if (!/^[0-9a-fA-F]{4}$/.test(hex)) {
1620
+ throw new ParseError(`malformed \\u escape in .properties: \\u${hex}`, line, i + 1);
1621
+ }
1622
+ out += String.fromCharCode(parseInt(hex, 16));
1623
+ i += 4;
1624
+ break;
1625
+ }
1626
+ default:
1627
+ out += e;
1628
+ }
1629
+ }
1630
+ return out;
1545
1631
  }
1546
1632
  var DANGEROUS_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
1547
1633
  function setNested(obj, segments, value) {
@@ -1581,6 +1667,16 @@ var _require = (0, import_node_module.createRequire)(import_meta.url);
1581
1667
  function isPnpPath(p) {
1582
1668
  return p.includes(".yarn/cache") || p.includes(".pnp.cjs") || p.includes(".pnp.loader");
1583
1669
  }
1670
+ function assertObjectRootedInclude(ast, sourcePath) {
1671
+ if (ast.kind === "array") {
1672
+ throw new ResolveError(
1673
+ `included file has array at file root \u2014 an included file must contain an object, not an array (HOCON.md L993-994): ${sourcePath}`,
1674
+ sourcePath,
1675
+ ast.pos.line,
1676
+ ast.pos.col
1677
+ );
1678
+ }
1679
+ }
1584
1680
  function validatePackageFile(file, identifier) {
1585
1681
  if (file.length === 0) {
1586
1682
  throw new ParseError(`include package("${identifier}", ...): file argument must be non-empty`, 0, 0);
@@ -1760,10 +1856,9 @@ var IncludeLoader = class {
1760
1856
  const resolver = packageResolver ?? ((id, f, includingFile, bDir) => defaultPackageResolver(id, f, includingFile, bDir, resolveFrom));
1761
1857
  const resolvedPath = resolver(identifier, file, void 0, baseDir);
1762
1858
  const content = readFileSync2(resolvedPath);
1763
- if (content.length === 0) return makeResObj();
1764
1859
  const tokens = tokenize(content);
1765
- assertNonEmptyDocument(tokens, resolvedPath);
1766
1860
  const ast = parseTokens(tokens);
1861
+ assertObjectRootedInclude(ast, resolvedPath);
1767
1862
  return this.onBuildResObj(ast, {
1768
1863
  ...this.opts,
1769
1864
  baseDir: nodePath.dirname(resolvedPath),
@@ -1793,10 +1888,9 @@ var IncludeLoader = class {
1793
1888
  const resolvedPath = resolver(identifier, file, void 0, baseDir);
1794
1889
  const read = readFile ? async (p) => readFile(p) : async (p) => readFileSync2(p);
1795
1890
  const content = await read(resolvedPath);
1796
- if (content.length === 0) return makeResObj();
1797
1891
  const tokens = tokenize(content);
1798
- assertNonEmptyDocument(tokens, resolvedPath);
1799
1892
  const ast = parseTokens(tokens);
1893
+ assertObjectRootedInclude(ast, resolvedPath);
1800
1894
  return this.onBuildResObjAsync(ast, {
1801
1895
  ...this.opts,
1802
1896
  baseDir: nodePath.dirname(resolvedPath),
@@ -1819,10 +1913,8 @@ var IncludeLoader = class {
1819
1913
  return hoconValueToResObj(propertiesToHoconValue(content));
1820
1914
  }
1821
1915
  const tokens = tokenize(content);
1822
- if (!hasContentTokens(tokens)) {
1823
- return makeResObj();
1824
- }
1825
1916
  const ast = parseTokens(tokens);
1917
+ assertObjectRootedInclude(ast, candidate);
1826
1918
  return this.onBuildResObj(ast, {
1827
1919
  ...this.opts,
1828
1920
  baseDir: nodePath.dirname(candidate),
@@ -1846,10 +1938,8 @@ var IncludeLoader = class {
1846
1938
  return hoconValueToResObj(propertiesToHoconValue(content));
1847
1939
  }
1848
1940
  const tokens = tokenize(content);
1849
- if (!hasContentTokens(tokens)) {
1850
- return makeResObj();
1851
- }
1852
1941
  const ast = parseTokens(tokens);
1942
+ assertObjectRootedInclude(ast, candidate);
1853
1943
  return this.onBuildResObjAsync(ast, {
1854
1944
  ...this.opts,
1855
1945
  baseDir: nodePath.dirname(candidate),
@@ -3026,8 +3116,14 @@ function defaultReadFileSync(filePath) {
3026
3116
  }
3027
3117
  function buildResolveContext(input, opts) {
3028
3118
  const tokens = tokenize(input);
3029
- assertNonEmptyDocument(tokens, "input");
3030
3119
  const ast = parseTokens(tokens);
3120
+ if (ast.kind === "array") {
3121
+ const origin = opts.originDescription ?? "input";
3122
+ throw new ConfigError(
3123
+ `${origin}: ${ast.pos.line}:${ast.pos.col}: document has type array rather than object at file root (HOCON.md L989-991); the Config API requires an object root`,
3124
+ ""
3125
+ );
3126
+ }
3031
3127
  const resolveOpts = {
3032
3128
  env: getEnv(opts),
3033
3129
  baseDir: opts.baseDir,
package/dist/zod.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { ZodType } from 'zod';
2
- import { C as Config, a as ParseOptions } from './config-D27s3_aG.cjs';
2
+ import { C as Config, a as ParseOptions } from './config-PdhVgeLS.cjs';
3
3
 
4
4
  declare function validate<T>(config: Config, schema: ZodType<T>): T;
5
5
  declare function getValidated<T>(config: Config, path: string, schema: ZodType<T>): T;
package/dist/zod.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { ZodType } from 'zod';
2
- import { C as Config, a as ParseOptions } from './config-D27s3_aG.js';
2
+ import { C as Config, a as ParseOptions } from './config-PdhVgeLS.js';
3
3
 
4
4
  declare function validate<T>(config: Config, schema: ZodType<T>): T;
5
5
  declare function getValidated<T>(config: Config, path: string, schema: ZodType<T>): T;
package/dist/zod.js CHANGED
@@ -80,7 +80,7 @@ function parseDuration(value, outputUnit = "ms") {
80
80
  if (i === 0) return NaN;
81
81
  const num = Number(trimmed.slice(0, i));
82
82
  if (Number.isNaN(num)) return NaN;
83
- const unit = trimHoconWs(trimmed.slice(i)).toLowerCase();
83
+ const unit = trimHoconWs(trimmed.slice(i));
84
84
  const divisor = OUTPUT_DURATION_UNITS[outputUnit];
85
85
  if (divisor === void 0) return NaN;
86
86
  if (unit === "") {
@@ -644,20 +644,6 @@ function isFileNotFoundError(e) {
644
644
  import { createRequire } from "module";
645
645
  import * as nodePath from "path";
646
646
 
647
- // src/internal/parser/empty-check.ts
648
- function hasContentTokens(tokens) {
649
- return tokens.some((t) => t.kind !== "eof" && t.kind !== "newline");
650
- }
651
- function assertNonEmptyDocument(tokens, sourceDescriptor) {
652
- if (!hasContentTokens(tokens)) {
653
- throw new ParseError(
654
- `empty file is not a valid HOCON document (HOCON.md L130): ${sourceDescriptor}`,
655
- 1,
656
- 1
657
- );
658
- }
659
- }
660
-
661
647
  // src/internal/lexer/lexer.ts
662
648
  function isHoconWhitespace(ch) {
663
649
  const cp = ch.codePointAt(0) ?? -1;
@@ -1058,12 +1044,12 @@ function isDecimalDigit(ch) {
1058
1044
  }
1059
1045
  function isUnquotedStart(ch) {
1060
1046
  if (ch === "" || isHoconWhitespace(ch)) return false;
1061
- if ('{}[],:=+#"$?!@*&^\\'.includes(ch)) return false;
1047
+ if ('{}[],:=+#"$?!@*&^`\\'.includes(ch)) return false;
1062
1048
  return true;
1063
1049
  }
1064
1050
  function isUnquotedContinue(ch, nextFn) {
1065
1051
  if (ch === "" || isHoconWhitespace(ch)) return false;
1066
- if ('{}[],:=#"$?!@*&^\\'.includes(ch)) return false;
1052
+ if ('{}[],:=#"$?!@*&^`\\'.includes(ch)) return false;
1067
1053
  if (ch === "+" && nextFn() === "=") return false;
1068
1054
  if (ch === "/" && nextFn() === "/") return false;
1069
1055
  return true;
@@ -1103,6 +1089,16 @@ var Parser = class {
1103
1089
  }
1104
1090
  return { kind: "object", fields: allFields, pos: first.pos };
1105
1091
  }
1092
+ if (t.kind === "lbracket") {
1093
+ this.advance();
1094
+ const arr = this.parseArray();
1095
+ this.skip("newline");
1096
+ const remaining = this.peek();
1097
+ if (remaining.kind !== "eof") {
1098
+ throw new ParseError(`Unexpected token '${remaining.value}' after root array`, remaining.line, remaining.col);
1099
+ }
1100
+ return { ...arr, pos: { line: t.line, col: t.col } };
1101
+ }
1106
1102
  return this.parseObject(false);
1107
1103
  }
1108
1104
  peek(offset = 0) {
@@ -1202,6 +1198,17 @@ var Parser = class {
1202
1198
  this.advance();
1203
1199
  const raw = t.value.startsWith(".") && segments.length > 0 && !t.precedingSpace ? t.value.slice(1) : t.value;
1204
1200
  const parts = raw.split(".");
1201
+ const leadingDotIsSeparator = spaceConcat || postDotPrefix !== "";
1202
+ for (let i = 0; i < parts.length; i++) {
1203
+ if (parts[i] !== "") continue;
1204
+ if (i === parts.length - 1) continue;
1205
+ if (i === 0 && leadingDotIsSeparator) continue;
1206
+ throw new ParseError(
1207
+ `path has ${i === 0 ? "a leading period '.'" : "two adjacent periods '.'"} \u2014 empty key segment not allowed, use quoted "" (HOCON.md path rules)`,
1208
+ t.line,
1209
+ t.col
1210
+ );
1211
+ }
1205
1212
  const filtered = parts.filter((s) => s.length > 0);
1206
1213
  if (spaceConcat) {
1207
1214
  segments[segments.length - 1] = `${segments[segments.length - 1]}${t.precedingWhitespace}`;
@@ -1480,29 +1487,108 @@ function parseTokens(tokens) {
1480
1487
 
1481
1488
  // src/internal/properties/properties.ts
1482
1489
  function parseProperties(input) {
1483
- const root = /* @__PURE__ */ Object.create(null);
1484
1490
  const pairs = [];
1485
- for (const line of input.split("\n")) {
1486
- const trimmed = line.trim();
1487
- if (trimmed === "" || trimmed.startsWith("#") || trimmed.startsWith("!")) continue;
1488
- const sepIdx = findSeparator(trimmed);
1489
- if (sepIdx === -1) continue;
1490
- const key = trimmed.slice(0, sepIdx).trim();
1491
- const value = trimmed.slice(sepIdx + 1).trim();
1491
+ for (const { text, line } of logicalLines(input)) {
1492
+ const [rawKey, rawValue] = splitKeyValue(text);
1493
+ const key = unescapeProps(rawKey, line);
1492
1494
  if (key === "") continue;
1493
- pairs.push([key, value]);
1495
+ pairs.push([key, unescapeProps(rawValue, line)]);
1494
1496
  }
1495
- pairs.sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
1496
- for (const [key, value] of pairs) {
1497
+ return nestPairs(pairs);
1498
+ }
1499
+ function nestPairs(pairs) {
1500
+ const root = /* @__PURE__ */ Object.create(null);
1501
+ const sorted = [...pairs].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
1502
+ for (const [key, value] of sorted) {
1497
1503
  setNested(root, key.split("."), value);
1498
1504
  }
1499
1505
  return root;
1500
1506
  }
1501
- function findSeparator(line) {
1502
- for (let i = 0; i < line.length; i++) {
1503
- if (line[i] === "=" || line[i] === ":") return i;
1507
+ function logicalLines(input) {
1508
+ const natural = input.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
1509
+ const out = [];
1510
+ for (let i = 0; i < natural.length; i++) {
1511
+ let text = trimLeadingSpace(natural[i] ?? "");
1512
+ if (text === "" || text.startsWith("#") || text.startsWith("!")) continue;
1513
+ const start = i + 1;
1514
+ while (endsWithContinuation(text)) {
1515
+ text = text.slice(0, -1);
1516
+ if (i + 1 >= natural.length) break;
1517
+ i++;
1518
+ text += trimLeadingSpace(natural[i] ?? "");
1519
+ }
1520
+ out.push({ text, line: start });
1504
1521
  }
1505
- return -1;
1522
+ return out;
1523
+ }
1524
+ function trimLeadingSpace(s) {
1525
+ return s.replace(/^[ \t\f]+/, "");
1526
+ }
1527
+ function endsWithContinuation(line) {
1528
+ let n = 0;
1529
+ for (let i = line.length - 1; i >= 0 && line[i] === "\\"; i--) n++;
1530
+ return n % 2 === 1;
1531
+ }
1532
+ function splitKeyValue(line) {
1533
+ let key = "";
1534
+ let i = 0;
1535
+ for (; i < line.length; i++) {
1536
+ const c = line[i];
1537
+ if (c === "\\" && i + 1 < line.length) {
1538
+ key += c + line[i + 1];
1539
+ i++;
1540
+ continue;
1541
+ }
1542
+ if (c === "=" || c === ":" || isPropsSpace(c)) break;
1543
+ key += c;
1544
+ }
1545
+ while (i < line.length && isPropsSpace(line[i])) i++;
1546
+ if (i < line.length && (line[i] === "=" || line[i] === ":")) {
1547
+ i++;
1548
+ while (i < line.length && isPropsSpace(line[i])) i++;
1549
+ }
1550
+ return [key, line.slice(i)];
1551
+ }
1552
+ function isPropsSpace(c) {
1553
+ return c === " " || c === " " || c === "\f";
1554
+ }
1555
+ function unescapeProps(s, line) {
1556
+ let out = "";
1557
+ for (let i = 0; i < s.length; i++) {
1558
+ if (s[i] !== "\\") {
1559
+ out += s[i];
1560
+ continue;
1561
+ }
1562
+ i++;
1563
+ if (i >= s.length) break;
1564
+ const e = s[i];
1565
+ switch (e) {
1566
+ case "t":
1567
+ out += " ";
1568
+ break;
1569
+ case "n":
1570
+ out += "\n";
1571
+ break;
1572
+ case "r":
1573
+ out += "\r";
1574
+ break;
1575
+ case "f":
1576
+ out += "\f";
1577
+ break;
1578
+ case "u": {
1579
+ const hex = s.slice(i + 1, i + 5);
1580
+ if (!/^[0-9a-fA-F]{4}$/.test(hex)) {
1581
+ throw new ParseError(`malformed \\u escape in .properties: \\u${hex}`, line, i + 1);
1582
+ }
1583
+ out += String.fromCharCode(parseInt(hex, 16));
1584
+ i += 4;
1585
+ break;
1586
+ }
1587
+ default:
1588
+ out += e;
1589
+ }
1590
+ }
1591
+ return out;
1506
1592
  }
1507
1593
  var DANGEROUS_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
1508
1594
  function setNested(obj, segments, value) {
@@ -1541,6 +1627,16 @@ var _require = createRequire(import.meta.url);
1541
1627
  function isPnpPath(p) {
1542
1628
  return p.includes(".yarn/cache") || p.includes(".pnp.cjs") || p.includes(".pnp.loader");
1543
1629
  }
1630
+ function assertObjectRootedInclude(ast, sourcePath) {
1631
+ if (ast.kind === "array") {
1632
+ throw new ResolveError(
1633
+ `included file has array at file root \u2014 an included file must contain an object, not an array (HOCON.md L993-994): ${sourcePath}`,
1634
+ sourcePath,
1635
+ ast.pos.line,
1636
+ ast.pos.col
1637
+ );
1638
+ }
1639
+ }
1544
1640
  function validatePackageFile(file, identifier) {
1545
1641
  if (file.length === 0) {
1546
1642
  throw new ParseError(`include package("${identifier}", ...): file argument must be non-empty`, 0, 0);
@@ -1720,10 +1816,9 @@ var IncludeLoader = class {
1720
1816
  const resolver = packageResolver ?? ((id, f, includingFile, bDir) => defaultPackageResolver(id, f, includingFile, bDir, resolveFrom));
1721
1817
  const resolvedPath = resolver(identifier, file, void 0, baseDir);
1722
1818
  const content = readFileSync2(resolvedPath);
1723
- if (content.length === 0) return makeResObj();
1724
1819
  const tokens = tokenize(content);
1725
- assertNonEmptyDocument(tokens, resolvedPath);
1726
1820
  const ast = parseTokens(tokens);
1821
+ assertObjectRootedInclude(ast, resolvedPath);
1727
1822
  return this.onBuildResObj(ast, {
1728
1823
  ...this.opts,
1729
1824
  baseDir: nodePath.dirname(resolvedPath),
@@ -1753,10 +1848,9 @@ var IncludeLoader = class {
1753
1848
  const resolvedPath = resolver(identifier, file, void 0, baseDir);
1754
1849
  const read = readFile ? async (p) => readFile(p) : async (p) => readFileSync2(p);
1755
1850
  const content = await read(resolvedPath);
1756
- if (content.length === 0) return makeResObj();
1757
1851
  const tokens = tokenize(content);
1758
- assertNonEmptyDocument(tokens, resolvedPath);
1759
1852
  const ast = parseTokens(tokens);
1853
+ assertObjectRootedInclude(ast, resolvedPath);
1760
1854
  return this.onBuildResObjAsync(ast, {
1761
1855
  ...this.opts,
1762
1856
  baseDir: nodePath.dirname(resolvedPath),
@@ -1779,10 +1873,8 @@ var IncludeLoader = class {
1779
1873
  return hoconValueToResObj(propertiesToHoconValue(content));
1780
1874
  }
1781
1875
  const tokens = tokenize(content);
1782
- if (!hasContentTokens(tokens)) {
1783
- return makeResObj();
1784
- }
1785
1876
  const ast = parseTokens(tokens);
1877
+ assertObjectRootedInclude(ast, candidate);
1786
1878
  return this.onBuildResObj(ast, {
1787
1879
  ...this.opts,
1788
1880
  baseDir: nodePath.dirname(candidate),
@@ -1806,10 +1898,8 @@ var IncludeLoader = class {
1806
1898
  return hoconValueToResObj(propertiesToHoconValue(content));
1807
1899
  }
1808
1900
  const tokens = tokenize(content);
1809
- if (!hasContentTokens(tokens)) {
1810
- return makeResObj();
1811
- }
1812
1901
  const ast = parseTokens(tokens);
1902
+ assertObjectRootedInclude(ast, candidate);
1813
1903
  return this.onBuildResObjAsync(ast, {
1814
1904
  ...this.opts,
1815
1905
  baseDir: nodePath.dirname(candidate),
@@ -2986,8 +3076,14 @@ function defaultReadFileSync(filePath) {
2986
3076
  }
2987
3077
  function buildResolveContext(input, opts) {
2988
3078
  const tokens = tokenize(input);
2989
- assertNonEmptyDocument(tokens, "input");
2990
3079
  const ast = parseTokens(tokens);
3080
+ if (ast.kind === "array") {
3081
+ const origin = opts.originDescription ?? "input";
3082
+ throw new ConfigError(
3083
+ `${origin}: ${ast.pos.line}:${ast.pos.col}: document has type array rather than object at file root (HOCON.md L989-991); the Config API requires an object root`,
3084
+ ""
3085
+ );
3086
+ }
2991
3087
  const resolveOpts = {
2992
3088
  env: getEnv(opts),
2993
3089
  baseDir: opts.baseDir,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@o3co/ts.hocon",
3
- "version": "1.8.0",
3
+ "version": "1.10.0",
4
4
  "description": "Full Lightbend HOCON spec-compliant parser and config library for TypeScript",
5
5
  "keywords": [
6
6
  "hocon",
@@ -21,6 +21,31 @@
21
21
  "types": "./dist/zod.d.ts",
22
22
  "import": "./dist/zod.js",
23
23
  "require": "./dist/zod.cjs"
24
+ },
25
+ "./adapters/properties": {
26
+ "types": "./dist/adapters/properties.d.ts",
27
+ "import": "./dist/adapters/properties.js",
28
+ "require": "./dist/adapters/properties.cjs"
29
+ },
30
+ "./adapters/env": {
31
+ "types": "./dist/adapters/env.d.ts",
32
+ "import": "./dist/adapters/env.js",
33
+ "require": "./dist/adapters/env.cjs"
34
+ },
35
+ "./adapters/jsonc": {
36
+ "types": "./dist/adapters/jsonc.d.ts",
37
+ "import": "./dist/adapters/jsonc.js",
38
+ "require": "./dist/adapters/jsonc.cjs"
39
+ },
40
+ "./adapters/toml": {
41
+ "types": "./dist/adapters/toml.d.ts",
42
+ "import": "./dist/adapters/toml.js",
43
+ "require": "./dist/adapters/toml.cjs"
44
+ },
45
+ "./adapters/yaml": {
46
+ "types": "./dist/adapters/yaml.d.ts",
47
+ "import": "./dist/adapters/yaml.js",
48
+ "require": "./dist/adapters/yaml.cjs"
24
49
  }
25
50
  },
26
51
  "main": "./dist/index.cjs",
@@ -30,27 +55,37 @@
30
55
  "dist"
31
56
  ],
32
57
  "peerDependencies": {
33
- "zod": ">=4.0.0"
58
+ "zod": ">=4.0.0",
59
+ "smol-toml": ">=1.0.0",
60
+ "yaml": ">=2.0.0"
34
61
  },
35
62
  "peerDependenciesMeta": {
36
63
  "zod": {
37
64
  "optional": true
65
+ },
66
+ "smol-toml": {
67
+ "optional": true
68
+ },
69
+ "yaml": {
70
+ "optional": true
38
71
  }
39
72
  },
40
73
  "devDependencies": {
41
74
  "@types/js-yaml": "^4.0.9",
42
- "@types/node": "^25.5.0",
43
- "@vitest/coverage-v8": "4.1.2",
44
- "js-yaml": "^4.1.1",
75
+ "@types/node": "^25.9.5",
76
+ "@vitest/coverage-v8": "4.1.10",
77
+ "js-yaml": "^4.3.0",
78
+ "smol-toml": "^1.7.0",
45
79
  "tsup": "^8.5.1",
46
- "typedoc": "^0.28.18",
80
+ "typedoc": "^0.28.20",
47
81
  "typescript": "^5.9.3",
48
- "vitest": "4.1.2",
49
- "zod": "^4.3.6"
82
+ "vitest": "4.1.10",
83
+ "yaml": "^2.9.0",
84
+ "zod": "^4.4.3"
50
85
  },
51
86
  "repository": {
52
87
  "type": "git",
53
- "url": "https://github.com/o3co/ts.hocon"
88
+ "url": "git+https://github.com/o3co/ts.hocon.git"
54
89
  },
55
90
  "license": "Apache-2.0",
56
91
  "engines": {
@@ -1,6 +1,3 @@
1
- type DurationUnit = 'ns' | 'us' | 'ms' | 's' | 'm' | 'h' | 'd';
2
- type ByteUnit = 'B' | 'KB' | 'KiB' | 'MB' | 'MiB' | 'GB' | 'GiB' | 'TB' | 'TiB';
3
-
4
1
  type ScalarValueType = 'string' | 'number' | 'boolean' | 'null';
5
2
  type HoconValue = {
6
3
  kind: 'object';
@@ -226,6 +223,9 @@ declare function parseAsync(input: string, opts?: ParseOptions): Promise<Config>
226
223
  declare function parseFile(filePath: string, opts?: ParseOptions): Config;
227
224
  declare function parseFileAsync(filePath: string, opts?: ParseOptions): Promise<Config>;
228
225
 
226
+ type DurationUnit = 'ns' | 'us' | 'ms' | 's' | 'm' | 'h' | 'd';
227
+ type ByteUnit = 'B' | 'KB' | 'KiB' | 'MB' | 'MiB' | 'GB' | 'GiB' | 'TB' | 'TiB';
228
+
229
229
  declare class Config {
230
230
  private readonly root;
231
231
  /** @internal resolved flag: true when no substitution placeholders remain. */