@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.
@@ -551,8 +551,33 @@ function deepMergeHoconValues(base, overlay) {
551
551
  // src/internal/resolver/include-loader.ts
552
552
  var import_node_module = require("module");
553
553
  var nodePath = __toESM(require("path"), 1);
554
- var import_meta = {};
555
- var _require = (0, import_node_module.createRequire)(import_meta.url);
554
+ var import_node_url = require("url");
555
+
556
+ // src/internal/strip-bom.ts
557
+ function stripBom(input) {
558
+ return input.charCodeAt(0) === 65279 ? input.slice(1) : input;
559
+ }
560
+
561
+ // src/internal/depth.ts
562
+ function guardStackDepth(fn, wrap) {
563
+ try {
564
+ return fn();
565
+ } catch (e) {
566
+ if (!isStackOverflow(e)) throw e;
567
+ throw wrap(STACK_MESSAGE);
568
+ }
569
+ }
570
+ var STACK_MESSAGE = "input is nested too deeply for this engine's stack, or contains a cycle (flatten the document, or break the cycle)";
571
+ function isStackOverflow(e) {
572
+ if (!(e instanceof RangeError)) return false;
573
+ const m = e.message.toLowerCase();
574
+ return m.includes("call stack") || // V8: "Maximum call stack size exceeded"
575
+ m.includes("recursion") || // SpiderMonkey: "too much recursion"
576
+ m.includes("stack overflow");
577
+ }
578
+ function depthError(message) {
579
+ return new ConfigError(message, "");
580
+ }
556
581
 
557
582
  // src/value/numeric-array.ts
558
583
  function numericObjectToArray(value) {
@@ -1141,10 +1166,10 @@ var Config = class _Config {
1141
1166
  }
1142
1167
  if (v.kind === "object") {
1143
1168
  const converted = numericObjectToArray(v);
1144
- if (converted !== null) return converted.map(hoconToJs);
1169
+ if (converted !== null) return converted.map((item) => hoconToJs(item));
1145
1170
  }
1146
1171
  if (v.kind !== "array") throw new ConfigError(`expected array at ${path}`, path);
1147
- return v.items.map(hoconToJs);
1172
+ return v.items.map((item) => hoconToJs(item));
1148
1173
  }
1149
1174
  has(path) {
1150
1175
  return this.lookupNode(path) !== void 0;
@@ -1272,8 +1297,52 @@ var Config = class _Config {
1272
1297
  originDescription: this._originDescription
1273
1298
  });
1274
1299
  }
1275
- toObject() {
1276
- return hoconToJs(this.root);
1300
+ /**
1301
+ * The whole config as plain JS data: objects, arrays, strings, numbers,
1302
+ * booleans and `null`.
1303
+ *
1304
+ * Keys are reproduced exactly as the config carries them, `__proto__`,
1305
+ * `constructor` and `prototype` included: each is defined as an **own data
1306
+ * property**, so the returned object's prototype is always
1307
+ * `Object.prototype`, and this call writes nothing to the global one.
1308
+ *
1309
+ * ### Passing the result to other code
1310
+ *
1311
+ * A config file — especially a `.properties` file or an env namespace you
1312
+ * merged in — can therefore hand you an own `__proto__` key, and what happens
1313
+ * next depends entirely on how the consumer copies it:
1314
+ *
1315
+ * ```ts
1316
+ * const data = cfg.toObject()
1317
+ * { ...data } // safe — own properties are copied as data
1318
+ * structuredClone(data) // safe
1319
+ * Object.assign({}, data) // NOT safe — [[Set]] on the {} target hits
1320
+ * // Object.prototype's __proto__ setter: the key
1321
+ * // vanishes and config data becomes the result's
1322
+ * // prototype
1323
+ * deepMerge({}, data) // NOT safe — a naive recursive merge reads
1324
+ * // target['__proto__'], gets Object.prototype,
1325
+ * // and writes to it: global pollution
1326
+ * for (const k in data) … // prefer Object.entries / Object.keys
1327
+ * ```
1328
+ *
1329
+ * Those two hazards come from the **destination** object's prototype, so no
1330
+ * option here can disarm them — copy with spread or `structuredClone` instead.
1331
+ *
1332
+ * What `{ nullPrototype: true }` does give you is a result that inherits
1333
+ * nothing: every object comes back with a `null` prototype, so reading
1334
+ * `x.__proto__`, `x.constructor` or `x.toString` yields config data or
1335
+ * `undefined` rather than something from `Object.prototype`, and code that
1336
+ * merges *into* this object cannot climb out of it. Keys are all still
1337
+ * present. The trade-off is the usual one for prototype-less objects: not
1338
+ * `instanceof Object`, and no `hasOwnProperty` of their own.
1339
+ *
1340
+ * Numbers follow the JS number model, so an integer wider than 2^53 rounds
1341
+ * here even when the config holds it exactly — read those with
1342
+ * {@link Config.getString} instead.
1343
+ */
1344
+ toObject(opts) {
1345
+ return hoconToJs(this.root, opts?.nullPrototype === true);
1277
1346
  }
1278
1347
  /**
1279
1348
  * @internal Test-only: renders this resolved Config's value tree as canonical
@@ -1385,16 +1454,25 @@ function scalarToJs(raw, valueType) {
1385
1454
  return raw;
1386
1455
  }
1387
1456
  }
1388
- function hoconToJs(v) {
1457
+ function hoconToJs(v, nullPrototype = false) {
1389
1458
  switch (v.kind) {
1390
1459
  case "scalar":
1391
1460
  return scalarToJs(v.raw, v.valueType);
1392
1461
  case "array":
1393
- return v.items.map(hoconToJs);
1462
+ return v.items.map((item) => hoconToJs(item, nullPrototype));
1394
1463
  case "object": {
1395
- const obj = /* @__PURE__ */ Object.create(null);
1396
- for (const [k, val] of v.fields) obj[k] = hoconToJs(val);
1397
- return Object.assign({}, obj);
1464
+ const entries = Array.from(v.fields, ([k, val]) => [k, hoconToJs(val, nullPrototype)]);
1465
+ if (nullPrototype) {
1466
+ const obj = /* @__PURE__ */ Object.create(null);
1467
+ for (const [k, val] of entries) Object.defineProperty(obj, k, {
1468
+ value: val,
1469
+ writable: true,
1470
+ enumerable: true,
1471
+ configurable: true
1472
+ });
1473
+ return obj;
1474
+ }
1475
+ return Object.fromEntries(entries);
1398
1476
  }
1399
1477
  }
1400
1478
  }
@@ -1479,10 +1557,15 @@ function normalizeLeadingZeroNumber(raw) {
1479
1557
 
1480
1558
  // src/value-factory.ts
1481
1559
  function fromMap(values, originDescription) {
1482
- const fields = coerceObject(values, "");
1560
+ const fields = guardStackDepth(
1561
+ () => coerceObject(values, ""),
1562
+ (msg) => depthError(`fromMap: ${msg}`)
1563
+ );
1483
1564
  const root = { kind: "object", fields };
1484
1565
  return Config._fromResolvedValue(root, { originDescription });
1485
1566
  }
1567
+ var INT64_MAX = 9223372036854775807n;
1568
+ var INT64_MIN = -9223372036854775808n;
1486
1569
  function coerceObject(obj, atPath) {
1487
1570
  const fields = /* @__PURE__ */ new Map();
1488
1571
  for (const [k, v] of Object.entries(obj)) {
@@ -1511,14 +1594,14 @@ function coerceValue(v, atPath) {
1511
1594
  atPath
1512
1595
  );
1513
1596
  }
1514
- return { kind: "scalar", raw: String(v), valueType: "number" };
1597
+ return { kind: "scalar", raw: Object.is(v, -0) ? "-0" : String(v), valueType: "number" };
1515
1598
  }
1516
1599
  case "boolean":
1517
1600
  return { kind: "scalar", raw: v ? "true" : "false", valueType: "boolean" };
1518
1601
  case "bigint": {
1519
- if (v > BigInt(Number.MAX_SAFE_INTEGER) || v < BigInt(Number.MIN_SAFE_INTEGER)) {
1602
+ if (v > INT64_MAX || v < INT64_MIN) {
1520
1603
  throw new ConfigError(
1521
- `fromMap: bigint ${v} exceeds Number.MAX_SAFE_INTEGER (not representable as HOCON number) at path "${atPath}"`,
1604
+ `fromMap: integer ${v} is out of int64 range (HOCON integers are int64; spec F0.5) at path "${atPath}"`,
1522
1605
  atPath
1523
1606
  );
1524
1607
  }
@@ -1542,7 +1625,7 @@ function coerceValue(v, atPath) {
1542
1625
 
1543
1626
  // src/adapters/yaml.ts
1544
1627
  function parseYaml(input, originDescription) {
1545
- const docs = import_yaml.default.parseAllDocuments(input, { version: "1.2", merge: true });
1628
+ const docs = import_yaml.default.parseAllDocuments(stripBom(input), { version: "1.2", merge: true, intAsBigInt: true });
1546
1629
  if (docs.length > 1) {
1547
1630
  throw new ConfigError(
1548
1631
  "yaml: multi-document streams are not supported (spec F5.7); a config is one document",
@@ -1554,11 +1637,14 @@ function parseYaml(input, originDescription) {
1554
1637
  if (doc.errors.length > 0) {
1555
1638
  throw new ConfigError(`yaml: ${doc.errors[0]?.message ?? "parse error"}`, "");
1556
1639
  }
1557
- return fromYamlValue(doc.toJS({ mapAsMap: false }), originDescription);
1640
+ return fromYamlValue(doc.toJS({ mapAsMap: true }), originDescription);
1558
1641
  }
1559
1642
  function fromYamlValue(value, originDescription) {
1560
1643
  if (value === null || value === void 0) return fromMap({}, originDescription);
1561
- const normalized = convert(value, "");
1644
+ const normalized = guardStackDepth(
1645
+ () => convert(value, ""),
1646
+ (msg) => depthError(`yaml: ${msg}`)
1647
+ );
1562
1648
  if (typeof normalized !== "object" || normalized === null || Array.isArray(normalized)) {
1563
1649
  throw new ConfigError(
1564
1650
  `yaml: document root is ${Array.isArray(normalized) ? "an array" : typeof normalized}, but a config root must be an object (spec F0.3)`,
@@ -1573,7 +1659,8 @@ function convert(v, atPath) {
1573
1659
  return v.toISOString();
1574
1660
  }
1575
1661
  if (v instanceof Map) {
1576
- const out = {};
1662
+ const entries = [];
1663
+ const seen = /* @__PURE__ */ new Map();
1577
1664
  for (const [k, e] of v) {
1578
1665
  if (k !== null && typeof k === "object") {
1579
1666
  throw new ConfigError(
@@ -1581,19 +1668,27 @@ function convert(v, atPath) {
1581
1668
  atPath
1582
1669
  );
1583
1670
  }
1584
- out[String(k)] = convert(e, atPath === "" ? String(k) : `${atPath}.${String(k)}`);
1671
+ const key = String(k);
1672
+ if (seen.has(key)) {
1673
+ throw new ConfigError(
1674
+ `yaml: sibling mapping keys ${keyForm(seen.get(key))} and ${keyForm(k)} both give the key "${key}"${atPath === "" ? "" : ` at "${atPath}"`}; rename one of them, because one of the two values would otherwise be lost. Quoting a non-string key helps only where that changes the key text, as 0x10 does and 1 does not (spec F5.3)`,
1675
+ atPath
1676
+ );
1677
+ }
1678
+ seen.set(key, k);
1679
+ entries.push([key, convert(e, atPath === "" ? key : `${atPath}.${key}`)]);
1585
1680
  }
1586
- return out;
1681
+ return Object.fromEntries(entries);
1587
1682
  }
1588
1683
  if (v instanceof Uint8Array) {
1589
- return btoa(String.fromCharCode(...v));
1684
+ return bytesToBase64(v);
1590
1685
  }
1591
1686
  if (v !== null && typeof v === "object") {
1592
- const out = {};
1593
- for (const [k, e] of Object.entries(v)) {
1594
- out[k] = convert(e, atPath === "" ? k : `${atPath}.${k}`);
1595
- }
1596
- return out;
1687
+ return Object.fromEntries(
1688
+ Object.entries(v).map(
1689
+ ([k, e]) => [k, convert(e, atPath === "" ? k : `${atPath}.${k}`)]
1690
+ )
1691
+ );
1597
1692
  }
1598
1693
  if (typeof v === "number" && !Number.isFinite(v)) {
1599
1694
  throw new ConfigError(
@@ -1601,8 +1696,28 @@ function convert(v, atPath) {
1601
1696
  atPath
1602
1697
  );
1603
1698
  }
1699
+ if (typeof v === "bigint") {
1700
+ return isSafeIntegerBigInt(v) ? Number(v) : v;
1701
+ }
1604
1702
  return v;
1605
1703
  }
1704
+ function bytesToBase64(bytes) {
1705
+ const CHUNK = 32768;
1706
+ let binary = "";
1707
+ for (let i = 0; i < bytes.length; i += CHUNK) {
1708
+ binary += String.fromCharCode(...bytes.subarray(i, i + CHUNK));
1709
+ }
1710
+ return btoa(binary);
1711
+ }
1712
+ function keyForm(k) {
1713
+ if (typeof k === "string") return JSON.stringify(k);
1714
+ return `${String(k)} (${k === null ? "null" : typeof k})`;
1715
+ }
1716
+ var MAX_SAFE = BigInt(Number.MAX_SAFE_INTEGER);
1717
+ var MIN_SAFE = BigInt(Number.MIN_SAFE_INTEGER);
1718
+ function isSafeIntegerBigInt(v) {
1719
+ return v <= MAX_SAFE && v >= MIN_SAFE;
1720
+ }
1606
1721
  // Annotate the CommonJS export names for ESM import in node:
1607
1722
  0 && (module.exports = {
1608
1723
  fromYamlValue,
@@ -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 YAML document as HOCON config.
@@ -23,7 +23,14 @@ import { C as Config } from '../config-PdhVgeLS.cjs';
23
23
  * const cfg = fromYamlValue(jsy.load(src), 'their-file.yml')
24
24
  * ```
25
25
  *
26
- * See docs/specs/format-ingestion-mapping.md items F5.x in the hocon scope.
26
+ * Integers are ingested losslessly (F0.5): the document is decoded with
27
+ * `intAsBigInt`, so a literal too wide for a JS `number` keeps its digits —
28
+ * `getString` returns them exactly — and one outside int64 is refused rather
29
+ * than rounded. `getNumber` and `toObject` still apply the JS number model, so
30
+ * read large identifiers with `getString`.
31
+ *
32
+ * See the F5.x items in the format-ingestion mapping spec:
33
+ * https://github.com/o3co/xx.hocon/blob/main/docs/format-ingestion-mapping.md
27
34
  */
28
35
  declare function parseYaml(input: string, originDescription?: string): Config;
29
36
  /**
@@ -36,7 +43,9 @@ declare function parseYaml(input: string, originDescription?: string): Config;
36
43
  * only the default one: a `Map` (eemeli with `mapAsMap`) has its scalar keys
37
44
  * stringified per F5.3, a `Date` (js-yaml 4 timestamps) becomes its ISO string
38
45
  * — the same reasoning as F4.2 for TOML dates — and a `Uint8Array` becomes
39
- * base64 (F5.5).
46
+ * base64 (F5.5), at any size. A `bigint` (a library decoding integers widely)
47
+ * narrows to a number where a double is exact and otherwise keeps its digits,
48
+ * bounded by int64 (F0.5).
40
49
  */
41
50
  declare function fromYamlValue(value: unknown, originDescription?: string): Config;
42
51
 
@@ -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 YAML document as HOCON config.
@@ -23,7 +23,14 @@ import { C as Config } from '../config-PdhVgeLS.js';
23
23
  * const cfg = fromYamlValue(jsy.load(src), 'their-file.yml')
24
24
  * ```
25
25
  *
26
- * See docs/specs/format-ingestion-mapping.md items F5.x in the hocon scope.
26
+ * Integers are ingested losslessly (F0.5): the document is decoded with
27
+ * `intAsBigInt`, so a literal too wide for a JS `number` keeps its digits —
28
+ * `getString` returns them exactly — and one outside int64 is refused rather
29
+ * than rounded. `getNumber` and `toObject` still apply the JS number model, so
30
+ * read large identifiers with `getString`.
31
+ *
32
+ * See the F5.x items in the format-ingestion mapping spec:
33
+ * https://github.com/o3co/xx.hocon/blob/main/docs/format-ingestion-mapping.md
27
34
  */
28
35
  declare function parseYaml(input: string, originDescription?: string): Config;
29
36
  /**
@@ -36,7 +43,9 @@ declare function parseYaml(input: string, originDescription?: string): Config;
36
43
  * only the default one: a `Map` (eemeli with `mapAsMap`) has its scalar keys
37
44
  * stringified per F5.3, a `Date` (js-yaml 4 timestamps) becomes its ISO string
38
45
  * — the same reasoning as F4.2 for TOML dates — and a `Uint8Array` becomes
39
- * base64 (F5.5).
46
+ * base64 (F5.5), at any size. A `bigint` (a library decoding integers widely)
47
+ * narrows to a number where a double is exact and otherwise keeps its digits,
48
+ * bounded by int64 (F0.5).
40
49
  */
41
50
  declare function fromYamlValue(value: unknown, originDescription?: string): Config;
42
51
 
@@ -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,7 +1590,7 @@ function coerceValue(v, atPath) {
1506
1590
 
1507
1591
  // src/adapters/yaml.ts
1508
1592
  function parseYaml(input, originDescription) {
1509
- const docs = YAML.parseAllDocuments(input, { version: "1.2", merge: true });
1593
+ const docs = YAML.parseAllDocuments(stripBom(input), { version: "1.2", merge: true, intAsBigInt: true });
1510
1594
  if (docs.length > 1) {
1511
1595
  throw new ConfigError(
1512
1596
  "yaml: multi-document streams are not supported (spec F5.7); a config is one document",
@@ -1518,11 +1602,14 @@ function parseYaml(input, originDescription) {
1518
1602
  if (doc.errors.length > 0) {
1519
1603
  throw new ConfigError(`yaml: ${doc.errors[0]?.message ?? "parse error"}`, "");
1520
1604
  }
1521
- return fromYamlValue(doc.toJS({ mapAsMap: false }), originDescription);
1605
+ return fromYamlValue(doc.toJS({ mapAsMap: true }), originDescription);
1522
1606
  }
1523
1607
  function fromYamlValue(value, originDescription) {
1524
1608
  if (value === null || value === void 0) return fromMap({}, originDescription);
1525
- const normalized = convert(value, "");
1609
+ const normalized = guardStackDepth(
1610
+ () => convert(value, ""),
1611
+ (msg) => depthError(`yaml: ${msg}`)
1612
+ );
1526
1613
  if (typeof normalized !== "object" || normalized === null || Array.isArray(normalized)) {
1527
1614
  throw new ConfigError(
1528
1615
  `yaml: document root is ${Array.isArray(normalized) ? "an array" : typeof normalized}, but a config root must be an object (spec F0.3)`,
@@ -1537,7 +1624,8 @@ function convert(v, atPath) {
1537
1624
  return v.toISOString();
1538
1625
  }
1539
1626
  if (v instanceof Map) {
1540
- const out = {};
1627
+ const entries = [];
1628
+ const seen = /* @__PURE__ */ new Map();
1541
1629
  for (const [k, e] of v) {
1542
1630
  if (k !== null && typeof k === "object") {
1543
1631
  throw new ConfigError(
@@ -1545,19 +1633,27 @@ function convert(v, atPath) {
1545
1633
  atPath
1546
1634
  );
1547
1635
  }
1548
- out[String(k)] = convert(e, atPath === "" ? String(k) : `${atPath}.${String(k)}`);
1636
+ const key = String(k);
1637
+ if (seen.has(key)) {
1638
+ throw new ConfigError(
1639
+ `yaml: sibling mapping keys ${keyForm(seen.get(key))} and ${keyForm(k)} both give the key "${key}"${atPath === "" ? "" : ` at "${atPath}"`}; rename one of them, because one of the two values would otherwise be lost. Quoting a non-string key helps only where that changes the key text, as 0x10 does and 1 does not (spec F5.3)`,
1640
+ atPath
1641
+ );
1642
+ }
1643
+ seen.set(key, k);
1644
+ entries.push([key, convert(e, atPath === "" ? key : `${atPath}.${key}`)]);
1549
1645
  }
1550
- return out;
1646
+ return Object.fromEntries(entries);
1551
1647
  }
1552
1648
  if (v instanceof Uint8Array) {
1553
- return btoa(String.fromCharCode(...v));
1649
+ return bytesToBase64(v);
1554
1650
  }
1555
1651
  if (v !== null && typeof v === "object") {
1556
- const out = {};
1557
- for (const [k, e] of Object.entries(v)) {
1558
- out[k] = convert(e, atPath === "" ? k : `${atPath}.${k}`);
1559
- }
1560
- return out;
1652
+ return Object.fromEntries(
1653
+ Object.entries(v).map(
1654
+ ([k, e]) => [k, convert(e, atPath === "" ? k : `${atPath}.${k}`)]
1655
+ )
1656
+ );
1561
1657
  }
1562
1658
  if (typeof v === "number" && !Number.isFinite(v)) {
1563
1659
  throw new ConfigError(
@@ -1565,8 +1661,28 @@ function convert(v, atPath) {
1565
1661
  atPath
1566
1662
  );
1567
1663
  }
1664
+ if (typeof v === "bigint") {
1665
+ return isSafeIntegerBigInt(v) ? Number(v) : v;
1666
+ }
1568
1667
  return v;
1569
1668
  }
1669
+ function bytesToBase64(bytes) {
1670
+ const CHUNK = 32768;
1671
+ let binary = "";
1672
+ for (let i = 0; i < bytes.length; i += CHUNK) {
1673
+ binary += String.fromCharCode(...bytes.subarray(i, i + CHUNK));
1674
+ }
1675
+ return btoa(binary);
1676
+ }
1677
+ function keyForm(k) {
1678
+ if (typeof k === "string") return JSON.stringify(k);
1679
+ return `${String(k)} (${k === null ? "null" : typeof k})`;
1680
+ }
1681
+ var MAX_SAFE = BigInt(Number.MAX_SAFE_INTEGER);
1682
+ var MIN_SAFE = BigInt(Number.MIN_SAFE_INTEGER);
1683
+ function isSafeIntegerBigInt(v) {
1684
+ return v <= MAX_SAFE && v >= MIN_SAFE;
1685
+ }
1570
1686
  export {
1571
1687
  fromYamlValue,
1572
1688
  parseYaml
@@ -316,7 +316,53 @@ declare class Config {
316
316
  * E12 decisions 9, 10.
317
317
  */
318
318
  resolveWith(source: Config, opts?: ResolveOptions): Config;
319
- toObject(): unknown;
319
+ /**
320
+ * The whole config as plain JS data: objects, arrays, strings, numbers,
321
+ * booleans and `null`.
322
+ *
323
+ * Keys are reproduced exactly as the config carries them, `__proto__`,
324
+ * `constructor` and `prototype` included: each is defined as an **own data
325
+ * property**, so the returned object's prototype is always
326
+ * `Object.prototype`, and this call writes nothing to the global one.
327
+ *
328
+ * ### Passing the result to other code
329
+ *
330
+ * A config file — especially a `.properties` file or an env namespace you
331
+ * merged in — can therefore hand you an own `__proto__` key, and what happens
332
+ * next depends entirely on how the consumer copies it:
333
+ *
334
+ * ```ts
335
+ * const data = cfg.toObject()
336
+ * { ...data } // safe — own properties are copied as data
337
+ * structuredClone(data) // safe
338
+ * Object.assign({}, data) // NOT safe — [[Set]] on the {} target hits
339
+ * // Object.prototype's __proto__ setter: the key
340
+ * // vanishes and config data becomes the result's
341
+ * // prototype
342
+ * deepMerge({}, data) // NOT safe — a naive recursive merge reads
343
+ * // target['__proto__'], gets Object.prototype,
344
+ * // and writes to it: global pollution
345
+ * for (const k in data) … // prefer Object.entries / Object.keys
346
+ * ```
347
+ *
348
+ * Those two hazards come from the **destination** object's prototype, so no
349
+ * option here can disarm them — copy with spread or `structuredClone` instead.
350
+ *
351
+ * What `{ nullPrototype: true }` does give you is a result that inherits
352
+ * nothing: every object comes back with a `null` prototype, so reading
353
+ * `x.__proto__`, `x.constructor` or `x.toString` yields config data or
354
+ * `undefined` rather than something from `Object.prototype`, and code that
355
+ * merges *into* this object cannot climb out of it. Keys are all still
356
+ * present. The trade-off is the usual one for prototype-less objects: not
357
+ * `instanceof Object`, and no `hasOwnProperty` of their own.
358
+ *
359
+ * Numbers follow the JS number model, so an integer wider than 2^53 rounds
360
+ * here even when the config holds it exactly — read those with
361
+ * {@link Config.getString} instead.
362
+ */
363
+ toObject(opts?: {
364
+ nullPrototype?: boolean;
365
+ }): unknown;
320
366
  /**
321
367
  * @internal Test-only: renders this resolved Config's value tree as canonical
322
368
  * JSON (sorted keys, no whitespace). Used by Layer-2 fixture tests to compare