@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/README.ja.md +129 -1
- package/README.md +103 -10
- package/dist/adapters/env.cjs +150 -31
- package/dist/adapters/env.d.cts +16 -1
- package/dist/adapters/env.d.ts +16 -1
- package/dist/adapters/env.js +150 -30
- package/dist/adapters/jsonc.cjs +148 -18
- package/dist/adapters/jsonc.d.cts +36 -5
- package/dist/adapters/jsonc.d.ts +36 -5
- package/dist/adapters/jsonc.js +148 -17
- package/dist/adapters/properties.cjs +124 -25
- package/dist/adapters/properties.d.cts +6 -1
- package/dist/adapters/properties.d.ts +6 -1
- package/dist/adapters/properties.js +124 -24
- package/dist/adapters/toml.cjs +111 -22
- package/dist/adapters/toml.d.cts +3 -2
- package/dist/adapters/toml.d.ts +3 -2
- package/dist/adapters/toml.js +111 -21
- package/dist/adapters/yaml.cjs +142 -27
- package/dist/adapters/yaml.d.cts +12 -3
- package/dist/adapters/yaml.d.ts +12 -3
- package/dist/adapters/yaml.js +142 -26
- package/dist/{config-PdhVgeLS.d.cts → config-Dlq6rF3w.d.cts} +47 -1
- package/dist/{config-PdhVgeLS.d.ts → config-Dlq6rF3w.d.ts} +47 -1
- package/dist/index.cjs +162 -31
- package/dist/index.d.cts +10 -3
- package/dist/index.d.ts +10 -3
- package/dist/index.js +169 -31
- package/dist/zod.cjs +153 -23
- package/dist/zod.d.cts +1 -1
- package/dist/zod.d.ts +1 -1
- package/dist/zod.js +160 -23
- package/package.json +58 -22
package/dist/adapters/yaml.cjs
CHANGED
|
@@ -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
|
|
555
|
-
|
|
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
|
-
|
|
1276
|
-
|
|
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
|
|
1396
|
-
|
|
1397
|
-
|
|
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 =
|
|
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 >
|
|
1602
|
+
if (v > INT64_MAX || v < INT64_MIN) {
|
|
1520
1603
|
throw new ConfigError(
|
|
1521
|
-
`fromMap:
|
|
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:
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
|
1681
|
+
return Object.fromEntries(entries);
|
|
1587
1682
|
}
|
|
1588
1683
|
if (v instanceof Uint8Array) {
|
|
1589
|
-
return
|
|
1684
|
+
return bytesToBase64(v);
|
|
1590
1685
|
}
|
|
1591
1686
|
if (v !== null && typeof v === "object") {
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
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,
|
package/dist/adapters/yaml.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as Config } from '../config-
|
|
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
|
-
*
|
|
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
|
|
package/dist/adapters/yaml.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as Config } from '../config-
|
|
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
|
-
*
|
|
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
|
|
package/dist/adapters/yaml.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
1240
|
-
|
|
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
|
|
1360
|
-
|
|
1361
|
-
|
|
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 =
|
|
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 >
|
|
1567
|
+
if (v > INT64_MAX || v < INT64_MIN) {
|
|
1484
1568
|
throw new ConfigError(
|
|
1485
|
-
`fromMap:
|
|
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:
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
|
1646
|
+
return Object.fromEntries(entries);
|
|
1551
1647
|
}
|
|
1552
1648
|
if (v instanceof Uint8Array) {
|
|
1553
|
-
return
|
|
1649
|
+
return bytesToBase64(v);
|
|
1554
1650
|
}
|
|
1555
1651
|
if (v !== null && typeof v === "object") {
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
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
|
-
|
|
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
|