@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.
@@ -522,26 +522,74 @@ function deepMergeHoconValues(base, overlay) {
522
522
  // src/internal/resolver/include-loader.ts
523
523
  import { createRequire } from "module";
524
524
  import * as nodePath from "path";
525
+ import { pathToFileURL } from "url";
526
+
527
+ // src/internal/strip-bom.ts
528
+ function stripBom(input) {
529
+ return input.charCodeAt(0) === 65279 ? input.slice(1) : input;
530
+ }
531
+
532
+ // src/internal/depth.ts
533
+ var MAX_PATH_SEGMENTS = 64;
534
+ function tooDeep(segments) {
535
+ return segments > MAX_PATH_SEGMENTS;
536
+ }
537
+ function guardStackDepth(fn, wrap) {
538
+ try {
539
+ return fn();
540
+ } catch (e) {
541
+ if (!isStackOverflow(e)) throw e;
542
+ throw wrap(STACK_MESSAGE);
543
+ }
544
+ }
545
+ var STACK_MESSAGE = "input is nested too deeply for this engine's stack, or contains a cycle (flatten the document, or break the cycle)";
546
+ function isStackOverflow(e) {
547
+ if (!(e instanceof RangeError)) return false;
548
+ const m = e.message.toLowerCase();
549
+ return m.includes("call stack") || // V8: "Maximum call stack size exceeded"
550
+ m.includes("recursion") || // SpiderMonkey: "too much recursion"
551
+ m.includes("stack overflow");
552
+ }
553
+ function depthError(message) {
554
+ return new ConfigError(message, "");
555
+ }
525
556
 
526
557
  // src/internal/properties/properties.ts
527
558
  function parseProperties(input) {
528
559
  const pairs = [];
529
- for (const { text, line } of logicalLines(input)) {
560
+ for (const { text, line } of logicalLines(stripBom(input))) {
530
561
  const [rawKey, rawValue] = splitKeyValue(text);
531
562
  const key = unescapeProps(rawKey, line);
532
563
  if (key === "") continue;
533
- pairs.push([key, unescapeProps(rawValue, line)]);
564
+ const segments = key.split(".");
565
+ if (tooDeep(segments.length)) {
566
+ throw new ParseError(
567
+ `key maps to a path ${segments.length} segments deep, over the limit of ${MAX_PATH_SEGMENTS}`,
568
+ line,
569
+ 1
570
+ );
571
+ }
572
+ pairs.push([segments, unescapeProps(rawValue, line)]);
534
573
  }
535
574
  return nestPairs(pairs);
536
575
  }
537
576
  function nestPairs(pairs) {
538
577
  const root = /* @__PURE__ */ Object.create(null);
539
- const sorted = [...pairs].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
540
- for (const [key, value] of sorted) {
541
- setNested(root, key.split("."), value);
578
+ const sorted = [...pairs].sort(([a], [b]) => compareSegments(a, b));
579
+ for (const [segments, value] of sorted) {
580
+ setNested(root, segments, value);
542
581
  }
543
582
  return root;
544
583
  }
584
+ function compareSegments(a, b) {
585
+ const n = Math.min(a.length, b.length);
586
+ for (let i = 0; i < n; i++) {
587
+ const x = a[i];
588
+ const y = b[i];
589
+ if (x !== y) return x < y ? -1 : 1;
590
+ }
591
+ return a.length - b.length;
592
+ }
545
593
  function logicalLines(input) {
546
594
  const natural = input.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
547
595
  const out = [];
@@ -628,26 +676,20 @@ function unescapeProps(s, line) {
628
676
  }
629
677
  return out;
630
678
  }
631
- var DANGEROUS_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
632
679
  function setNested(obj, segments, value) {
633
680
  let current = obj;
634
681
  for (let i = 0; i < segments.length - 1; i++) {
635
682
  const seg = segments[i];
636
- if (seg === void 0 || DANGEROUS_KEYS.has(seg)) return;
637
683
  if (!(seg in current) || typeof current[seg] !== "object" || current[seg] === null) {
638
684
  current[seg] = /* @__PURE__ */ Object.create(null);
639
685
  }
640
686
  current = current[seg];
641
687
  }
642
688
  const last = segments[segments.length - 1];
643
- if (last === void 0 || DANGEROUS_KEYS.has(last)) return;
644
689
  if (typeof current[last] === "object" && current[last] !== null) return;
645
690
  current[last] = value;
646
691
  }
647
692
 
648
- // src/internal/resolver/include-loader.ts
649
- var _require = createRequire(import.meta.url);
650
-
651
693
  // src/value/numeric-array.ts
652
694
  function numericObjectToArray(value) {
653
695
  if (value.kind !== "object") return null;
@@ -1235,10 +1277,10 @@ var Config = class _Config {
1235
1277
  }
1236
1278
  if (v.kind === "object") {
1237
1279
  const converted = numericObjectToArray(v);
1238
- if (converted !== null) return converted.map(hoconToJs);
1280
+ if (converted !== null) return converted.map((item) => hoconToJs(item));
1239
1281
  }
1240
1282
  if (v.kind !== "array") throw new ConfigError(`expected array at ${path}`, path);
1241
- return v.items.map(hoconToJs);
1283
+ return v.items.map((item) => hoconToJs(item));
1242
1284
  }
1243
1285
  has(path) {
1244
1286
  return this.lookupNode(path) !== void 0;
@@ -1366,8 +1408,52 @@ var Config = class _Config {
1366
1408
  originDescription: this._originDescription
1367
1409
  });
1368
1410
  }
1369
- toObject() {
1370
- return hoconToJs(this.root);
1411
+ /**
1412
+ * The whole config as plain JS data: objects, arrays, strings, numbers,
1413
+ * booleans and `null`.
1414
+ *
1415
+ * Keys are reproduced exactly as the config carries them, `__proto__`,
1416
+ * `constructor` and `prototype` included: each is defined as an **own data
1417
+ * property**, so the returned object's prototype is always
1418
+ * `Object.prototype`, and this call writes nothing to the global one.
1419
+ *
1420
+ * ### Passing the result to other code
1421
+ *
1422
+ * A config file — especially a `.properties` file or an env namespace you
1423
+ * merged in — can therefore hand you an own `__proto__` key, and what happens
1424
+ * next depends entirely on how the consumer copies it:
1425
+ *
1426
+ * ```ts
1427
+ * const data = cfg.toObject()
1428
+ * { ...data } // safe — own properties are copied as data
1429
+ * structuredClone(data) // safe
1430
+ * Object.assign({}, data) // NOT safe — [[Set]] on the {} target hits
1431
+ * // Object.prototype's __proto__ setter: the key
1432
+ * // vanishes and config data becomes the result's
1433
+ * // prototype
1434
+ * deepMerge({}, data) // NOT safe — a naive recursive merge reads
1435
+ * // target['__proto__'], gets Object.prototype,
1436
+ * // and writes to it: global pollution
1437
+ * for (const k in data) … // prefer Object.entries / Object.keys
1438
+ * ```
1439
+ *
1440
+ * Those two hazards come from the **destination** object's prototype, so no
1441
+ * option here can disarm them — copy with spread or `structuredClone` instead.
1442
+ *
1443
+ * What `{ nullPrototype: true }` does give you is a result that inherits
1444
+ * nothing: every object comes back with a `null` prototype, so reading
1445
+ * `x.__proto__`, `x.constructor` or `x.toString` yields config data or
1446
+ * `undefined` rather than something from `Object.prototype`, and code that
1447
+ * merges *into* this object cannot climb out of it. Keys are all still
1448
+ * present. The trade-off is the usual one for prototype-less objects: not
1449
+ * `instanceof Object`, and no `hasOwnProperty` of their own.
1450
+ *
1451
+ * Numbers follow the JS number model, so an integer wider than 2^53 rounds
1452
+ * here even when the config holds it exactly — read those with
1453
+ * {@link Config.getString} instead.
1454
+ */
1455
+ toObject(opts) {
1456
+ return hoconToJs(this.root, opts?.nullPrototype === true);
1371
1457
  }
1372
1458
  /**
1373
1459
  * @internal Test-only: renders this resolved Config's value tree as canonical
@@ -1479,16 +1565,25 @@ function scalarToJs(raw, valueType) {
1479
1565
  return raw;
1480
1566
  }
1481
1567
  }
1482
- function hoconToJs(v) {
1568
+ function hoconToJs(v, nullPrototype = false) {
1483
1569
  switch (v.kind) {
1484
1570
  case "scalar":
1485
1571
  return scalarToJs(v.raw, v.valueType);
1486
1572
  case "array":
1487
- return v.items.map(hoconToJs);
1573
+ return v.items.map((item) => hoconToJs(item, nullPrototype));
1488
1574
  case "object": {
1489
- const obj = /* @__PURE__ */ Object.create(null);
1490
- for (const [k, val] of v.fields) obj[k] = hoconToJs(val);
1491
- return Object.assign({}, obj);
1575
+ const entries = Array.from(v.fields, ([k, val]) => [k, hoconToJs(val, nullPrototype)]);
1576
+ if (nullPrototype) {
1577
+ const obj = /* @__PURE__ */ Object.create(null);
1578
+ for (const [k, val] of entries) Object.defineProperty(obj, k, {
1579
+ value: val,
1580
+ writable: true,
1581
+ enumerable: true,
1582
+ configurable: true
1583
+ });
1584
+ return obj;
1585
+ }
1586
+ return Object.fromEntries(entries);
1492
1587
  }
1493
1588
  }
1494
1589
  }
@@ -1573,10 +1668,15 @@ function normalizeLeadingZeroNumber(raw) {
1573
1668
 
1574
1669
  // src/value-factory.ts
1575
1670
  function fromMap(values, originDescription) {
1576
- const fields = coerceObject(values, "");
1671
+ const fields = guardStackDepth(
1672
+ () => coerceObject(values, ""),
1673
+ (msg) => depthError(`fromMap: ${msg}`)
1674
+ );
1577
1675
  const root = { kind: "object", fields };
1578
1676
  return Config._fromResolvedValue(root, { originDescription });
1579
1677
  }
1678
+ var INT64_MAX = 9223372036854775807n;
1679
+ var INT64_MIN = -9223372036854775808n;
1580
1680
  function coerceObject(obj, atPath) {
1581
1681
  const fields = /* @__PURE__ */ new Map();
1582
1682
  for (const [k, v] of Object.entries(obj)) {
@@ -1605,14 +1705,14 @@ function coerceValue(v, atPath) {
1605
1705
  atPath
1606
1706
  );
1607
1707
  }
1608
- return { kind: "scalar", raw: String(v), valueType: "number" };
1708
+ return { kind: "scalar", raw: Object.is(v, -0) ? "-0" : String(v), valueType: "number" };
1609
1709
  }
1610
1710
  case "boolean":
1611
1711
  return { kind: "scalar", raw: v ? "true" : "false", valueType: "boolean" };
1612
1712
  case "bigint": {
1613
- if (v > BigInt(Number.MAX_SAFE_INTEGER) || v < BigInt(Number.MIN_SAFE_INTEGER)) {
1713
+ if (v > INT64_MAX || v < INT64_MIN) {
1614
1714
  throw new ConfigError(
1615
- `fromMap: bigint ${v} exceeds Number.MAX_SAFE_INTEGER (not representable as HOCON number) at path "${atPath}"`,
1715
+ `fromMap: integer ${v} is out of int64 range (HOCON integers are int64; spec F0.5) at path "${atPath}"`,
1616
1716
  atPath
1617
1717
  );
1618
1718
  }
@@ -550,8 +550,33 @@ function deepMergeHoconValues(base, overlay) {
550
550
  // src/internal/resolver/include-loader.ts
551
551
  var import_node_module = require("module");
552
552
  var nodePath = __toESM(require("path"), 1);
553
- var import_meta = {};
554
- var _require = (0, import_node_module.createRequire)(import_meta.url);
553
+ var import_node_url = require("url");
554
+
555
+ // src/internal/strip-bom.ts
556
+ function stripBom(input) {
557
+ return input.charCodeAt(0) === 65279 ? input.slice(1) : input;
558
+ }
559
+
560
+ // src/internal/depth.ts
561
+ function guardStackDepth(fn, wrap) {
562
+ try {
563
+ return fn();
564
+ } catch (e) {
565
+ if (!isStackOverflow(e)) throw e;
566
+ throw wrap(STACK_MESSAGE);
567
+ }
568
+ }
569
+ var STACK_MESSAGE = "input is nested too deeply for this engine's stack, or contains a cycle (flatten the document, or break the cycle)";
570
+ function isStackOverflow(e) {
571
+ if (!(e instanceof RangeError)) return false;
572
+ const m = e.message.toLowerCase();
573
+ return m.includes("call stack") || // V8: "Maximum call stack size exceeded"
574
+ m.includes("recursion") || // SpiderMonkey: "too much recursion"
575
+ m.includes("stack overflow");
576
+ }
577
+ function depthError(message) {
578
+ return new ConfigError(message, "");
579
+ }
555
580
 
556
581
  // src/value/numeric-array.ts
557
582
  function numericObjectToArray(value) {
@@ -1140,10 +1165,10 @@ var Config = class _Config {
1140
1165
  }
1141
1166
  if (v.kind === "object") {
1142
1167
  const converted = numericObjectToArray(v);
1143
- if (converted !== null) return converted.map(hoconToJs);
1168
+ if (converted !== null) return converted.map((item) => hoconToJs(item));
1144
1169
  }
1145
1170
  if (v.kind !== "array") throw new ConfigError(`expected array at ${path}`, path);
1146
- return v.items.map(hoconToJs);
1171
+ return v.items.map((item) => hoconToJs(item));
1147
1172
  }
1148
1173
  has(path) {
1149
1174
  return this.lookupNode(path) !== void 0;
@@ -1271,8 +1296,52 @@ var Config = class _Config {
1271
1296
  originDescription: this._originDescription
1272
1297
  });
1273
1298
  }
1274
- toObject() {
1275
- return hoconToJs(this.root);
1299
+ /**
1300
+ * The whole config as plain JS data: objects, arrays, strings, numbers,
1301
+ * booleans and `null`.
1302
+ *
1303
+ * Keys are reproduced exactly as the config carries them, `__proto__`,
1304
+ * `constructor` and `prototype` included: each is defined as an **own data
1305
+ * property**, so the returned object's prototype is always
1306
+ * `Object.prototype`, and this call writes nothing to the global one.
1307
+ *
1308
+ * ### Passing the result to other code
1309
+ *
1310
+ * A config file — especially a `.properties` file or an env namespace you
1311
+ * merged in — can therefore hand you an own `__proto__` key, and what happens
1312
+ * next depends entirely on how the consumer copies it:
1313
+ *
1314
+ * ```ts
1315
+ * const data = cfg.toObject()
1316
+ * { ...data } // safe — own properties are copied as data
1317
+ * structuredClone(data) // safe
1318
+ * Object.assign({}, data) // NOT safe — [[Set]] on the {} target hits
1319
+ * // Object.prototype's __proto__ setter: the key
1320
+ * // vanishes and config data becomes the result's
1321
+ * // prototype
1322
+ * deepMerge({}, data) // NOT safe — a naive recursive merge reads
1323
+ * // target['__proto__'], gets Object.prototype,
1324
+ * // and writes to it: global pollution
1325
+ * for (const k in data) … // prefer Object.entries / Object.keys
1326
+ * ```
1327
+ *
1328
+ * Those two hazards come from the **destination** object's prototype, so no
1329
+ * option here can disarm them — copy with spread or `structuredClone` instead.
1330
+ *
1331
+ * What `{ nullPrototype: true }` does give you is a result that inherits
1332
+ * nothing: every object comes back with a `null` prototype, so reading
1333
+ * `x.__proto__`, `x.constructor` or `x.toString` yields config data or
1334
+ * `undefined` rather than something from `Object.prototype`, and code that
1335
+ * merges *into* this object cannot climb out of it. Keys are all still
1336
+ * present. The trade-off is the usual one for prototype-less objects: not
1337
+ * `instanceof Object`, and no `hasOwnProperty` of their own.
1338
+ *
1339
+ * Numbers follow the JS number model, so an integer wider than 2^53 rounds
1340
+ * here even when the config holds it exactly — read those with
1341
+ * {@link Config.getString} instead.
1342
+ */
1343
+ toObject(opts) {
1344
+ return hoconToJs(this.root, opts?.nullPrototype === true);
1276
1345
  }
1277
1346
  /**
1278
1347
  * @internal Test-only: renders this resolved Config's value tree as canonical
@@ -1384,16 +1453,25 @@ function scalarToJs(raw, valueType) {
1384
1453
  return raw;
1385
1454
  }
1386
1455
  }
1387
- function hoconToJs(v) {
1456
+ function hoconToJs(v, nullPrototype = false) {
1388
1457
  switch (v.kind) {
1389
1458
  case "scalar":
1390
1459
  return scalarToJs(v.raw, v.valueType);
1391
1460
  case "array":
1392
- return v.items.map(hoconToJs);
1461
+ return v.items.map((item) => hoconToJs(item, nullPrototype));
1393
1462
  case "object": {
1394
- const obj = /* @__PURE__ */ Object.create(null);
1395
- for (const [k, val] of v.fields) obj[k] = hoconToJs(val);
1396
- return Object.assign({}, obj);
1463
+ const entries = Array.from(v.fields, ([k, val]) => [k, hoconToJs(val, nullPrototype)]);
1464
+ if (nullPrototype) {
1465
+ const obj = /* @__PURE__ */ Object.create(null);
1466
+ for (const [k, val] of entries) Object.defineProperty(obj, k, {
1467
+ value: val,
1468
+ writable: true,
1469
+ enumerable: true,
1470
+ configurable: true
1471
+ });
1472
+ return obj;
1473
+ }
1474
+ return Object.fromEntries(entries);
1397
1475
  }
1398
1476
  }
1399
1477
  }
@@ -1478,10 +1556,15 @@ function normalizeLeadingZeroNumber(raw) {
1478
1556
 
1479
1557
  // src/value-factory.ts
1480
1558
  function fromMap(values, originDescription) {
1481
- const fields = coerceObject(values, "");
1559
+ const fields = guardStackDepth(
1560
+ () => coerceObject(values, ""),
1561
+ (msg) => depthError(`fromMap: ${msg}`)
1562
+ );
1482
1563
  const root = { kind: "object", fields };
1483
1564
  return Config._fromResolvedValue(root, { originDescription });
1484
1565
  }
1566
+ var INT64_MAX = 9223372036854775807n;
1567
+ var INT64_MIN = -9223372036854775808n;
1485
1568
  function coerceObject(obj, atPath) {
1486
1569
  const fields = /* @__PURE__ */ new Map();
1487
1570
  for (const [k, v] of Object.entries(obj)) {
@@ -1510,14 +1593,14 @@ function coerceValue(v, atPath) {
1510
1593
  atPath
1511
1594
  );
1512
1595
  }
1513
- return { kind: "scalar", raw: String(v), valueType: "number" };
1596
+ return { kind: "scalar", raw: Object.is(v, -0) ? "-0" : String(v), valueType: "number" };
1514
1597
  }
1515
1598
  case "boolean":
1516
1599
  return { kind: "scalar", raw: v ? "true" : "false", valueType: "boolean" };
1517
1600
  case "bigint": {
1518
- if (v > BigInt(Number.MAX_SAFE_INTEGER) || v < BigInt(Number.MIN_SAFE_INTEGER)) {
1601
+ if (v > INT64_MAX || v < INT64_MIN) {
1519
1602
  throw new ConfigError(
1520
- `fromMap: bigint ${v} exceeds Number.MAX_SAFE_INTEGER (not representable as HOCON number) at path "${atPath}"`,
1603
+ `fromMap: integer ${v} is out of int64 range (HOCON integers are int64; spec F0.5) at path "${atPath}"`,
1521
1604
  atPath
1522
1605
  );
1523
1606
  }
@@ -1541,8 +1624,14 @@ function coerceValue(v, atPath) {
1541
1624
 
1542
1625
  // src/adapters/toml.ts
1543
1626
  function parseTomlConfig(input, originDescription) {
1544
- const doc = (0, import_smol_toml.parse)(input);
1545
- return fromMap(convert(doc, ""), originDescription);
1627
+ const doc = (0, import_smol_toml.parse)(stripBom(input));
1628
+ return fromMap(
1629
+ guardStackDepth(
1630
+ () => convert(doc, ""),
1631
+ (msg) => depthError(`toml: ${msg}`)
1632
+ ),
1633
+ originDescription
1634
+ );
1546
1635
  }
1547
1636
  function convert(v, atPath) {
1548
1637
  if (Array.isArray(v)) return v.map((e, i) => convert(e, `${atPath}[${i}]`));
@@ -1550,11 +1639,11 @@ function convert(v, atPath) {
1550
1639
  return v.toISOString().replace(/\.(\d*?)0+(?=Z?$)/, (_m, keep) => keep ? `.${keep}` : "");
1551
1640
  }
1552
1641
  if (v !== null && typeof v === "object") {
1553
- const out = {};
1554
- for (const [k, e] of Object.entries(v)) {
1555
- out[k] = convert(e, atPath === "" ? k : `${atPath}.${k}`);
1556
- }
1557
- return out;
1642
+ return Object.fromEntries(
1643
+ Object.entries(v).map(
1644
+ ([k, e]) => [k, convert(e, atPath === "" ? k : `${atPath}.${k}`)]
1645
+ )
1646
+ );
1558
1647
  }
1559
1648
  if (typeof v === "number" && !Number.isFinite(v)) {
1560
1649
  throw new ConfigError(
@@ -1,4 +1,4 @@
1
- import { C as Config } from '../config-PdhVgeLS.cjs';
1
+ import { C as Config } from '../config-Dlq6rF3w.cjs';
2
2
 
3
3
  /**
4
4
  * Read a TOML document as HOCON config, via `smol-toml`.
@@ -11,7 +11,8 @@ import { C as Config } from '../config-PdhVgeLS.cjs';
11
11
  * the safe-integer range is an error here rather than silently rounded — go's
12
12
  * adapter accepts those, its int64 being wide enough (spec F0.5).
13
13
  *
14
- * See docs/specs/format-ingestion-mapping.md items F4.x in the hocon scope.
14
+ * See the F4.x items in the format-ingestion mapping spec:
15
+ * https://github.com/o3co/xx.hocon/blob/main/docs/format-ingestion-mapping.md
15
16
  */
16
17
  declare function parseTomlConfig(input: string, originDescription?: string): Config;
17
18
 
@@ -1,4 +1,4 @@
1
- import { C as Config } from '../config-PdhVgeLS.js';
1
+ import { C as Config } from '../config-Dlq6rF3w.js';
2
2
 
3
3
  /**
4
4
  * Read a TOML document as HOCON config, via `smol-toml`.
@@ -11,7 +11,8 @@ import { C as Config } from '../config-PdhVgeLS.js';
11
11
  * the safe-integer range is an error here rather than silently rounded — go's
12
12
  * adapter accepts those, its int64 being wide enough (spec F0.5).
13
13
  *
14
- * See docs/specs/format-ingestion-mapping.md items F4.x in the hocon scope.
14
+ * See the F4.x items in the format-ingestion mapping spec:
15
+ * https://github.com/o3co/xx.hocon/blob/main/docs/format-ingestion-mapping.md
15
16
  */
16
17
  declare function parseTomlConfig(input: string, originDescription?: string): Config;
17
18
 
@@ -516,7 +516,33 @@ function deepMergeHoconValues(base, overlay) {
516
516
  // src/internal/resolver/include-loader.ts
517
517
  import { createRequire } from "module";
518
518
  import * as nodePath from "path";
519
- var _require = createRequire(import.meta.url);
519
+ import { pathToFileURL } from "url";
520
+
521
+ // src/internal/strip-bom.ts
522
+ function stripBom(input) {
523
+ return input.charCodeAt(0) === 65279 ? input.slice(1) : input;
524
+ }
525
+
526
+ // src/internal/depth.ts
527
+ function guardStackDepth(fn, wrap) {
528
+ try {
529
+ return fn();
530
+ } catch (e) {
531
+ if (!isStackOverflow(e)) throw e;
532
+ throw wrap(STACK_MESSAGE);
533
+ }
534
+ }
535
+ var STACK_MESSAGE = "input is nested too deeply for this engine's stack, or contains a cycle (flatten the document, or break the cycle)";
536
+ function isStackOverflow(e) {
537
+ if (!(e instanceof RangeError)) return false;
538
+ const m = e.message.toLowerCase();
539
+ return m.includes("call stack") || // V8: "Maximum call stack size exceeded"
540
+ m.includes("recursion") || // SpiderMonkey: "too much recursion"
541
+ m.includes("stack overflow");
542
+ }
543
+ function depthError(message) {
544
+ return new ConfigError(message, "");
545
+ }
520
546
 
521
547
  // src/value/numeric-array.ts
522
548
  function numericObjectToArray(value) {
@@ -1105,10 +1131,10 @@ var Config = class _Config {
1105
1131
  }
1106
1132
  if (v.kind === "object") {
1107
1133
  const converted = numericObjectToArray(v);
1108
- if (converted !== null) return converted.map(hoconToJs);
1134
+ if (converted !== null) return converted.map((item) => hoconToJs(item));
1109
1135
  }
1110
1136
  if (v.kind !== "array") throw new ConfigError(`expected array at ${path}`, path);
1111
- return v.items.map(hoconToJs);
1137
+ return v.items.map((item) => hoconToJs(item));
1112
1138
  }
1113
1139
  has(path) {
1114
1140
  return this.lookupNode(path) !== void 0;
@@ -1236,8 +1262,52 @@ var Config = class _Config {
1236
1262
  originDescription: this._originDescription
1237
1263
  });
1238
1264
  }
1239
- toObject() {
1240
- return hoconToJs(this.root);
1265
+ /**
1266
+ * The whole config as plain JS data: objects, arrays, strings, numbers,
1267
+ * booleans and `null`.
1268
+ *
1269
+ * Keys are reproduced exactly as the config carries them, `__proto__`,
1270
+ * `constructor` and `prototype` included: each is defined as an **own data
1271
+ * property**, so the returned object's prototype is always
1272
+ * `Object.prototype`, and this call writes nothing to the global one.
1273
+ *
1274
+ * ### Passing the result to other code
1275
+ *
1276
+ * A config file — especially a `.properties` file or an env namespace you
1277
+ * merged in — can therefore hand you an own `__proto__` key, and what happens
1278
+ * next depends entirely on how the consumer copies it:
1279
+ *
1280
+ * ```ts
1281
+ * const data = cfg.toObject()
1282
+ * { ...data } // safe — own properties are copied as data
1283
+ * structuredClone(data) // safe
1284
+ * Object.assign({}, data) // NOT safe — [[Set]] on the {} target hits
1285
+ * // Object.prototype's __proto__ setter: the key
1286
+ * // vanishes and config data becomes the result's
1287
+ * // prototype
1288
+ * deepMerge({}, data) // NOT safe — a naive recursive merge reads
1289
+ * // target['__proto__'], gets Object.prototype,
1290
+ * // and writes to it: global pollution
1291
+ * for (const k in data) … // prefer Object.entries / Object.keys
1292
+ * ```
1293
+ *
1294
+ * Those two hazards come from the **destination** object's prototype, so no
1295
+ * option here can disarm them — copy with spread or `structuredClone` instead.
1296
+ *
1297
+ * What `{ nullPrototype: true }` does give you is a result that inherits
1298
+ * nothing: every object comes back with a `null` prototype, so reading
1299
+ * `x.__proto__`, `x.constructor` or `x.toString` yields config data or
1300
+ * `undefined` rather than something from `Object.prototype`, and code that
1301
+ * merges *into* this object cannot climb out of it. Keys are all still
1302
+ * present. The trade-off is the usual one for prototype-less objects: not
1303
+ * `instanceof Object`, and no `hasOwnProperty` of their own.
1304
+ *
1305
+ * Numbers follow the JS number model, so an integer wider than 2^53 rounds
1306
+ * here even when the config holds it exactly — read those with
1307
+ * {@link Config.getString} instead.
1308
+ */
1309
+ toObject(opts) {
1310
+ return hoconToJs(this.root, opts?.nullPrototype === true);
1241
1311
  }
1242
1312
  /**
1243
1313
  * @internal Test-only: renders this resolved Config's value tree as canonical
@@ -1349,16 +1419,25 @@ function scalarToJs(raw, valueType) {
1349
1419
  return raw;
1350
1420
  }
1351
1421
  }
1352
- function hoconToJs(v) {
1422
+ function hoconToJs(v, nullPrototype = false) {
1353
1423
  switch (v.kind) {
1354
1424
  case "scalar":
1355
1425
  return scalarToJs(v.raw, v.valueType);
1356
1426
  case "array":
1357
- return v.items.map(hoconToJs);
1427
+ return v.items.map((item) => hoconToJs(item, nullPrototype));
1358
1428
  case "object": {
1359
- const obj = /* @__PURE__ */ Object.create(null);
1360
- for (const [k, val] of v.fields) obj[k] = hoconToJs(val);
1361
- return Object.assign({}, obj);
1429
+ const entries = Array.from(v.fields, ([k, val]) => [k, hoconToJs(val, nullPrototype)]);
1430
+ if (nullPrototype) {
1431
+ const obj = /* @__PURE__ */ Object.create(null);
1432
+ for (const [k, val] of entries) Object.defineProperty(obj, k, {
1433
+ value: val,
1434
+ writable: true,
1435
+ enumerable: true,
1436
+ configurable: true
1437
+ });
1438
+ return obj;
1439
+ }
1440
+ return Object.fromEntries(entries);
1362
1441
  }
1363
1442
  }
1364
1443
  }
@@ -1443,10 +1522,15 @@ function normalizeLeadingZeroNumber(raw) {
1443
1522
 
1444
1523
  // src/value-factory.ts
1445
1524
  function fromMap(values, originDescription) {
1446
- const fields = coerceObject(values, "");
1525
+ const fields = guardStackDepth(
1526
+ () => coerceObject(values, ""),
1527
+ (msg) => depthError(`fromMap: ${msg}`)
1528
+ );
1447
1529
  const root = { kind: "object", fields };
1448
1530
  return Config._fromResolvedValue(root, { originDescription });
1449
1531
  }
1532
+ var INT64_MAX = 9223372036854775807n;
1533
+ var INT64_MIN = -9223372036854775808n;
1450
1534
  function coerceObject(obj, atPath) {
1451
1535
  const fields = /* @__PURE__ */ new Map();
1452
1536
  for (const [k, v] of Object.entries(obj)) {
@@ -1475,14 +1559,14 @@ function coerceValue(v, atPath) {
1475
1559
  atPath
1476
1560
  );
1477
1561
  }
1478
- return { kind: "scalar", raw: String(v), valueType: "number" };
1562
+ return { kind: "scalar", raw: Object.is(v, -0) ? "-0" : String(v), valueType: "number" };
1479
1563
  }
1480
1564
  case "boolean":
1481
1565
  return { kind: "scalar", raw: v ? "true" : "false", valueType: "boolean" };
1482
1566
  case "bigint": {
1483
- if (v > BigInt(Number.MAX_SAFE_INTEGER) || v < BigInt(Number.MIN_SAFE_INTEGER)) {
1567
+ if (v > INT64_MAX || v < INT64_MIN) {
1484
1568
  throw new ConfigError(
1485
- `fromMap: bigint ${v} exceeds Number.MAX_SAFE_INTEGER (not representable as HOCON number) at path "${atPath}"`,
1569
+ `fromMap: integer ${v} is out of int64 range (HOCON integers are int64; spec F0.5) at path "${atPath}"`,
1486
1570
  atPath
1487
1571
  );
1488
1572
  }
@@ -1506,8 +1590,14 @@ function coerceValue(v, atPath) {
1506
1590
 
1507
1591
  // src/adapters/toml.ts
1508
1592
  function parseTomlConfig(input, originDescription) {
1509
- const doc = parseToml(input);
1510
- return fromMap(convert(doc, ""), originDescription);
1593
+ const doc = parseToml(stripBom(input));
1594
+ return fromMap(
1595
+ guardStackDepth(
1596
+ () => convert(doc, ""),
1597
+ (msg) => depthError(`toml: ${msg}`)
1598
+ ),
1599
+ originDescription
1600
+ );
1511
1601
  }
1512
1602
  function convert(v, atPath) {
1513
1603
  if (Array.isArray(v)) return v.map((e, i) => convert(e, `${atPath}[${i}]`));
@@ -1515,11 +1605,11 @@ function convert(v, atPath) {
1515
1605
  return v.toISOString().replace(/\.(\d*?)0+(?=Z?$)/, (_m, keep) => keep ? `.${keep}` : "");
1516
1606
  }
1517
1607
  if (v !== null && typeof v === "object") {
1518
- const out = {};
1519
- for (const [k, e] of Object.entries(v)) {
1520
- out[k] = convert(e, atPath === "" ? k : `${atPath}.${k}`);
1521
- }
1522
- return out;
1608
+ return Object.fromEntries(
1609
+ Object.entries(v).map(
1610
+ ([k, e]) => [k, convert(e, atPath === "" ? k : `${atPath}.${k}`)]
1611
+ )
1612
+ );
1523
1613
  }
1524
1614
  if (typeof v === "number" && !Number.isFinite(v)) {
1525
1615
  throw new ConfigError(