@o3co/ts.hocon 1.10.0 → 1.12.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
@@ -1,3 +1,10 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
1
8
  // src/coerce.ts
2
9
  var DECIMAL_NUMBER_RE = /^-?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$/;
3
10
  function isHoconWs(cp) {
@@ -643,6 +650,7 @@ function isFileNotFoundError(e) {
643
650
  // src/internal/resolver/include-loader.ts
644
651
  import { createRequire } from "module";
645
652
  import * as nodePath from "path";
653
+ import { pathToFileURL } from "url";
646
654
 
647
655
  // src/internal/lexer/lexer.ts
648
656
  function isHoconWhitespace(ch) {
@@ -1485,25 +1493,69 @@ function parseTokens(tokens) {
1485
1493
  return new Parser(tokens).parse();
1486
1494
  }
1487
1495
 
1496
+ // src/internal/strip-bom.ts
1497
+ function stripBom(input) {
1498
+ return input.charCodeAt(0) === 65279 ? input.slice(1) : input;
1499
+ }
1500
+
1501
+ // src/internal/depth.ts
1502
+ var MAX_PATH_SEGMENTS = 64;
1503
+ function tooDeep(segments) {
1504
+ return segments > MAX_PATH_SEGMENTS;
1505
+ }
1506
+ function guardStackDepth(fn, wrap) {
1507
+ try {
1508
+ return fn();
1509
+ } catch (e) {
1510
+ if (!isStackOverflow(e)) throw e;
1511
+ throw wrap(STACK_MESSAGE);
1512
+ }
1513
+ }
1514
+ var STACK_MESSAGE = "input is nested too deeply for this engine's stack, or contains a cycle (flatten the document, or break the cycle)";
1515
+ function isStackOverflow(e) {
1516
+ if (!(e instanceof RangeError)) return false;
1517
+ const m = e.message.toLowerCase();
1518
+ return m.includes("call stack") || // V8: "Maximum call stack size exceeded"
1519
+ m.includes("recursion") || // SpiderMonkey: "too much recursion"
1520
+ m.includes("stack overflow");
1521
+ }
1522
+
1488
1523
  // src/internal/properties/properties.ts
1489
1524
  function parseProperties(input) {
1490
1525
  const pairs = [];
1491
- for (const { text, line } of logicalLines(input)) {
1526
+ for (const { text, line } of logicalLines(stripBom(input))) {
1492
1527
  const [rawKey, rawValue] = splitKeyValue(text);
1493
1528
  const key = unescapeProps(rawKey, line);
1494
1529
  if (key === "") continue;
1495
- pairs.push([key, unescapeProps(rawValue, line)]);
1530
+ const segments = key.split(".");
1531
+ if (tooDeep(segments.length)) {
1532
+ throw new ParseError(
1533
+ `key maps to a path ${segments.length} segments deep, over the limit of ${MAX_PATH_SEGMENTS}`,
1534
+ line,
1535
+ 1
1536
+ );
1537
+ }
1538
+ pairs.push([segments, unescapeProps(rawValue, line)]);
1496
1539
  }
1497
1540
  return nestPairs(pairs);
1498
1541
  }
1499
1542
  function nestPairs(pairs) {
1500
1543
  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) {
1503
- setNested(root, key.split("."), value);
1544
+ const sorted = [...pairs].sort(([a], [b]) => compareSegments(a, b));
1545
+ for (const [segments, value] of sorted) {
1546
+ setNested(root, segments, value);
1504
1547
  }
1505
1548
  return root;
1506
1549
  }
1550
+ function compareSegments(a, b) {
1551
+ const n = Math.min(a.length, b.length);
1552
+ for (let i = 0; i < n; i++) {
1553
+ const x = a[i];
1554
+ const y = b[i];
1555
+ if (x !== y) return x < y ? -1 : 1;
1556
+ }
1557
+ return a.length - b.length;
1558
+ }
1507
1559
  function logicalLines(input) {
1508
1560
  const natural = input.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
1509
1561
  const out = [];
@@ -1590,19 +1642,16 @@ function unescapeProps(s, line) {
1590
1642
  }
1591
1643
  return out;
1592
1644
  }
1593
- var DANGEROUS_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
1594
1645
  function setNested(obj, segments, value) {
1595
1646
  let current = obj;
1596
1647
  for (let i = 0; i < segments.length - 1; i++) {
1597
1648
  const seg = segments[i];
1598
- if (seg === void 0 || DANGEROUS_KEYS.has(seg)) return;
1599
1649
  if (!(seg in current) || typeof current[seg] !== "object" || current[seg] === null) {
1600
1650
  current[seg] = /* @__PURE__ */ Object.create(null);
1601
1651
  }
1602
1652
  current = current[seg];
1603
1653
  }
1604
1654
  const last = segments[segments.length - 1];
1605
- if (last === void 0 || DANGEROUS_KEYS.has(last)) return;
1606
1655
  if (typeof current[last] === "object" && current[last] !== null) return;
1607
1656
  current[last] = value;
1608
1657
  }
@@ -1623,7 +1672,22 @@ function recordToHoconValue(obj) {
1623
1672
  }
1624
1673
 
1625
1674
  // src/internal/resolver/include-loader.ts
1626
- var _require = createRequire(import.meta.url);
1675
+ var _require;
1676
+ function getRequire() {
1677
+ if (_require === void 0) {
1678
+ _require = pickRequire(
1679
+ typeof __require === "function" ? __require : void 0,
1680
+ import.meta.url
1681
+ );
1682
+ }
1683
+ return _require;
1684
+ }
1685
+ function pickRequire(candidate, metaUrl) {
1686
+ if (typeof candidate?.resolve === "function") {
1687
+ return candidate;
1688
+ }
1689
+ return createRequire(metaUrl ?? pathToFileURL(nodePath.join(process.cwd(), "noop.js")));
1690
+ }
1627
1691
  function isPnpPath(p) {
1628
1692
  return p.includes(".yarn/cache") || p.includes(".pnp.cjs") || p.includes(".pnp.loader");
1629
1693
  }
@@ -1679,7 +1743,7 @@ function defaultPackageResolver(identifier, file, includingFile, baseDir, resolv
1679
1743
  const from = resolveFrom ? Array.isArray(resolveFrom) ? resolveFrom : [resolveFrom] : baseDir ? [baseDir] : includingFile ? [nodePath.dirname(includingFile)] : [process.cwd()];
1680
1744
  let resolved;
1681
1745
  try {
1682
- resolved = _require.resolve(`${identifier}/${file}`, { paths: from });
1746
+ resolved = getRequire().resolve(`${identifier}/${file}`, { paths: from });
1683
1747
  } catch {
1684
1748
  throw new PackageLookupError(
1685
1749
  `include package("${identifier}", "${file}"): module not found (starting from: ${from.join(", ")})`,
@@ -2729,10 +2793,10 @@ var Config = class _Config {
2729
2793
  }
2730
2794
  if (v.kind === "object") {
2731
2795
  const converted = numericObjectToArray(v);
2732
- if (converted !== null) return converted.map(hoconToJs);
2796
+ if (converted !== null) return converted.map((item) => hoconToJs(item));
2733
2797
  }
2734
2798
  if (v.kind !== "array") throw new ConfigError(`expected array at ${path2}`, path2);
2735
- return v.items.map(hoconToJs);
2799
+ return v.items.map((item) => hoconToJs(item));
2736
2800
  }
2737
2801
  has(path2) {
2738
2802
  return this.lookupNode(path2) !== void 0;
@@ -2860,8 +2924,52 @@ var Config = class _Config {
2860
2924
  originDescription: this._originDescription
2861
2925
  });
2862
2926
  }
2863
- toObject() {
2864
- return hoconToJs(this.root);
2927
+ /**
2928
+ * The whole config as plain JS data: objects, arrays, strings, numbers,
2929
+ * booleans and `null`.
2930
+ *
2931
+ * Keys are reproduced exactly as the config carries them, `__proto__`,
2932
+ * `constructor` and `prototype` included: each is defined as an **own data
2933
+ * property**, so the returned object's prototype is always
2934
+ * `Object.prototype`, and this call writes nothing to the global one.
2935
+ *
2936
+ * ### Passing the result to other code
2937
+ *
2938
+ * A config file — especially a `.properties` file or an env namespace you
2939
+ * merged in — can therefore hand you an own `__proto__` key, and what happens
2940
+ * next depends entirely on how the consumer copies it:
2941
+ *
2942
+ * ```ts
2943
+ * const data = cfg.toObject()
2944
+ * { ...data } // safe — own properties are copied as data
2945
+ * structuredClone(data) // safe
2946
+ * Object.assign({}, data) // NOT safe — [[Set]] on the {} target hits
2947
+ * // Object.prototype's __proto__ setter: the key
2948
+ * // vanishes and config data becomes the result's
2949
+ * // prototype
2950
+ * deepMerge({}, data) // NOT safe — a naive recursive merge reads
2951
+ * // target['__proto__'], gets Object.prototype,
2952
+ * // and writes to it: global pollution
2953
+ * for (const k in data) … // prefer Object.entries / Object.keys
2954
+ * ```
2955
+ *
2956
+ * Those two hazards come from the **destination** object's prototype, so no
2957
+ * option here can disarm them — copy with spread or `structuredClone` instead.
2958
+ *
2959
+ * What `{ nullPrototype: true }` does give you is a result that inherits
2960
+ * nothing: every object comes back with a `null` prototype, so reading
2961
+ * `x.__proto__`, `x.constructor` or `x.toString` yields config data or
2962
+ * `undefined` rather than something from `Object.prototype`, and code that
2963
+ * merges *into* this object cannot climb out of it. Keys are all still
2964
+ * present. The trade-off is the usual one for prototype-less objects: not
2965
+ * `instanceof Object`, and no `hasOwnProperty` of their own.
2966
+ *
2967
+ * Numbers follow the JS number model, so an integer wider than 2^53 rounds
2968
+ * here even when the config holds it exactly — read those with
2969
+ * {@link Config.getString} instead.
2970
+ */
2971
+ toObject(opts) {
2972
+ return hoconToJs(this.root, opts?.nullPrototype === true);
2865
2973
  }
2866
2974
  /**
2867
2975
  * @internal Test-only: renders this resolved Config's value tree as canonical
@@ -2973,16 +3081,25 @@ function scalarToJs(raw, valueType) {
2973
3081
  return raw;
2974
3082
  }
2975
3083
  }
2976
- function hoconToJs(v) {
3084
+ function hoconToJs(v, nullPrototype = false) {
2977
3085
  switch (v.kind) {
2978
3086
  case "scalar":
2979
3087
  return scalarToJs(v.raw, v.valueType);
2980
3088
  case "array":
2981
- return v.items.map(hoconToJs);
3089
+ return v.items.map((item) => hoconToJs(item, nullPrototype));
2982
3090
  case "object": {
2983
- const obj = /* @__PURE__ */ Object.create(null);
2984
- for (const [k, val] of v.fields) obj[k] = hoconToJs(val);
2985
- return Object.assign({}, obj);
3091
+ const entries = Array.from(v.fields, ([k, val]) => [k, hoconToJs(val, nullPrototype)]);
3092
+ if (nullPrototype) {
3093
+ const obj = /* @__PURE__ */ Object.create(null);
3094
+ for (const [k, val] of entries) Object.defineProperty(obj, k, {
3095
+ value: val,
3096
+ writable: true,
3097
+ enumerable: true,
3098
+ configurable: true
3099
+ });
3100
+ return obj;
3101
+ }
3102
+ return Object.fromEntries(entries);
2986
3103
  }
2987
3104
  }
2988
3105
  }
@@ -3096,10 +3213,30 @@ function buildResolveContext(input, opts) {
3096
3213
  return { ast, resolveOpts };
3097
3214
  }
3098
3215
  function parse(input, opts = {}) {
3216
+ return parseStringWithOptions(input, opts);
3217
+ }
3218
+ function parseStringWithOptions(input, opts = {}) {
3219
+ return guardStackDepth(
3220
+ () => parseStringInner(input, opts),
3221
+ (msg) => new ParseError(msg, 1, 1, opts.originDescription)
3222
+ );
3223
+ }
3224
+ function parseStringInner(input, opts) {
3225
+ const resolveSubstitutions = opts.resolveSubstitutions ?? true;
3099
3226
  const { ast, resolveOpts } = buildResolveContext(input, opts);
3100
- const value = resolve2(ast, resolveOpts);
3101
- if (value.kind !== "object") throw new Error("resolved value is not an object");
3102
- return new Config(value, { resolved: true, originDescription: opts.originDescription });
3227
+ if (resolveSubstitutions) {
3228
+ const value = resolve2(ast, resolveOpts);
3229
+ if (value.kind !== "object") throw new Error("resolved value is not an object");
3230
+ return new Config(value, { resolved: true, originDescription: opts.originDescription });
3231
+ }
3232
+ const tree = buildTree(ast, resolveOpts);
3233
+ const hasPlaceholders = containsPlaceholders(tree);
3234
+ return Config._fromUnresolvedResObj(tree, {
3235
+ parseBaseDir: opts.baseDir,
3236
+ originDescription: opts.originDescription,
3237
+ resolved: !hasPlaceholders,
3238
+ resolveOpts
3239
+ });
3103
3240
  }
3104
3241
  function parseFile(filePath, opts = {}) {
3105
3242
  if (typeof process === "undefined" && !opts.readFileSync) {
@@ -3173,7 +3310,7 @@ function coerceValue(value, schema) {
3173
3310
  const obj = value;
3174
3311
  const result = { ...obj };
3175
3312
  for (const key of Object.keys(shape)) {
3176
- if (key in obj) {
3313
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
3177
3314
  result[key] = coerceValue(obj[key], shape[key]);
3178
3315
  }
3179
3316
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@o3co/ts.hocon",
3
- "version": "1.10.0",
3
+ "version": "1.12.0",
4
4
  "description": "Full Lightbend HOCON spec-compliant parser and config library for TypeScript",
5
5
  "keywords": [
6
6
  "hocon",
@@ -13,39 +13,74 @@
13
13
  "type": "module",
14
14
  "exports": {
15
15
  ".": {
16
- "types": "./dist/index.d.ts",
17
- "import": "./dist/index.js",
18
- "require": "./dist/index.cjs"
16
+ "import": {
17
+ "types": "./dist/index.d.ts",
18
+ "default": "./dist/index.js"
19
+ },
20
+ "require": {
21
+ "types": "./dist/index.d.cts",
22
+ "default": "./dist/index.cjs"
23
+ }
19
24
  },
20
25
  "./zod": {
21
- "types": "./dist/zod.d.ts",
22
- "import": "./dist/zod.js",
23
- "require": "./dist/zod.cjs"
26
+ "import": {
27
+ "types": "./dist/zod.d.ts",
28
+ "default": "./dist/zod.js"
29
+ },
30
+ "require": {
31
+ "types": "./dist/zod.d.cts",
32
+ "default": "./dist/zod.cjs"
33
+ }
24
34
  },
25
35
  "./adapters/properties": {
26
- "types": "./dist/adapters/properties.d.ts",
27
- "import": "./dist/adapters/properties.js",
28
- "require": "./dist/adapters/properties.cjs"
36
+ "import": {
37
+ "types": "./dist/adapters/properties.d.ts",
38
+ "default": "./dist/adapters/properties.js"
39
+ },
40
+ "require": {
41
+ "types": "./dist/adapters/properties.d.cts",
42
+ "default": "./dist/adapters/properties.cjs"
43
+ }
29
44
  },
30
45
  "./adapters/env": {
31
- "types": "./dist/adapters/env.d.ts",
32
- "import": "./dist/adapters/env.js",
33
- "require": "./dist/adapters/env.cjs"
46
+ "import": {
47
+ "types": "./dist/adapters/env.d.ts",
48
+ "default": "./dist/adapters/env.js"
49
+ },
50
+ "require": {
51
+ "types": "./dist/adapters/env.d.cts",
52
+ "default": "./dist/adapters/env.cjs"
53
+ }
34
54
  },
35
55
  "./adapters/jsonc": {
36
- "types": "./dist/adapters/jsonc.d.ts",
37
- "import": "./dist/adapters/jsonc.js",
38
- "require": "./dist/adapters/jsonc.cjs"
56
+ "import": {
57
+ "types": "./dist/adapters/jsonc.d.ts",
58
+ "default": "./dist/adapters/jsonc.js"
59
+ },
60
+ "require": {
61
+ "types": "./dist/adapters/jsonc.d.cts",
62
+ "default": "./dist/adapters/jsonc.cjs"
63
+ }
39
64
  },
40
65
  "./adapters/toml": {
41
- "types": "./dist/adapters/toml.d.ts",
42
- "import": "./dist/adapters/toml.js",
43
- "require": "./dist/adapters/toml.cjs"
66
+ "import": {
67
+ "types": "./dist/adapters/toml.d.ts",
68
+ "default": "./dist/adapters/toml.js"
69
+ },
70
+ "require": {
71
+ "types": "./dist/adapters/toml.d.cts",
72
+ "default": "./dist/adapters/toml.cjs"
73
+ }
44
74
  },
45
75
  "./adapters/yaml": {
46
- "types": "./dist/adapters/yaml.d.ts",
47
- "import": "./dist/adapters/yaml.js",
48
- "require": "./dist/adapters/yaml.cjs"
76
+ "import": {
77
+ "types": "./dist/adapters/yaml.d.ts",
78
+ "default": "./dist/adapters/yaml.js"
79
+ },
80
+ "require": {
81
+ "types": "./dist/adapters/yaml.d.cts",
82
+ "default": "./dist/adapters/yaml.cjs"
83
+ }
49
84
  }
50
85
  },
51
86
  "main": "./dist/index.cjs",
@@ -95,6 +130,7 @@
95
130
  "bugs": "https://github.com/o3co/ts.hocon/issues",
96
131
  "scripts": {
97
132
  "build": "tsup",
133
+ "smoke": "tsup && node tools/smoke-entrypoints.mjs",
98
134
  "test": "vitest run",
99
135
  "test:watch": "vitest",
100
136
  "typecheck": "tsc --noEmit",