@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/env.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
|
type EnvOptions = {
|
|
4
4
|
/**
|
|
@@ -22,6 +22,16 @@ type EnvOptions = {
|
|
|
22
22
|
* Reading a single variable needs nothing from here — HOCON's own `${?VAR}`
|
|
23
23
|
* already does that. This is for mounting a whole namespace as a subtree.
|
|
24
24
|
*
|
|
25
|
+
* `__` is the only path separator: a single `_` stays part of its segment
|
|
26
|
+
* (`APP_DB__MAX_CONN` → `db.max_conn`), and a literal `.` in a variable name is
|
|
27
|
+
* key text rather than a boundary, so `APP_FOO.BAR` becomes the single
|
|
28
|
+
* top-level key `foo.bar` — addressable as the quoted path `"foo.bar"` — and
|
|
29
|
+
* coexists with `APP_FOO__BAR` instead of colliding with it (F1.2).
|
|
30
|
+
*
|
|
31
|
+
* Two names that do map to one path are an error, the environment having no
|
|
32
|
+
* order to break the tie with (F1.6). Keys named `__proto__`, `constructor` or
|
|
33
|
+
* `prototype` are kept like any other (F2.9).
|
|
34
|
+
*
|
|
25
35
|
* Values are always strings, and a `${...}` inside one stays literal (F0.2, F1.4).
|
|
26
36
|
*/
|
|
27
37
|
declare function loadEnv(opts?: EnvOptions & {
|
|
@@ -35,6 +45,11 @@ declare function loadEnv(opts?: EnvOptions & {
|
|
|
35
45
|
* double quotes with `\n \r \t \\ \"`. Multi-line values and trailing comments
|
|
36
46
|
* are not supported — an unquoted value containing ` #` is an error rather than
|
|
37
47
|
* a guess about whether a comment was meant. No `${...}` expansion.
|
|
48
|
+
*
|
|
49
|
+
* Names map to paths exactly as in {@link loadEnv}: `__` is the only separator,
|
|
50
|
+
* so a literal `.` stays key text (`FOO.BAR` → the single key `foo.bar`). A
|
|
51
|
+
* file has a definite line order, so a repeated name is last-wins (F0.7)
|
|
52
|
+
* rather than the collision error the process environment gets.
|
|
38
53
|
*/
|
|
39
54
|
declare function parseDotEnv(input: string, opts?: EnvOptions): Config;
|
|
40
55
|
|
package/dist/adapters/env.js
CHANGED
|
@@ -513,36 +513,73 @@ function deepMergeHoconValues(base, overlay) {
|
|
|
513
513
|
// src/internal/resolver/include-loader.ts
|
|
514
514
|
import { createRequire } from "module";
|
|
515
515
|
import * as nodePath from "path";
|
|
516
|
+
import { pathToFileURL } from "url";
|
|
517
|
+
|
|
518
|
+
// src/internal/strip-bom.ts
|
|
519
|
+
function stripBom(input) {
|
|
520
|
+
return input.charCodeAt(0) === 65279 ? input.slice(1) : input;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// src/internal/depth.ts
|
|
524
|
+
var MAX_PATH_SEGMENTS = 64;
|
|
525
|
+
function tooDeep(segments) {
|
|
526
|
+
return segments > MAX_PATH_SEGMENTS;
|
|
527
|
+
}
|
|
528
|
+
function guardStackDepth(fn, wrap) {
|
|
529
|
+
try {
|
|
530
|
+
return fn();
|
|
531
|
+
} catch (e) {
|
|
532
|
+
if (!isStackOverflow(e)) throw e;
|
|
533
|
+
throw wrap(STACK_MESSAGE);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
var STACK_MESSAGE = "input is nested too deeply for this engine's stack, or contains a cycle (flatten the document, or break the cycle)";
|
|
537
|
+
function isStackOverflow(e) {
|
|
538
|
+
if (!(e instanceof RangeError)) return false;
|
|
539
|
+
const m = e.message.toLowerCase();
|
|
540
|
+
return m.includes("call stack") || // V8: "Maximum call stack size exceeded"
|
|
541
|
+
m.includes("recursion") || // SpiderMonkey: "too much recursion"
|
|
542
|
+
m.includes("stack overflow");
|
|
543
|
+
}
|
|
544
|
+
function depthError(message) {
|
|
545
|
+
return new ConfigError(message, "");
|
|
546
|
+
}
|
|
516
547
|
|
|
517
548
|
// src/internal/properties/properties.ts
|
|
518
549
|
function nestPairs(pairs) {
|
|
519
550
|
const root = /* @__PURE__ */ Object.create(null);
|
|
520
|
-
const sorted = [...pairs].sort(([a], [b]) => a
|
|
521
|
-
for (const [
|
|
522
|
-
setNested(root,
|
|
551
|
+
const sorted = [...pairs].sort(([a], [b]) => compareSegments(a, b));
|
|
552
|
+
for (const [segments, value] of sorted) {
|
|
553
|
+
setNested(root, segments, value);
|
|
523
554
|
}
|
|
524
555
|
return root;
|
|
525
556
|
}
|
|
526
|
-
|
|
557
|
+
function compareSegments(a, b) {
|
|
558
|
+
const n = Math.min(a.length, b.length);
|
|
559
|
+
for (let i = 0; i < n; i++) {
|
|
560
|
+
const x = a[i];
|
|
561
|
+
const y = b[i];
|
|
562
|
+
if (x !== y) return x < y ? -1 : 1;
|
|
563
|
+
}
|
|
564
|
+
return a.length - b.length;
|
|
565
|
+
}
|
|
566
|
+
function pathKey(segments) {
|
|
567
|
+
return JSON.stringify(segments);
|
|
568
|
+
}
|
|
527
569
|
function setNested(obj, segments, value) {
|
|
528
570
|
let current = obj;
|
|
529
571
|
for (let i = 0; i < segments.length - 1; i++) {
|
|
530
572
|
const seg = segments[i];
|
|
531
|
-
if (seg === void 0 || DANGEROUS_KEYS.has(seg)) return;
|
|
532
573
|
if (!(seg in current) || typeof current[seg] !== "object" || current[seg] === null) {
|
|
533
574
|
current[seg] = /* @__PURE__ */ Object.create(null);
|
|
534
575
|
}
|
|
535
576
|
current = current[seg];
|
|
536
577
|
}
|
|
537
578
|
const last = segments[segments.length - 1];
|
|
538
|
-
if (last === void 0 || DANGEROUS_KEYS.has(last)) return;
|
|
539
579
|
if (typeof current[last] === "object" && current[last] !== null) return;
|
|
540
580
|
current[last] = value;
|
|
541
581
|
}
|
|
542
582
|
|
|
543
|
-
// src/internal/resolver/include-loader.ts
|
|
544
|
-
var _require = createRequire(import.meta.url);
|
|
545
|
-
|
|
546
583
|
// src/value/numeric-array.ts
|
|
547
584
|
function numericObjectToArray(value) {
|
|
548
585
|
if (value.kind !== "object") return null;
|
|
@@ -1130,10 +1167,10 @@ var Config = class _Config {
|
|
|
1130
1167
|
}
|
|
1131
1168
|
if (v.kind === "object") {
|
|
1132
1169
|
const converted = numericObjectToArray(v);
|
|
1133
|
-
if (converted !== null) return converted.map(hoconToJs);
|
|
1170
|
+
if (converted !== null) return converted.map((item) => hoconToJs(item));
|
|
1134
1171
|
}
|
|
1135
1172
|
if (v.kind !== "array") throw new ConfigError(`expected array at ${path}`, path);
|
|
1136
|
-
return v.items.map(hoconToJs);
|
|
1173
|
+
return v.items.map((item) => hoconToJs(item));
|
|
1137
1174
|
}
|
|
1138
1175
|
has(path) {
|
|
1139
1176
|
return this.lookupNode(path) !== void 0;
|
|
@@ -1261,8 +1298,52 @@ var Config = class _Config {
|
|
|
1261
1298
|
originDescription: this._originDescription
|
|
1262
1299
|
});
|
|
1263
1300
|
}
|
|
1264
|
-
|
|
1265
|
-
|
|
1301
|
+
/**
|
|
1302
|
+
* The whole config as plain JS data: objects, arrays, strings, numbers,
|
|
1303
|
+
* booleans and `null`.
|
|
1304
|
+
*
|
|
1305
|
+
* Keys are reproduced exactly as the config carries them, `__proto__`,
|
|
1306
|
+
* `constructor` and `prototype` included: each is defined as an **own data
|
|
1307
|
+
* property**, so the returned object's prototype is always
|
|
1308
|
+
* `Object.prototype`, and this call writes nothing to the global one.
|
|
1309
|
+
*
|
|
1310
|
+
* ### Passing the result to other code
|
|
1311
|
+
*
|
|
1312
|
+
* A config file — especially a `.properties` file or an env namespace you
|
|
1313
|
+
* merged in — can therefore hand you an own `__proto__` key, and what happens
|
|
1314
|
+
* next depends entirely on how the consumer copies it:
|
|
1315
|
+
*
|
|
1316
|
+
* ```ts
|
|
1317
|
+
* const data = cfg.toObject()
|
|
1318
|
+
* { ...data } // safe — own properties are copied as data
|
|
1319
|
+
* structuredClone(data) // safe
|
|
1320
|
+
* Object.assign({}, data) // NOT safe — [[Set]] on the {} target hits
|
|
1321
|
+
* // Object.prototype's __proto__ setter: the key
|
|
1322
|
+
* // vanishes and config data becomes the result's
|
|
1323
|
+
* // prototype
|
|
1324
|
+
* deepMerge({}, data) // NOT safe — a naive recursive merge reads
|
|
1325
|
+
* // target['__proto__'], gets Object.prototype,
|
|
1326
|
+
* // and writes to it: global pollution
|
|
1327
|
+
* for (const k in data) … // prefer Object.entries / Object.keys
|
|
1328
|
+
* ```
|
|
1329
|
+
*
|
|
1330
|
+
* Those two hazards come from the **destination** object's prototype, so no
|
|
1331
|
+
* option here can disarm them — copy with spread or `structuredClone` instead.
|
|
1332
|
+
*
|
|
1333
|
+
* What `{ nullPrototype: true }` does give you is a result that inherits
|
|
1334
|
+
* nothing: every object comes back with a `null` prototype, so reading
|
|
1335
|
+
* `x.__proto__`, `x.constructor` or `x.toString` yields config data or
|
|
1336
|
+
* `undefined` rather than something from `Object.prototype`, and code that
|
|
1337
|
+
* merges *into* this object cannot climb out of it. Keys are all still
|
|
1338
|
+
* present. The trade-off is the usual one for prototype-less objects: not
|
|
1339
|
+
* `instanceof Object`, and no `hasOwnProperty` of their own.
|
|
1340
|
+
*
|
|
1341
|
+
* Numbers follow the JS number model, so an integer wider than 2^53 rounds
|
|
1342
|
+
* here even when the config holds it exactly — read those with
|
|
1343
|
+
* {@link Config.getString} instead.
|
|
1344
|
+
*/
|
|
1345
|
+
toObject(opts) {
|
|
1346
|
+
return hoconToJs(this.root, opts?.nullPrototype === true);
|
|
1266
1347
|
}
|
|
1267
1348
|
/**
|
|
1268
1349
|
* @internal Test-only: renders this resolved Config's value tree as canonical
|
|
@@ -1374,16 +1455,25 @@ function scalarToJs(raw, valueType) {
|
|
|
1374
1455
|
return raw;
|
|
1375
1456
|
}
|
|
1376
1457
|
}
|
|
1377
|
-
function hoconToJs(v) {
|
|
1458
|
+
function hoconToJs(v, nullPrototype = false) {
|
|
1378
1459
|
switch (v.kind) {
|
|
1379
1460
|
case "scalar":
|
|
1380
1461
|
return scalarToJs(v.raw, v.valueType);
|
|
1381
1462
|
case "array":
|
|
1382
|
-
return v.items.map(hoconToJs);
|
|
1463
|
+
return v.items.map((item) => hoconToJs(item, nullPrototype));
|
|
1383
1464
|
case "object": {
|
|
1384
|
-
const
|
|
1385
|
-
|
|
1386
|
-
|
|
1465
|
+
const entries = Array.from(v.fields, ([k, val]) => [k, hoconToJs(val, nullPrototype)]);
|
|
1466
|
+
if (nullPrototype) {
|
|
1467
|
+
const obj = /* @__PURE__ */ Object.create(null);
|
|
1468
|
+
for (const [k, val] of entries) Object.defineProperty(obj, k, {
|
|
1469
|
+
value: val,
|
|
1470
|
+
writable: true,
|
|
1471
|
+
enumerable: true,
|
|
1472
|
+
configurable: true
|
|
1473
|
+
});
|
|
1474
|
+
return obj;
|
|
1475
|
+
}
|
|
1476
|
+
return Object.fromEntries(entries);
|
|
1387
1477
|
}
|
|
1388
1478
|
}
|
|
1389
1479
|
}
|
|
@@ -1468,10 +1558,15 @@ function normalizeLeadingZeroNumber(raw) {
|
|
|
1468
1558
|
|
|
1469
1559
|
// src/value-factory.ts
|
|
1470
1560
|
function fromMap(values, originDescription) {
|
|
1471
|
-
const fields =
|
|
1561
|
+
const fields = guardStackDepth(
|
|
1562
|
+
() => coerceObject(values, ""),
|
|
1563
|
+
(msg) => depthError(`fromMap: ${msg}`)
|
|
1564
|
+
);
|
|
1472
1565
|
const root = { kind: "object", fields };
|
|
1473
1566
|
return Config._fromResolvedValue(root, { originDescription });
|
|
1474
1567
|
}
|
|
1568
|
+
var INT64_MAX = 9223372036854775807n;
|
|
1569
|
+
var INT64_MIN = -9223372036854775808n;
|
|
1475
1570
|
function coerceObject(obj, atPath) {
|
|
1476
1571
|
const fields = /* @__PURE__ */ new Map();
|
|
1477
1572
|
for (const [k, v] of Object.entries(obj)) {
|
|
@@ -1500,14 +1595,14 @@ function coerceValue(v, atPath) {
|
|
|
1500
1595
|
atPath
|
|
1501
1596
|
);
|
|
1502
1597
|
}
|
|
1503
|
-
return { kind: "scalar", raw: String(v), valueType: "number" };
|
|
1598
|
+
return { kind: "scalar", raw: Object.is(v, -0) ? "-0" : String(v), valueType: "number" };
|
|
1504
1599
|
}
|
|
1505
1600
|
case "boolean":
|
|
1506
1601
|
return { kind: "scalar", raw: v ? "true" : "false", valueType: "boolean" };
|
|
1507
1602
|
case "bigint": {
|
|
1508
|
-
if (v >
|
|
1603
|
+
if (v > INT64_MAX || v < INT64_MIN) {
|
|
1509
1604
|
throw new ConfigError(
|
|
1510
|
-
`fromMap:
|
|
1605
|
+
`fromMap: integer ${v} is out of int64 range (HOCON integers are int64; spec F0.5) at path "${atPath}"`,
|
|
1511
1606
|
atPath
|
|
1512
1607
|
);
|
|
1513
1608
|
}
|
|
@@ -1548,11 +1643,12 @@ function loadEnv(opts = {}) {
|
|
|
1548
1643
|
const value = source[name];
|
|
1549
1644
|
if (value === void 0) continue;
|
|
1550
1645
|
const path = toPath(name.slice(prefix.length));
|
|
1551
|
-
const
|
|
1646
|
+
const key = pathKey(path);
|
|
1647
|
+
const prev = seen.get(key);
|
|
1552
1648
|
if (prev !== void 0) {
|
|
1553
|
-
throw new ConfigError(`loadEnv: ${prev} and ${name} both map to "${path}"`, path);
|
|
1649
|
+
throw new ConfigError(`loadEnv: ${prev} and ${name} both map to "${path.join(".")}"`, path.join("."));
|
|
1554
1650
|
}
|
|
1555
|
-
seen.set(
|
|
1651
|
+
seen.set(key, name);
|
|
1556
1652
|
pairs.push([path, value]);
|
|
1557
1653
|
}
|
|
1558
1654
|
return fromMap(nestPairs(pairs), opts.originDescription ?? "environment variables");
|
|
@@ -1561,27 +1657,37 @@ function parseDotEnv(input, opts = {}) {
|
|
|
1561
1657
|
const origin = opts.originDescription ?? ".env";
|
|
1562
1658
|
const prefix = opts.prefix ?? "";
|
|
1563
1659
|
const pairs = [];
|
|
1564
|
-
const lines = input.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
|
|
1660
|
+
const lines = stripBom(input).replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
|
|
1565
1661
|
for (let i = 0; i < lines.length; i++) {
|
|
1566
1662
|
const raw = (lines[i] ?? "").trim();
|
|
1567
1663
|
if (raw === "" || raw.startsWith("#")) continue;
|
|
1568
|
-
const line =
|
|
1664
|
+
const line = stripExport(raw);
|
|
1569
1665
|
const eq = line.indexOf("=");
|
|
1570
1666
|
if (eq === -1) throw new ConfigError(`${origin}:${i + 1}: expected NAME=value`, "");
|
|
1571
1667
|
const name = line.slice(0, eq).trim();
|
|
1572
1668
|
if (name === "") throw new ConfigError(`${origin}:${i + 1}: empty variable name`, "");
|
|
1573
|
-
const value = dotEnvValue(line.slice(eq + 1).replace(/^[ \t]+/, ""), origin, i + 1, name);
|
|
1574
1669
|
if (!name.startsWith(prefix)) continue;
|
|
1670
|
+
checkName(name, origin, i + 1);
|
|
1671
|
+
const value = dotEnvValue(line.slice(eq + 1).replace(/^[ \t]+/, ""), origin, i + 1, name);
|
|
1575
1672
|
pairs.push([toPath(name.slice(prefix.length)), value]);
|
|
1576
1673
|
}
|
|
1577
1674
|
return fromMap(nestPairs(pairs), origin);
|
|
1578
1675
|
}
|
|
1579
1676
|
function toPath(name) {
|
|
1580
|
-
const segs = name.split(SEPARATOR).map(
|
|
1677
|
+
const segs = name.split(SEPARATOR).map(asciiLower);
|
|
1581
1678
|
if (segs.some((s) => s === "")) {
|
|
1582
1679
|
throw new ConfigError(`env: "${name}" produces an empty path segment`, name);
|
|
1583
1680
|
}
|
|
1584
|
-
|
|
1681
|
+
if (tooDeep(segs.length)) {
|
|
1682
|
+
throw new ConfigError(
|
|
1683
|
+
`env: "${name}" maps to a path ${segs.length} segments deep, over the limit of ${MAX_PATH_SEGMENTS}`,
|
|
1684
|
+
name
|
|
1685
|
+
);
|
|
1686
|
+
}
|
|
1687
|
+
return segs;
|
|
1688
|
+
}
|
|
1689
|
+
function asciiLower(s) {
|
|
1690
|
+
return s.replace(/[A-Z]/g, (c) => String.fromCharCode(c.charCodeAt(0) + 32));
|
|
1585
1691
|
}
|
|
1586
1692
|
function dotEnvValue(v, origin, line, name) {
|
|
1587
1693
|
const fail = (msg) => {
|
|
@@ -1623,6 +1729,20 @@ function dotEnvValue(v, origin, line, name) {
|
|
|
1623
1729
|
}
|
|
1624
1730
|
return trimmed;
|
|
1625
1731
|
}
|
|
1732
|
+
function stripExport(line) {
|
|
1733
|
+
const m = /^export[ \t]+/.exec(line);
|
|
1734
|
+
return m ? line.slice(m[0].length) : line;
|
|
1735
|
+
}
|
|
1736
|
+
function checkName(name, origin, line) {
|
|
1737
|
+
const bad = /\p{White_Space}|#/u.exec(name);
|
|
1738
|
+
if (bad) {
|
|
1739
|
+
const what = bad[0] === "#" ? "'#'" : "whitespace";
|
|
1740
|
+
throw new ConfigError(
|
|
1741
|
+
`${origin}:${line}: variable name "${name}" contains ${what}; the line is not NAME=value (spec F1.7)`,
|
|
1742
|
+
name
|
|
1743
|
+
);
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1626
1746
|
export {
|
|
1627
1747
|
loadEnv,
|
|
1628
1748
|
parseDotEnv
|
package/dist/adapters/jsonc.cjs
CHANGED
|
@@ -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
|
|
554
|
-
|
|
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
|
-
|
|
1275
|
-
|
|
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
|
|
1395
|
-
|
|
1396
|
-
|
|
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 =
|
|
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 >
|
|
1601
|
+
if (v > INT64_MAX || v < INT64_MIN) {
|
|
1519
1602
|
throw new ConfigError(
|
|
1520
|
-
`fromMap:
|
|
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,7 +1624,10 @@ function coerceValue(v, atPath) {
|
|
|
1541
1624
|
|
|
1542
1625
|
// src/adapters/jsonc.ts
|
|
1543
1626
|
function parseJsonc(input, originDescription) {
|
|
1544
|
-
const doc =
|
|
1627
|
+
const doc = guardStackDepth(
|
|
1628
|
+
() => JSON.parse(stripTrailingCommas(stripComments(stripBom(input))), bigIntReviver),
|
|
1629
|
+
(msg) => depthError(`jsonc: ${msg}`)
|
|
1630
|
+
);
|
|
1545
1631
|
if (doc === null || typeof doc !== "object" || Array.isArray(doc)) {
|
|
1546
1632
|
throw new ConfigError(
|
|
1547
1633
|
`jsonc: document root is ${Array.isArray(doc) ? "an array" : typeof doc}, but a config root must be an object (spec F0.3)`,
|
|
@@ -1550,6 +1636,50 @@ function parseJsonc(input, originDescription) {
|
|
|
1550
1636
|
}
|
|
1551
1637
|
return fromMap(doc, originDescription);
|
|
1552
1638
|
}
|
|
1639
|
+
function isIntegerLiteral(source) {
|
|
1640
|
+
return !/[.eE]/.test(source);
|
|
1641
|
+
}
|
|
1642
|
+
var HAS_SOURCE_TEXT_ACCESS = (() => {
|
|
1643
|
+
let seen;
|
|
1644
|
+
JSON.parse("1", function(_k, v, context) {
|
|
1645
|
+
seen = context?.source;
|
|
1646
|
+
return v;
|
|
1647
|
+
});
|
|
1648
|
+
return seen !== void 0;
|
|
1649
|
+
})();
|
|
1650
|
+
function bigIntReviver(key, value, context) {
|
|
1651
|
+
if (typeof value !== "number" || Number.isSafeInteger(value)) return value;
|
|
1652
|
+
const source = context?.source;
|
|
1653
|
+
if (source === void 0) {
|
|
1654
|
+
if (!Number.isInteger(value)) return value;
|
|
1655
|
+
throw new ConfigError(
|
|
1656
|
+
`jsonc: the integer at "${key || "<root>"}" needs more precision than a JS number holds, and this runtime does not expose JSON.parse source text (${HAS_SOURCE_TEXT_ACCESS ? "unexpected \u2014 the capability probe said otherwise" : "requires Node >= 22 or an engine with JSON.parse source access"}), so it cannot be read losslessly (spec F0.5)`,
|
|
1657
|
+
key
|
|
1658
|
+
);
|
|
1659
|
+
}
|
|
1660
|
+
if (!isIntegerLiteral(source)) return value;
|
|
1661
|
+
return BigInt(source);
|
|
1662
|
+
}
|
|
1663
|
+
function isLineBreak(c) {
|
|
1664
|
+
return c === "\n" || c === "\r";
|
|
1665
|
+
}
|
|
1666
|
+
function lineBreaksOf(span) {
|
|
1667
|
+
let out = "";
|
|
1668
|
+
for (let i = 0; i < span.length; i++) {
|
|
1669
|
+
const c = span[i];
|
|
1670
|
+
if (c === "\r") {
|
|
1671
|
+
if (span[i + 1] === "\n") {
|
|
1672
|
+
out += "\r\n";
|
|
1673
|
+
i++;
|
|
1674
|
+
} else {
|
|
1675
|
+
out += "\r";
|
|
1676
|
+
}
|
|
1677
|
+
} else if (c === "\n") {
|
|
1678
|
+
out += "\n";
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
return out;
|
|
1682
|
+
}
|
|
1553
1683
|
function stripComments(src) {
|
|
1554
1684
|
let out = "";
|
|
1555
1685
|
for (let i = 0; i < src.length; ) {
|
|
@@ -1561,13 +1691,13 @@ function stripComments(src) {
|
|
|
1561
1691
|
continue;
|
|
1562
1692
|
}
|
|
1563
1693
|
if (c === "/" && src[i + 1] === "/") {
|
|
1564
|
-
while (i < src.length && src[i]
|
|
1694
|
+
while (i < src.length && !isLineBreak(src[i])) i++;
|
|
1565
1695
|
continue;
|
|
1566
1696
|
}
|
|
1567
1697
|
if (c === "/" && src[i + 1] === "*") {
|
|
1568
1698
|
const end = src.indexOf("*/", i + 2);
|
|
1569
1699
|
if (end === -1) throw new ConfigError("jsonc: unterminated block comment", "");
|
|
1570
|
-
|
|
1700
|
+
out += lineBreaksOf(src.slice(i, end + 2)) || " ";
|
|
1571
1701
|
i = end + 2;
|
|
1572
1702
|
continue;
|
|
1573
1703
|
}
|
|
@@ -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 JSON with comments and trailing commas — the dialect VS Code and
|
|
@@ -10,15 +10,46 @@ import { C as Config } from '../config-PdhVgeLS.cjs';
|
|
|
10
10
|
* of its own.)
|
|
11
11
|
*
|
|
12
12
|
* Comments and trailing commas are removed and `JSON.parse` does the rest, so
|
|
13
|
-
* the accepted grammar is otherwise exactly the platform's JSON.
|
|
13
|
+
* the accepted grammar is otherwise exactly the platform's JSON. A comment is
|
|
14
|
+
* replaced by whitespace rather than deleted, so it separates the tokens around
|
|
15
|
+
* it and `{"a":1/*x*\/2}` is a syntax error rather than `{"a":12}` (F3.2).
|
|
14
16
|
*
|
|
15
|
-
*
|
|
17
|
+
* Integers are ingested losslessly (F0.5): a literal too wide for a JS `number`
|
|
18
|
+
* keeps its digits — `getString` returns them exactly — and one outside int64
|
|
19
|
+
* is refused instead of being rounded. `getNumber` and `toObject` still apply
|
|
20
|
+
* the JS number model, as they do for HOCON's own literals, so read large
|
|
21
|
+
* identifiers with `getString`.
|
|
22
|
+
*
|
|
23
|
+
* **An unpaired `\uXXXX` surrogate is accepted here and refused by go.hocon,
|
|
24
|
+
* py.hocon and rs.hocon** (F3.5). That is deliberate, and the same divergence
|
|
25
|
+
* the properties adapter already carries under F2.8: a JavaScript string is
|
|
26
|
+
* UTF-16, like Java's, so it holds a lone surrogate natively and round-trips it.
|
|
27
|
+
* A Go or Rust string cannot hold one at all — Go silently substituted U+FFFD
|
|
28
|
+
* until F3.5 — and a Python `str` can hold one but cannot encode it as UTF-8,
|
|
29
|
+
* so all three refuse rather than defer the failure. Refusing here would mean
|
|
30
|
+
* this spec overriding the host language rather than protecting the user; see
|
|
31
|
+
* S1.2.6 for the class.
|
|
32
|
+
*
|
|
33
|
+
* See the F3.x items in the format-ingestion mapping spec:
|
|
34
|
+
* https://github.com/o3co/xx.hocon/blob/main/docs/format-ingestion-mapping.md
|
|
16
35
|
*/
|
|
17
36
|
declare function parseJsonc(input: string, originDescription?: string): Config;
|
|
18
37
|
/**
|
|
19
38
|
* Remove `//` line comments and block comments, leaving string literals alone.
|
|
20
|
-
*
|
|
21
|
-
*
|
|
39
|
+
*
|
|
40
|
+
* F3.2: a comment is replaced by *whitespace*, never the empty string — it must
|
|
41
|
+
* keep separating the tokens around it, so a `1`, a block comment and a `2` stay
|
|
42
|
+
* two tokens and fail the JSON decode instead of silently reading as `12`.
|
|
43
|
+
* Newlines inside a removed span are kept so JSON.parse still reports useful
|
|
44
|
+
* positions; a span with no newline becomes a single space.
|
|
45
|
+
*
|
|
46
|
+
* A `//` comment ends at **LF or CR** (F3.2), and at nothing else. Stopping only
|
|
47
|
+
* at LF let a CR-terminated comment eat the rest of the line, and the
|
|
48
|
+
* trailing-comma pass then tidied the remains into valid JSON — so
|
|
49
|
+
* `{"a":1,//c\r"b":2,\n"c":3}` decoded as `{"a":1,"c":3}`, losing a key with no
|
|
50
|
+
* error at all. Stopping at U+2028/U+2029 would be the mirror-image mistake:
|
|
51
|
+
* ending a comment the dialect's own parser is still inside, so the document
|
|
52
|
+
* would mean something different here than in VS Code. See {@link isLineBreak}.
|
|
22
53
|
*/
|
|
23
54
|
declare function stripComments(src: string): string;
|
|
24
55
|
|