@o3co/ts.hocon 1.7.1 → 1.9.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.md +5 -4
- package/dist/{config-DCSLXkNY.d.cts → config-D27s3_aG.d.cts} +68 -1
- package/dist/{config-DCSLXkNY.d.ts → config-D27s3_aG.d.ts} +68 -1
- package/dist/index.cjs +105 -26
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +96 -26
- package/dist/zod.cjs +58 -26
- package/dist/zod.d.cts +1 -1
- package/dist/zod.d.ts +1 -1
- package/dist/zod.js +58 -26
- package/package.json +19 -18
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ A [Lightbend HOCON](https://github.com/lightbend/config/blob/main/HOCON.md) pars
|
|
|
14
14
|
|
|
15
15
|
> **Library stance:** ts.hocon is a HOCON config loader — its purpose is reading `.hocon` config files and providing typed access via the Config API (`getString`, `getNumber`, `getBoolean`, `getDuration`, `getBytes`, `toObject`). It is not a low-level parser API. Internal types like `HoconValue` may change between minor versions.
|
|
16
16
|
>
|
|
17
|
-
> **Cross-language conformance:** This implementation is tested against shared expected-JSON fixtures from [o3co/xx.hocon](https://github.com/o3co/xx.hocon) alongside [go.hocon](https://github.com/o3co/go.hocon)
|
|
17
|
+
> **Cross-language conformance:** This implementation is tested against shared expected-JSON fixtures from [o3co/xx.hocon](https://github.com/o3co/xx.hocon) alongside [go.hocon](https://github.com/o3co/go.hocon), [rs.hocon](https://github.com/o3co/rs.hocon), and [py.hocon](https://github.com/o3co/py.hocon) to ensure all four implementations meet the same Lightbend HOCON specification.
|
|
18
18
|
|
|
19
19
|
---
|
|
20
20
|
|
|
@@ -245,7 +245,7 @@ c.getBytes('max-size') // 536870912 (bytes)
|
|
|
245
245
|
c.getBytes('max-size', 'MiB') // 512
|
|
246
246
|
```
|
|
247
247
|
|
|
248
|
-
Supported duration units: `ns`, `us`, `ms`, `s`, `m`, `h`, `d` (and long forms like `seconds`, `minutes`).
|
|
248
|
+
Supported duration units: `ns`, `us`, `ms`, `s`, `m`, `h`, `d` (and long forms like `seconds`, `minutes`). Duration unit names are case-sensitive and must be lowercase (HOCON spec). Byte units are more case-tolerant: the canonical forms below plus lowercase aliases (`kb`, `kib`, …), any-case long forms (`megabytes`), and single-letter powers-of-two in both cases (`K`/`k`, per Lightbend).
|
|
249
249
|
Supported byte units: `B`, `KB`/`KiB`, `MB`/`MiB`, `GB`/`GiB`, `TB`/`TiB` (and long forms like `megabytes`, `mebibytes`).
|
|
250
250
|
|
|
251
251
|
## Spec Compliance
|
|
@@ -386,9 +386,10 @@ const config = parseWithSchema(hoconInput, schema) // fails fast on startup
|
|
|
386
386
|
|---------|----------|----------|-------------|
|
|
387
387
|
| [go.hocon](https://github.com/o3co/go.hocon) | Go | [pkg.go.dev](https://pkg.go.dev/github.com/o3co/go.hocon) | HOCON parser for Go |
|
|
388
388
|
| [rs.hocon](https://github.com/o3co/rs.hocon) | Rust | [crates.io](https://crates.io/crates/o3co-hocon) | HOCON parser for Rust |
|
|
389
|
+
| [py.hocon](https://github.com/o3co/py.hocon) | Python | [PyPI](https://pypi.org/project/hocon-parser/) | HOCON parser for Python |
|
|
389
390
|
| [hocon2](https://github.com/o3co/hocon2) | Go | [pkg.go.dev](https://pkg.go.dev/github.com/o3co/hocon2) | HOCON → JSON/YAML/TOML/Properties CLI |
|
|
390
391
|
|
|
391
|
-
The
|
|
392
|
+
The four parser implementations ([ts.hocon](https://github.com/o3co/ts.hocon), [rs.hocon](https://github.com/o3co/rs.hocon), [go.hocon](https://github.com/o3co/go.hocon), [py.hocon](https://github.com/o3co/py.hocon)) are all tracked against the same Lightbend HOCON spec — see the [cross-impl roll-up](https://github.com/o3co/xx.hocon/blob/main/docs/compliance-matrix.md) for per-impl conformance rates.
|
|
392
393
|
|
|
393
394
|
## Known Limitations
|
|
394
395
|
|
|
@@ -402,7 +403,7 @@ The three parser implementations ([ts.hocon](https://github.com/o3co/ts.hocon),
|
|
|
402
403
|
|
|
403
404
|
When parsing untrusted HOCON input, be aware of:
|
|
404
405
|
|
|
405
|
-
- **Path traversal in includes:** `include
|
|
406
|
+
- **Path traversal in includes:** a relative `include` path resolves against `baseDir` and can climb out of it with `..` segments to reach sensitive files such as `/etc/passwd`. Use a custom `readFileSync`/`readFile` that validates paths if parsing untrusted input.
|
|
406
407
|
- **Input size:** The parser has no built-in input size limit. For untrusted input, validate size before calling `parse()`.
|
|
407
408
|
- **Include depth:** Limited to 50 levels to prevent stack overflow from deep include chains.
|
|
408
409
|
|
|
@@ -13,6 +13,59 @@ type HoconValue = {
|
|
|
13
13
|
raw: string;
|
|
14
14
|
valueType: ScalarValueType;
|
|
15
15
|
};
|
|
16
|
+
/**
|
|
17
|
+
* Deeply-immutable view of a {@link HoconValue}. `Config.getValue` returns this
|
|
18
|
+
* so callers cannot mutate the parsed configuration tree (the live nodes are
|
|
19
|
+
* shared, not cloned). `Map`/array containers become `ReadonlyMap`/`readonly[]`
|
|
20
|
+
* recursively, so `.set` / `.push` / element assignment are compile errors.
|
|
21
|
+
*
|
|
22
|
+
* `HoconValue` is structurally assignable to `ReadonlyHoconValue`, so internal
|
|
23
|
+
* code holding a mutable node can return it here, and the accessor functions
|
|
24
|
+
* below accept both forms.
|
|
25
|
+
*/
|
|
26
|
+
type ReadonlyHoconValue = {
|
|
27
|
+
readonly kind: 'object';
|
|
28
|
+
readonly fields: ReadonlyMap<string, ReadonlyHoconValue>;
|
|
29
|
+
} | {
|
|
30
|
+
readonly kind: 'array';
|
|
31
|
+
readonly items: readonly ReadonlyHoconValue[];
|
|
32
|
+
} | {
|
|
33
|
+
readonly kind: 'scalar';
|
|
34
|
+
readonly raw: string;
|
|
35
|
+
readonly valueType: ScalarValueType;
|
|
36
|
+
};
|
|
37
|
+
/** Scalar string only (strict): non-string scalars and containers → undefined. */
|
|
38
|
+
declare function asString(v: ReadonlyHoconValue): string | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* Scalar coerced to a number via {@link coerceNumber} (lenient, matching the
|
|
41
|
+
* `getNumber` getter: a numeric-looking string scalar coerces too). TS has a
|
|
42
|
+
* single `number` type, so this unifies rs's `as_i64` + `as_f64`.
|
|
43
|
+
*/
|
|
44
|
+
declare function asNumber(v: ReadonlyHoconValue): number | undefined;
|
|
45
|
+
/** Scalar coerced to a boolean via {@link coerceBoolean} (true/yes/on, false/no/off). */
|
|
46
|
+
declare function asBoolean(v: ReadonlyHoconValue): boolean | undefined;
|
|
47
|
+
/** The object's fields, or undefined if not an object. */
|
|
48
|
+
declare function asObject(v: ReadonlyHoconValue): ReadonlyMap<string, ReadonlyHoconValue> | undefined;
|
|
49
|
+
/** The array's items, or undefined if not an array. */
|
|
50
|
+
declare function asArray(v: ReadonlyHoconValue): readonly ReadonlyHoconValue[] | undefined;
|
|
51
|
+
/** Type guard: narrows to the object variant. */
|
|
52
|
+
declare function isObject(v: ReadonlyHoconValue): v is {
|
|
53
|
+
readonly kind: 'object';
|
|
54
|
+
readonly fields: ReadonlyMap<string, ReadonlyHoconValue>;
|
|
55
|
+
};
|
|
56
|
+
/** Type guard: narrows to the array variant. */
|
|
57
|
+
declare function isArray(v: ReadonlyHoconValue): v is {
|
|
58
|
+
readonly kind: 'array';
|
|
59
|
+
readonly items: readonly ReadonlyHoconValue[];
|
|
60
|
+
};
|
|
61
|
+
/** Type guard: narrows to the scalar variant. */
|
|
62
|
+
declare function isScalar(v: ReadonlyHoconValue): v is {
|
|
63
|
+
readonly kind: 'scalar';
|
|
64
|
+
readonly raw: string;
|
|
65
|
+
readonly valueType: ScalarValueType;
|
|
66
|
+
};
|
|
67
|
+
/** True for a null scalar. */
|
|
68
|
+
declare function isNull(v: ReadonlyHoconValue): boolean;
|
|
16
69
|
|
|
17
70
|
interface Segment {
|
|
18
71
|
readonly text: string;
|
|
@@ -212,6 +265,20 @@ declare class Config {
|
|
|
212
265
|
resolveOpts: ResolveOptions$1;
|
|
213
266
|
}): Config;
|
|
214
267
|
get(path: string): unknown;
|
|
268
|
+
/**
|
|
269
|
+
* Returns the raw {@link HoconValue} node at `path` as a deeply-immutable
|
|
270
|
+
* {@link ReadonlyHoconValue} view, or `undefined` if the path is absent. Unlike
|
|
271
|
+
* `get` (which decodes to a plain JS value), this exposes the value tree for
|
|
272
|
+
* structural introspection via the `value.ts` accessors (`asString`, `asObject`,
|
|
273
|
+
* `isScalar`, …).
|
|
274
|
+
*
|
|
275
|
+
* `getValue('')` returns the root object node.
|
|
276
|
+
*
|
|
277
|
+
* Throws {@link NotResolvedError} when the node (or its subtree) is unresolved:
|
|
278
|
+
* the `HoconValue` union has no placeholder variant, so an unresolved value
|
|
279
|
+
* cannot be represented. This matches `getConfig` / `getList`.
|
|
280
|
+
*/
|
|
281
|
+
getValue(path: string): ReadonlyHoconValue | undefined;
|
|
215
282
|
getString(path: string): string;
|
|
216
283
|
getNumber(path: string): number;
|
|
217
284
|
getBoolean(path: string): boolean;
|
|
@@ -273,4 +340,4 @@ declare class Config {
|
|
|
273
340
|
private requireScalar;
|
|
274
341
|
}
|
|
275
342
|
|
|
276
|
-
export { type ByteUnit as B, Config as C, type DurationUnit as D, type HoconValue as H, type PackageResolver as P, type
|
|
343
|
+
export { type ByteUnit as B, Config as C, type DurationUnit as D, type HoconValue as H, type PackageResolver as P, type ReadonlyHoconValue as R, type ScalarValueType as S, type ParseOptions as a, type ResolveOptions as b, asArray as c, asBoolean as d, asNumber as e, asObject as f, asString as g, defaultParseOptions as h, defaultResolveOptions as i, isArray as j, isNull as k, isObject as l, isScalar as m, parseAsync as n, parseFile as o, parse as p, parseFileAsync as q, parseFileWithOptions as r, parseString as s, parseStringWithOptions as t };
|
|
@@ -13,6 +13,59 @@ type HoconValue = {
|
|
|
13
13
|
raw: string;
|
|
14
14
|
valueType: ScalarValueType;
|
|
15
15
|
};
|
|
16
|
+
/**
|
|
17
|
+
* Deeply-immutable view of a {@link HoconValue}. `Config.getValue` returns this
|
|
18
|
+
* so callers cannot mutate the parsed configuration tree (the live nodes are
|
|
19
|
+
* shared, not cloned). `Map`/array containers become `ReadonlyMap`/`readonly[]`
|
|
20
|
+
* recursively, so `.set` / `.push` / element assignment are compile errors.
|
|
21
|
+
*
|
|
22
|
+
* `HoconValue` is structurally assignable to `ReadonlyHoconValue`, so internal
|
|
23
|
+
* code holding a mutable node can return it here, and the accessor functions
|
|
24
|
+
* below accept both forms.
|
|
25
|
+
*/
|
|
26
|
+
type ReadonlyHoconValue = {
|
|
27
|
+
readonly kind: 'object';
|
|
28
|
+
readonly fields: ReadonlyMap<string, ReadonlyHoconValue>;
|
|
29
|
+
} | {
|
|
30
|
+
readonly kind: 'array';
|
|
31
|
+
readonly items: readonly ReadonlyHoconValue[];
|
|
32
|
+
} | {
|
|
33
|
+
readonly kind: 'scalar';
|
|
34
|
+
readonly raw: string;
|
|
35
|
+
readonly valueType: ScalarValueType;
|
|
36
|
+
};
|
|
37
|
+
/** Scalar string only (strict): non-string scalars and containers → undefined. */
|
|
38
|
+
declare function asString(v: ReadonlyHoconValue): string | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* Scalar coerced to a number via {@link coerceNumber} (lenient, matching the
|
|
41
|
+
* `getNumber` getter: a numeric-looking string scalar coerces too). TS has a
|
|
42
|
+
* single `number` type, so this unifies rs's `as_i64` + `as_f64`.
|
|
43
|
+
*/
|
|
44
|
+
declare function asNumber(v: ReadonlyHoconValue): number | undefined;
|
|
45
|
+
/** Scalar coerced to a boolean via {@link coerceBoolean} (true/yes/on, false/no/off). */
|
|
46
|
+
declare function asBoolean(v: ReadonlyHoconValue): boolean | undefined;
|
|
47
|
+
/** The object's fields, or undefined if not an object. */
|
|
48
|
+
declare function asObject(v: ReadonlyHoconValue): ReadonlyMap<string, ReadonlyHoconValue> | undefined;
|
|
49
|
+
/** The array's items, or undefined if not an array. */
|
|
50
|
+
declare function asArray(v: ReadonlyHoconValue): readonly ReadonlyHoconValue[] | undefined;
|
|
51
|
+
/** Type guard: narrows to the object variant. */
|
|
52
|
+
declare function isObject(v: ReadonlyHoconValue): v is {
|
|
53
|
+
readonly kind: 'object';
|
|
54
|
+
readonly fields: ReadonlyMap<string, ReadonlyHoconValue>;
|
|
55
|
+
};
|
|
56
|
+
/** Type guard: narrows to the array variant. */
|
|
57
|
+
declare function isArray(v: ReadonlyHoconValue): v is {
|
|
58
|
+
readonly kind: 'array';
|
|
59
|
+
readonly items: readonly ReadonlyHoconValue[];
|
|
60
|
+
};
|
|
61
|
+
/** Type guard: narrows to the scalar variant. */
|
|
62
|
+
declare function isScalar(v: ReadonlyHoconValue): v is {
|
|
63
|
+
readonly kind: 'scalar';
|
|
64
|
+
readonly raw: string;
|
|
65
|
+
readonly valueType: ScalarValueType;
|
|
66
|
+
};
|
|
67
|
+
/** True for a null scalar. */
|
|
68
|
+
declare function isNull(v: ReadonlyHoconValue): boolean;
|
|
16
69
|
|
|
17
70
|
interface Segment {
|
|
18
71
|
readonly text: string;
|
|
@@ -212,6 +265,20 @@ declare class Config {
|
|
|
212
265
|
resolveOpts: ResolveOptions$1;
|
|
213
266
|
}): Config;
|
|
214
267
|
get(path: string): unknown;
|
|
268
|
+
/**
|
|
269
|
+
* Returns the raw {@link HoconValue} node at `path` as a deeply-immutable
|
|
270
|
+
* {@link ReadonlyHoconValue} view, or `undefined` if the path is absent. Unlike
|
|
271
|
+
* `get` (which decodes to a plain JS value), this exposes the value tree for
|
|
272
|
+
* structural introspection via the `value.ts` accessors (`asString`, `asObject`,
|
|
273
|
+
* `isScalar`, …).
|
|
274
|
+
*
|
|
275
|
+
* `getValue('')` returns the root object node.
|
|
276
|
+
*
|
|
277
|
+
* Throws {@link NotResolvedError} when the node (or its subtree) is unresolved:
|
|
278
|
+
* the `HoconValue` union has no placeholder variant, so an unresolved value
|
|
279
|
+
* cannot be represented. This matches `getConfig` / `getList`.
|
|
280
|
+
*/
|
|
281
|
+
getValue(path: string): ReadonlyHoconValue | undefined;
|
|
215
282
|
getString(path: string): string;
|
|
216
283
|
getNumber(path: string): number;
|
|
217
284
|
getBoolean(path: string): boolean;
|
|
@@ -273,4 +340,4 @@ declare class Config {
|
|
|
273
340
|
private requireScalar;
|
|
274
341
|
}
|
|
275
342
|
|
|
276
|
-
export { type ByteUnit as B, Config as C, type DurationUnit as D, type HoconValue as H, type PackageResolver as P, type
|
|
343
|
+
export { type ByteUnit as B, Config as C, type DurationUnit as D, type HoconValue as H, type PackageResolver as P, type ReadonlyHoconValue as R, type ScalarValueType as S, type ParseOptions as a, type ResolveOptions as b, asArray as c, asBoolean as d, asNumber as e, asObject as f, asString as g, defaultParseOptions as h, defaultResolveOptions as i, isArray as j, isNull as k, isObject as l, isScalar as m, parseAsync as n, parseFile as o, parse as p, parseFileAsync as q, parseFileWithOptions as r, parseString as s, parseStringWithOptions as t };
|
package/dist/index.cjs
CHANGED
|
@@ -36,10 +36,19 @@ __export(src_exports, {
|
|
|
36
36
|
PackageLookupError: () => PackageLookupError,
|
|
37
37
|
ParseError: () => ParseError,
|
|
38
38
|
ResolveError: () => ResolveError,
|
|
39
|
+
asArray: () => asArray,
|
|
40
|
+
asBoolean: () => asBoolean,
|
|
41
|
+
asNumber: () => asNumber,
|
|
42
|
+
asObject: () => asObject,
|
|
43
|
+
asString: () => asString,
|
|
39
44
|
defaultParseOptions: () => defaultParseOptions,
|
|
40
45
|
defaultResolveOptions: () => defaultResolveOptions,
|
|
41
46
|
empty: () => empty,
|
|
42
47
|
fromMap: () => fromMap,
|
|
48
|
+
isArray: () => isArray,
|
|
49
|
+
isNull: () => isNull,
|
|
50
|
+
isObject: () => isObject,
|
|
51
|
+
isScalar: () => isScalar,
|
|
43
52
|
parse: () => parse,
|
|
44
53
|
parseAsync: () => parseAsync,
|
|
45
54
|
parseFile: () => parseFile,
|
|
@@ -132,7 +141,7 @@ function parseDuration(value, outputUnit = "ms") {
|
|
|
132
141
|
if (i === 0) return NaN;
|
|
133
142
|
const num = Number(trimmed.slice(0, i));
|
|
134
143
|
if (Number.isNaN(num)) return NaN;
|
|
135
|
-
const unit = trimHoconWs(trimmed.slice(i))
|
|
144
|
+
const unit = trimHoconWs(trimmed.slice(i));
|
|
136
145
|
const divisor = OUTPUT_DURATION_UNITS[outputUnit];
|
|
137
146
|
if (divisor === void 0) return NaN;
|
|
138
147
|
if (unit === "") {
|
|
@@ -692,20 +701,6 @@ function isFileNotFoundError(e) {
|
|
|
692
701
|
var import_node_module = require("module");
|
|
693
702
|
var nodePath = __toESM(require("path"), 1);
|
|
694
703
|
|
|
695
|
-
// src/internal/parser/empty-check.ts
|
|
696
|
-
function hasContentTokens(tokens) {
|
|
697
|
-
return tokens.some((t) => t.kind !== "eof" && t.kind !== "newline");
|
|
698
|
-
}
|
|
699
|
-
function assertNonEmptyDocument(tokens, sourceDescriptor) {
|
|
700
|
-
if (!hasContentTokens(tokens)) {
|
|
701
|
-
throw new ParseError(
|
|
702
|
-
`empty file is not a valid HOCON document (HOCON.md L130): ${sourceDescriptor}`,
|
|
703
|
-
1,
|
|
704
|
-
1
|
|
705
|
-
);
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
|
|
709
704
|
// src/internal/lexer/lexer.ts
|
|
710
705
|
function isHoconWhitespace(ch) {
|
|
711
706
|
const cp = ch.codePointAt(0) ?? -1;
|
|
@@ -1151,6 +1146,16 @@ var Parser = class {
|
|
|
1151
1146
|
}
|
|
1152
1147
|
return { kind: "object", fields: allFields, pos: first.pos };
|
|
1153
1148
|
}
|
|
1149
|
+
if (t.kind === "lbracket") {
|
|
1150
|
+
this.advance();
|
|
1151
|
+
const arr = this.parseArray();
|
|
1152
|
+
this.skip("newline");
|
|
1153
|
+
const remaining = this.peek();
|
|
1154
|
+
if (remaining.kind !== "eof") {
|
|
1155
|
+
throw new ParseError(`Unexpected token '${remaining.value}' after root array`, remaining.line, remaining.col);
|
|
1156
|
+
}
|
|
1157
|
+
return { ...arr, pos: { line: t.line, col: t.col } };
|
|
1158
|
+
}
|
|
1154
1159
|
return this.parseObject(false);
|
|
1155
1160
|
}
|
|
1156
1161
|
peek(offset = 0) {
|
|
@@ -1590,6 +1595,16 @@ var _require = (0, import_node_module.createRequire)(import_meta.url);
|
|
|
1590
1595
|
function isPnpPath(p) {
|
|
1591
1596
|
return p.includes(".yarn/cache") || p.includes(".pnp.cjs") || p.includes(".pnp.loader");
|
|
1592
1597
|
}
|
|
1598
|
+
function assertObjectRootedInclude(ast, sourcePath) {
|
|
1599
|
+
if (ast.kind === "array") {
|
|
1600
|
+
throw new ResolveError(
|
|
1601
|
+
`included file has array at file root \u2014 an included file must contain an object, not an array (HOCON.md L993-994): ${sourcePath}`,
|
|
1602
|
+
sourcePath,
|
|
1603
|
+
ast.pos.line,
|
|
1604
|
+
ast.pos.col
|
|
1605
|
+
);
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1593
1608
|
function validatePackageFile(file, identifier) {
|
|
1594
1609
|
if (file.length === 0) {
|
|
1595
1610
|
throw new ParseError(`include package("${identifier}", ...): file argument must be non-empty`, 0, 0);
|
|
@@ -1769,10 +1784,9 @@ var IncludeLoader = class {
|
|
|
1769
1784
|
const resolver = packageResolver ?? ((id, f, includingFile, bDir) => defaultPackageResolver(id, f, includingFile, bDir, resolveFrom));
|
|
1770
1785
|
const resolvedPath = resolver(identifier, file, void 0, baseDir);
|
|
1771
1786
|
const content = readFileSync2(resolvedPath);
|
|
1772
|
-
if (content.length === 0) return makeResObj();
|
|
1773
1787
|
const tokens = tokenize(content);
|
|
1774
|
-
assertNonEmptyDocument(tokens, resolvedPath);
|
|
1775
1788
|
const ast = parseTokens(tokens);
|
|
1789
|
+
assertObjectRootedInclude(ast, resolvedPath);
|
|
1776
1790
|
return this.onBuildResObj(ast, {
|
|
1777
1791
|
...this.opts,
|
|
1778
1792
|
baseDir: nodePath.dirname(resolvedPath),
|
|
@@ -1802,10 +1816,9 @@ var IncludeLoader = class {
|
|
|
1802
1816
|
const resolvedPath = resolver(identifier, file, void 0, baseDir);
|
|
1803
1817
|
const read = readFile ? async (p) => readFile(p) : async (p) => readFileSync2(p);
|
|
1804
1818
|
const content = await read(resolvedPath);
|
|
1805
|
-
if (content.length === 0) return makeResObj();
|
|
1806
1819
|
const tokens = tokenize(content);
|
|
1807
|
-
assertNonEmptyDocument(tokens, resolvedPath);
|
|
1808
1820
|
const ast = parseTokens(tokens);
|
|
1821
|
+
assertObjectRootedInclude(ast, resolvedPath);
|
|
1809
1822
|
return this.onBuildResObjAsync(ast, {
|
|
1810
1823
|
...this.opts,
|
|
1811
1824
|
baseDir: nodePath.dirname(resolvedPath),
|
|
@@ -1828,10 +1841,8 @@ var IncludeLoader = class {
|
|
|
1828
1841
|
return hoconValueToResObj(propertiesToHoconValue(content));
|
|
1829
1842
|
}
|
|
1830
1843
|
const tokens = tokenize(content);
|
|
1831
|
-
if (!hasContentTokens(tokens)) {
|
|
1832
|
-
return makeResObj();
|
|
1833
|
-
}
|
|
1834
1844
|
const ast = parseTokens(tokens);
|
|
1845
|
+
assertObjectRootedInclude(ast, candidate);
|
|
1835
1846
|
return this.onBuildResObj(ast, {
|
|
1836
1847
|
...this.opts,
|
|
1837
1848
|
baseDir: nodePath.dirname(candidate),
|
|
@@ -1855,10 +1866,8 @@ var IncludeLoader = class {
|
|
|
1855
1866
|
return hoconValueToResObj(propertiesToHoconValue(content));
|
|
1856
1867
|
}
|
|
1857
1868
|
const tokens = tokenize(content);
|
|
1858
|
-
if (!hasContentTokens(tokens)) {
|
|
1859
|
-
return makeResObj();
|
|
1860
|
-
}
|
|
1861
1869
|
const ast = parseTokens(tokens);
|
|
1870
|
+
assertObjectRootedInclude(ast, candidate);
|
|
1862
1871
|
return this.onBuildResObjAsync(ast, {
|
|
1863
1872
|
...this.opts,
|
|
1864
1873
|
baseDir: nodePath.dirname(candidate),
|
|
@@ -2601,6 +2610,32 @@ var Config = class _Config {
|
|
|
2601
2610
|
if (v === void 0) return void 0;
|
|
2602
2611
|
return hoconToJs(v);
|
|
2603
2612
|
}
|
|
2613
|
+
/**
|
|
2614
|
+
* Returns the raw {@link HoconValue} node at `path` as a deeply-immutable
|
|
2615
|
+
* {@link ReadonlyHoconValue} view, or `undefined` if the path is absent. Unlike
|
|
2616
|
+
* `get` (which decodes to a plain JS value), this exposes the value tree for
|
|
2617
|
+
* structural introspection via the `value.ts` accessors (`asString`, `asObject`,
|
|
2618
|
+
* `isScalar`, …).
|
|
2619
|
+
*
|
|
2620
|
+
* `getValue('')` returns the root object node.
|
|
2621
|
+
*
|
|
2622
|
+
* Throws {@link NotResolvedError} when the node (or its subtree) is unresolved:
|
|
2623
|
+
* the `HoconValue` union has no placeholder variant, so an unresolved value
|
|
2624
|
+
* cannot be represented. This matches `getConfig` / `getList`.
|
|
2625
|
+
*/
|
|
2626
|
+
getValue(path2) {
|
|
2627
|
+
const v = this.lookupNode(path2);
|
|
2628
|
+
if (v === void 0) {
|
|
2629
|
+
if (!this._resolved && this._resObjRootSubtreeHasPlaceholders(path2)) {
|
|
2630
|
+
throw new NotResolvedError(path2);
|
|
2631
|
+
}
|
|
2632
|
+
return void 0;
|
|
2633
|
+
}
|
|
2634
|
+
if (v.kind !== "scalar" && !this._resolved && this._resObjRootSubtreeHasPlaceholders(path2)) {
|
|
2635
|
+
throw new NotResolvedError(path2);
|
|
2636
|
+
}
|
|
2637
|
+
return v;
|
|
2638
|
+
}
|
|
2604
2639
|
getString(path2) {
|
|
2605
2640
|
const v = this.requireScalar(path2);
|
|
2606
2641
|
if (v.valueType === "null") {
|
|
@@ -3024,8 +3059,14 @@ async function defaultReadFile(filePath) {
|
|
|
3024
3059
|
}
|
|
3025
3060
|
function buildResolveContext(input, opts) {
|
|
3026
3061
|
const tokens = tokenize(input);
|
|
3027
|
-
assertNonEmptyDocument(tokens, "input");
|
|
3028
3062
|
const ast = parseTokens(tokens);
|
|
3063
|
+
if (ast.kind === "array") {
|
|
3064
|
+
const origin = opts.originDescription ?? "input";
|
|
3065
|
+
throw new ConfigError(
|
|
3066
|
+
`${origin}: ${ast.pos.line}:${ast.pos.col}: document has type array rather than object at file root (HOCON.md L989-991); the Config API requires an object root`,
|
|
3067
|
+
""
|
|
3068
|
+
);
|
|
3069
|
+
}
|
|
3029
3070
|
const resolveOpts = {
|
|
3030
3071
|
env: getEnv(opts),
|
|
3031
3072
|
baseDir: opts.baseDir,
|
|
@@ -3108,6 +3149,35 @@ async function parseFileAsync(filePath, opts = {}) {
|
|
|
3108
3149
|
});
|
|
3109
3150
|
}
|
|
3110
3151
|
|
|
3152
|
+
// src/value.ts
|
|
3153
|
+
function asString(v) {
|
|
3154
|
+
return v.kind === "scalar" && v.valueType === "string" ? v.raw : void 0;
|
|
3155
|
+
}
|
|
3156
|
+
function asNumber(v) {
|
|
3157
|
+
return v.kind === "scalar" ? coerceNumber(v.raw) : void 0;
|
|
3158
|
+
}
|
|
3159
|
+
function asBoolean(v) {
|
|
3160
|
+
return v.kind === "scalar" ? coerceBoolean(v.raw) : void 0;
|
|
3161
|
+
}
|
|
3162
|
+
function asObject(v) {
|
|
3163
|
+
return v.kind === "object" ? v.fields : void 0;
|
|
3164
|
+
}
|
|
3165
|
+
function asArray(v) {
|
|
3166
|
+
return v.kind === "array" ? v.items : void 0;
|
|
3167
|
+
}
|
|
3168
|
+
function isObject(v) {
|
|
3169
|
+
return v.kind === "object";
|
|
3170
|
+
}
|
|
3171
|
+
function isArray(v) {
|
|
3172
|
+
return v.kind === "array";
|
|
3173
|
+
}
|
|
3174
|
+
function isScalar(v) {
|
|
3175
|
+
return v.kind === "scalar";
|
|
3176
|
+
}
|
|
3177
|
+
function isNull(v) {
|
|
3178
|
+
return v.kind === "scalar" && v.valueType === "null";
|
|
3179
|
+
}
|
|
3180
|
+
|
|
3111
3181
|
// src/value-factory.ts
|
|
3112
3182
|
function fromMap(values, originDescription) {
|
|
3113
3183
|
const fields = coerceObject(values, "");
|
|
@@ -3182,10 +3252,19 @@ function coerceValue(v, atPath) {
|
|
|
3182
3252
|
PackageLookupError,
|
|
3183
3253
|
ParseError,
|
|
3184
3254
|
ResolveError,
|
|
3255
|
+
asArray,
|
|
3256
|
+
asBoolean,
|
|
3257
|
+
asNumber,
|
|
3258
|
+
asObject,
|
|
3259
|
+
asString,
|
|
3185
3260
|
defaultParseOptions,
|
|
3186
3261
|
defaultResolveOptions,
|
|
3187
3262
|
empty,
|
|
3188
3263
|
fromMap,
|
|
3264
|
+
isArray,
|
|
3265
|
+
isNull,
|
|
3266
|
+
isObject,
|
|
3267
|
+
isScalar,
|
|
3189
3268
|
parse,
|
|
3190
3269
|
parseAsync,
|
|
3191
3270
|
parseFile,
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { C as Config } from './config-
|
|
2
|
-
export { B as ByteUnit, D as DurationUnit, H as HoconValue, P as PackageResolver, a as ParseOptions, R as ResolveOptions, S as ScalarValueType, d as defaultParseOptions,
|
|
1
|
+
import { C as Config } from './config-D27s3_aG.cjs';
|
|
2
|
+
export { B as ByteUnit, D as DurationUnit, H as HoconValue, P as PackageResolver, a as ParseOptions, R as ReadonlyHoconValue, b as ResolveOptions, S as ScalarValueType, c as asArray, d as asBoolean, e as asNumber, f as asObject, g as asString, h as defaultParseOptions, i as defaultResolveOptions, j as isArray, k as isNull, l as isObject, m as isScalar, p as parse, n as parseAsync, o as parseFile, q as parseFileAsync, r as parseFileWithOptions, s as parseString, t as parseStringWithOptions } from './config-D27s3_aG.cjs';
|
|
3
3
|
|
|
4
4
|
declare class ParseError extends Error {
|
|
5
5
|
readonly line: number;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { C as Config } from './config-
|
|
2
|
-
export { B as ByteUnit, D as DurationUnit, H as HoconValue, P as PackageResolver, a as ParseOptions, R as ResolveOptions, S as ScalarValueType, d as defaultParseOptions,
|
|
1
|
+
import { C as Config } from './config-D27s3_aG.js';
|
|
2
|
+
export { B as ByteUnit, D as DurationUnit, H as HoconValue, P as PackageResolver, a as ParseOptions, R as ReadonlyHoconValue, b as ResolveOptions, S as ScalarValueType, c as asArray, d as asBoolean, e as asNumber, f as asObject, g as asString, h as defaultParseOptions, i as defaultResolveOptions, j as isArray, k as isNull, l as isObject, m as isScalar, p as parse, n as parseAsync, o as parseFile, q as parseFileAsync, r as parseFileWithOptions, s as parseString, t as parseStringWithOptions } from './config-D27s3_aG.js';
|
|
3
3
|
|
|
4
4
|
declare class ParseError extends Error {
|
|
5
5
|
readonly line: number;
|
package/dist/index.js
CHANGED
|
@@ -80,7 +80,7 @@ function parseDuration(value, outputUnit = "ms") {
|
|
|
80
80
|
if (i === 0) return NaN;
|
|
81
81
|
const num = Number(trimmed.slice(0, i));
|
|
82
82
|
if (Number.isNaN(num)) return NaN;
|
|
83
|
-
const unit = trimHoconWs(trimmed.slice(i))
|
|
83
|
+
const unit = trimHoconWs(trimmed.slice(i));
|
|
84
84
|
const divisor = OUTPUT_DURATION_UNITS[outputUnit];
|
|
85
85
|
if (divisor === void 0) return NaN;
|
|
86
86
|
if (unit === "") {
|
|
@@ -640,20 +640,6 @@ function isFileNotFoundError(e) {
|
|
|
640
640
|
import { createRequire } from "module";
|
|
641
641
|
import * as nodePath from "path";
|
|
642
642
|
|
|
643
|
-
// src/internal/parser/empty-check.ts
|
|
644
|
-
function hasContentTokens(tokens) {
|
|
645
|
-
return tokens.some((t) => t.kind !== "eof" && t.kind !== "newline");
|
|
646
|
-
}
|
|
647
|
-
function assertNonEmptyDocument(tokens, sourceDescriptor) {
|
|
648
|
-
if (!hasContentTokens(tokens)) {
|
|
649
|
-
throw new ParseError(
|
|
650
|
-
`empty file is not a valid HOCON document (HOCON.md L130): ${sourceDescriptor}`,
|
|
651
|
-
1,
|
|
652
|
-
1
|
|
653
|
-
);
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
|
|
657
643
|
// src/internal/lexer/lexer.ts
|
|
658
644
|
function isHoconWhitespace(ch) {
|
|
659
645
|
const cp = ch.codePointAt(0) ?? -1;
|
|
@@ -1099,6 +1085,16 @@ var Parser = class {
|
|
|
1099
1085
|
}
|
|
1100
1086
|
return { kind: "object", fields: allFields, pos: first.pos };
|
|
1101
1087
|
}
|
|
1088
|
+
if (t.kind === "lbracket") {
|
|
1089
|
+
this.advance();
|
|
1090
|
+
const arr = this.parseArray();
|
|
1091
|
+
this.skip("newline");
|
|
1092
|
+
const remaining = this.peek();
|
|
1093
|
+
if (remaining.kind !== "eof") {
|
|
1094
|
+
throw new ParseError(`Unexpected token '${remaining.value}' after root array`, remaining.line, remaining.col);
|
|
1095
|
+
}
|
|
1096
|
+
return { ...arr, pos: { line: t.line, col: t.col } };
|
|
1097
|
+
}
|
|
1102
1098
|
return this.parseObject(false);
|
|
1103
1099
|
}
|
|
1104
1100
|
peek(offset = 0) {
|
|
@@ -1537,6 +1533,16 @@ var _require = createRequire(import.meta.url);
|
|
|
1537
1533
|
function isPnpPath(p) {
|
|
1538
1534
|
return p.includes(".yarn/cache") || p.includes(".pnp.cjs") || p.includes(".pnp.loader");
|
|
1539
1535
|
}
|
|
1536
|
+
function assertObjectRootedInclude(ast, sourcePath) {
|
|
1537
|
+
if (ast.kind === "array") {
|
|
1538
|
+
throw new ResolveError(
|
|
1539
|
+
`included file has array at file root \u2014 an included file must contain an object, not an array (HOCON.md L993-994): ${sourcePath}`,
|
|
1540
|
+
sourcePath,
|
|
1541
|
+
ast.pos.line,
|
|
1542
|
+
ast.pos.col
|
|
1543
|
+
);
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1540
1546
|
function validatePackageFile(file, identifier) {
|
|
1541
1547
|
if (file.length === 0) {
|
|
1542
1548
|
throw new ParseError(`include package("${identifier}", ...): file argument must be non-empty`, 0, 0);
|
|
@@ -1716,10 +1722,9 @@ var IncludeLoader = class {
|
|
|
1716
1722
|
const resolver = packageResolver ?? ((id, f, includingFile, bDir) => defaultPackageResolver(id, f, includingFile, bDir, resolveFrom));
|
|
1717
1723
|
const resolvedPath = resolver(identifier, file, void 0, baseDir);
|
|
1718
1724
|
const content = readFileSync2(resolvedPath);
|
|
1719
|
-
if (content.length === 0) return makeResObj();
|
|
1720
1725
|
const tokens = tokenize(content);
|
|
1721
|
-
assertNonEmptyDocument(tokens, resolvedPath);
|
|
1722
1726
|
const ast = parseTokens(tokens);
|
|
1727
|
+
assertObjectRootedInclude(ast, resolvedPath);
|
|
1723
1728
|
return this.onBuildResObj(ast, {
|
|
1724
1729
|
...this.opts,
|
|
1725
1730
|
baseDir: nodePath.dirname(resolvedPath),
|
|
@@ -1749,10 +1754,9 @@ var IncludeLoader = class {
|
|
|
1749
1754
|
const resolvedPath = resolver(identifier, file, void 0, baseDir);
|
|
1750
1755
|
const read = readFile ? async (p) => readFile(p) : async (p) => readFileSync2(p);
|
|
1751
1756
|
const content = await read(resolvedPath);
|
|
1752
|
-
if (content.length === 0) return makeResObj();
|
|
1753
1757
|
const tokens = tokenize(content);
|
|
1754
|
-
assertNonEmptyDocument(tokens, resolvedPath);
|
|
1755
1758
|
const ast = parseTokens(tokens);
|
|
1759
|
+
assertObjectRootedInclude(ast, resolvedPath);
|
|
1756
1760
|
return this.onBuildResObjAsync(ast, {
|
|
1757
1761
|
...this.opts,
|
|
1758
1762
|
baseDir: nodePath.dirname(resolvedPath),
|
|
@@ -1775,10 +1779,8 @@ var IncludeLoader = class {
|
|
|
1775
1779
|
return hoconValueToResObj(propertiesToHoconValue(content));
|
|
1776
1780
|
}
|
|
1777
1781
|
const tokens = tokenize(content);
|
|
1778
|
-
if (!hasContentTokens(tokens)) {
|
|
1779
|
-
return makeResObj();
|
|
1780
|
-
}
|
|
1781
1782
|
const ast = parseTokens(tokens);
|
|
1783
|
+
assertObjectRootedInclude(ast, candidate);
|
|
1782
1784
|
return this.onBuildResObj(ast, {
|
|
1783
1785
|
...this.opts,
|
|
1784
1786
|
baseDir: nodePath.dirname(candidate),
|
|
@@ -1802,10 +1804,8 @@ var IncludeLoader = class {
|
|
|
1802
1804
|
return hoconValueToResObj(propertiesToHoconValue(content));
|
|
1803
1805
|
}
|
|
1804
1806
|
const tokens = tokenize(content);
|
|
1805
|
-
if (!hasContentTokens(tokens)) {
|
|
1806
|
-
return makeResObj();
|
|
1807
|
-
}
|
|
1808
1807
|
const ast = parseTokens(tokens);
|
|
1808
|
+
assertObjectRootedInclude(ast, candidate);
|
|
1809
1809
|
return this.onBuildResObjAsync(ast, {
|
|
1810
1810
|
...this.opts,
|
|
1811
1811
|
baseDir: nodePath.dirname(candidate),
|
|
@@ -2548,6 +2548,32 @@ var Config = class _Config {
|
|
|
2548
2548
|
if (v === void 0) return void 0;
|
|
2549
2549
|
return hoconToJs(v);
|
|
2550
2550
|
}
|
|
2551
|
+
/**
|
|
2552
|
+
* Returns the raw {@link HoconValue} node at `path` as a deeply-immutable
|
|
2553
|
+
* {@link ReadonlyHoconValue} view, or `undefined` if the path is absent. Unlike
|
|
2554
|
+
* `get` (which decodes to a plain JS value), this exposes the value tree for
|
|
2555
|
+
* structural introspection via the `value.ts` accessors (`asString`, `asObject`,
|
|
2556
|
+
* `isScalar`, …).
|
|
2557
|
+
*
|
|
2558
|
+
* `getValue('')` returns the root object node.
|
|
2559
|
+
*
|
|
2560
|
+
* Throws {@link NotResolvedError} when the node (or its subtree) is unresolved:
|
|
2561
|
+
* the `HoconValue` union has no placeholder variant, so an unresolved value
|
|
2562
|
+
* cannot be represented. This matches `getConfig` / `getList`.
|
|
2563
|
+
*/
|
|
2564
|
+
getValue(path2) {
|
|
2565
|
+
const v = this.lookupNode(path2);
|
|
2566
|
+
if (v === void 0) {
|
|
2567
|
+
if (!this._resolved && this._resObjRootSubtreeHasPlaceholders(path2)) {
|
|
2568
|
+
throw new NotResolvedError(path2);
|
|
2569
|
+
}
|
|
2570
|
+
return void 0;
|
|
2571
|
+
}
|
|
2572
|
+
if (v.kind !== "scalar" && !this._resolved && this._resObjRootSubtreeHasPlaceholders(path2)) {
|
|
2573
|
+
throw new NotResolvedError(path2);
|
|
2574
|
+
}
|
|
2575
|
+
return v;
|
|
2576
|
+
}
|
|
2551
2577
|
getString(path2) {
|
|
2552
2578
|
const v = this.requireScalar(path2);
|
|
2553
2579
|
if (v.valueType === "null") {
|
|
@@ -2971,8 +2997,14 @@ async function defaultReadFile(filePath) {
|
|
|
2971
2997
|
}
|
|
2972
2998
|
function buildResolveContext(input, opts) {
|
|
2973
2999
|
const tokens = tokenize(input);
|
|
2974
|
-
assertNonEmptyDocument(tokens, "input");
|
|
2975
3000
|
const ast = parseTokens(tokens);
|
|
3001
|
+
if (ast.kind === "array") {
|
|
3002
|
+
const origin = opts.originDescription ?? "input";
|
|
3003
|
+
throw new ConfigError(
|
|
3004
|
+
`${origin}: ${ast.pos.line}:${ast.pos.col}: document has type array rather than object at file root (HOCON.md L989-991); the Config API requires an object root`,
|
|
3005
|
+
""
|
|
3006
|
+
);
|
|
3007
|
+
}
|
|
2976
3008
|
const resolveOpts = {
|
|
2977
3009
|
env: getEnv(opts),
|
|
2978
3010
|
baseDir: opts.baseDir,
|
|
@@ -3055,6 +3087,35 @@ async function parseFileAsync(filePath, opts = {}) {
|
|
|
3055
3087
|
});
|
|
3056
3088
|
}
|
|
3057
3089
|
|
|
3090
|
+
// src/value.ts
|
|
3091
|
+
function asString(v) {
|
|
3092
|
+
return v.kind === "scalar" && v.valueType === "string" ? v.raw : void 0;
|
|
3093
|
+
}
|
|
3094
|
+
function asNumber(v) {
|
|
3095
|
+
return v.kind === "scalar" ? coerceNumber(v.raw) : void 0;
|
|
3096
|
+
}
|
|
3097
|
+
function asBoolean(v) {
|
|
3098
|
+
return v.kind === "scalar" ? coerceBoolean(v.raw) : void 0;
|
|
3099
|
+
}
|
|
3100
|
+
function asObject(v) {
|
|
3101
|
+
return v.kind === "object" ? v.fields : void 0;
|
|
3102
|
+
}
|
|
3103
|
+
function asArray(v) {
|
|
3104
|
+
return v.kind === "array" ? v.items : void 0;
|
|
3105
|
+
}
|
|
3106
|
+
function isObject(v) {
|
|
3107
|
+
return v.kind === "object";
|
|
3108
|
+
}
|
|
3109
|
+
function isArray(v) {
|
|
3110
|
+
return v.kind === "array";
|
|
3111
|
+
}
|
|
3112
|
+
function isScalar(v) {
|
|
3113
|
+
return v.kind === "scalar";
|
|
3114
|
+
}
|
|
3115
|
+
function isNull(v) {
|
|
3116
|
+
return v.kind === "scalar" && v.valueType === "null";
|
|
3117
|
+
}
|
|
3118
|
+
|
|
3058
3119
|
// src/value-factory.ts
|
|
3059
3120
|
function fromMap(values, originDescription) {
|
|
3060
3121
|
const fields = coerceObject(values, "");
|
|
@@ -3128,10 +3189,19 @@ export {
|
|
|
3128
3189
|
PackageLookupError,
|
|
3129
3190
|
ParseError,
|
|
3130
3191
|
ResolveError,
|
|
3192
|
+
asArray,
|
|
3193
|
+
asBoolean,
|
|
3194
|
+
asNumber,
|
|
3195
|
+
asObject,
|
|
3196
|
+
asString,
|
|
3131
3197
|
defaultParseOptions,
|
|
3132
3198
|
defaultResolveOptions,
|
|
3133
3199
|
empty,
|
|
3134
3200
|
fromMap,
|
|
3201
|
+
isArray,
|
|
3202
|
+
isNull,
|
|
3203
|
+
isObject,
|
|
3204
|
+
isScalar,
|
|
3135
3205
|
parse,
|
|
3136
3206
|
parseAsync,
|
|
3137
3207
|
parseFile,
|
package/dist/zod.cjs
CHANGED
|
@@ -119,7 +119,7 @@ function parseDuration(value, outputUnit = "ms") {
|
|
|
119
119
|
if (i === 0) return NaN;
|
|
120
120
|
const num = Number(trimmed.slice(0, i));
|
|
121
121
|
if (Number.isNaN(num)) return NaN;
|
|
122
|
-
const unit = trimHoconWs(trimmed.slice(i))
|
|
122
|
+
const unit = trimHoconWs(trimmed.slice(i));
|
|
123
123
|
const divisor = OUTPUT_DURATION_UNITS[outputUnit];
|
|
124
124
|
if (divisor === void 0) return NaN;
|
|
125
125
|
if (unit === "") {
|
|
@@ -683,20 +683,6 @@ function isFileNotFoundError(e) {
|
|
|
683
683
|
var import_node_module = require("module");
|
|
684
684
|
var nodePath = __toESM(require("path"), 1);
|
|
685
685
|
|
|
686
|
-
// src/internal/parser/empty-check.ts
|
|
687
|
-
function hasContentTokens(tokens) {
|
|
688
|
-
return tokens.some((t) => t.kind !== "eof" && t.kind !== "newline");
|
|
689
|
-
}
|
|
690
|
-
function assertNonEmptyDocument(tokens, sourceDescriptor) {
|
|
691
|
-
if (!hasContentTokens(tokens)) {
|
|
692
|
-
throw new ParseError(
|
|
693
|
-
`empty file is not a valid HOCON document (HOCON.md L130): ${sourceDescriptor}`,
|
|
694
|
-
1,
|
|
695
|
-
1
|
|
696
|
-
);
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
|
|
700
686
|
// src/internal/lexer/lexer.ts
|
|
701
687
|
function isHoconWhitespace(ch) {
|
|
702
688
|
const cp = ch.codePointAt(0) ?? -1;
|
|
@@ -1142,6 +1128,16 @@ var Parser = class {
|
|
|
1142
1128
|
}
|
|
1143
1129
|
return { kind: "object", fields: allFields, pos: first.pos };
|
|
1144
1130
|
}
|
|
1131
|
+
if (t.kind === "lbracket") {
|
|
1132
|
+
this.advance();
|
|
1133
|
+
const arr = this.parseArray();
|
|
1134
|
+
this.skip("newline");
|
|
1135
|
+
const remaining = this.peek();
|
|
1136
|
+
if (remaining.kind !== "eof") {
|
|
1137
|
+
throw new ParseError(`Unexpected token '${remaining.value}' after root array`, remaining.line, remaining.col);
|
|
1138
|
+
}
|
|
1139
|
+
return { ...arr, pos: { line: t.line, col: t.col } };
|
|
1140
|
+
}
|
|
1145
1141
|
return this.parseObject(false);
|
|
1146
1142
|
}
|
|
1147
1143
|
peek(offset = 0) {
|
|
@@ -1581,6 +1577,16 @@ var _require = (0, import_node_module.createRequire)(import_meta.url);
|
|
|
1581
1577
|
function isPnpPath(p) {
|
|
1582
1578
|
return p.includes(".yarn/cache") || p.includes(".pnp.cjs") || p.includes(".pnp.loader");
|
|
1583
1579
|
}
|
|
1580
|
+
function assertObjectRootedInclude(ast, sourcePath) {
|
|
1581
|
+
if (ast.kind === "array") {
|
|
1582
|
+
throw new ResolveError(
|
|
1583
|
+
`included file has array at file root \u2014 an included file must contain an object, not an array (HOCON.md L993-994): ${sourcePath}`,
|
|
1584
|
+
sourcePath,
|
|
1585
|
+
ast.pos.line,
|
|
1586
|
+
ast.pos.col
|
|
1587
|
+
);
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1584
1590
|
function validatePackageFile(file, identifier) {
|
|
1585
1591
|
if (file.length === 0) {
|
|
1586
1592
|
throw new ParseError(`include package("${identifier}", ...): file argument must be non-empty`, 0, 0);
|
|
@@ -1760,10 +1766,9 @@ var IncludeLoader = class {
|
|
|
1760
1766
|
const resolver = packageResolver ?? ((id, f, includingFile, bDir) => defaultPackageResolver(id, f, includingFile, bDir, resolveFrom));
|
|
1761
1767
|
const resolvedPath = resolver(identifier, file, void 0, baseDir);
|
|
1762
1768
|
const content = readFileSync2(resolvedPath);
|
|
1763
|
-
if (content.length === 0) return makeResObj();
|
|
1764
1769
|
const tokens = tokenize(content);
|
|
1765
|
-
assertNonEmptyDocument(tokens, resolvedPath);
|
|
1766
1770
|
const ast = parseTokens(tokens);
|
|
1771
|
+
assertObjectRootedInclude(ast, resolvedPath);
|
|
1767
1772
|
return this.onBuildResObj(ast, {
|
|
1768
1773
|
...this.opts,
|
|
1769
1774
|
baseDir: nodePath.dirname(resolvedPath),
|
|
@@ -1793,10 +1798,9 @@ var IncludeLoader = class {
|
|
|
1793
1798
|
const resolvedPath = resolver(identifier, file, void 0, baseDir);
|
|
1794
1799
|
const read = readFile ? async (p) => readFile(p) : async (p) => readFileSync2(p);
|
|
1795
1800
|
const content = await read(resolvedPath);
|
|
1796
|
-
if (content.length === 0) return makeResObj();
|
|
1797
1801
|
const tokens = tokenize(content);
|
|
1798
|
-
assertNonEmptyDocument(tokens, resolvedPath);
|
|
1799
1802
|
const ast = parseTokens(tokens);
|
|
1803
|
+
assertObjectRootedInclude(ast, resolvedPath);
|
|
1800
1804
|
return this.onBuildResObjAsync(ast, {
|
|
1801
1805
|
...this.opts,
|
|
1802
1806
|
baseDir: nodePath.dirname(resolvedPath),
|
|
@@ -1819,10 +1823,8 @@ var IncludeLoader = class {
|
|
|
1819
1823
|
return hoconValueToResObj(propertiesToHoconValue(content));
|
|
1820
1824
|
}
|
|
1821
1825
|
const tokens = tokenize(content);
|
|
1822
|
-
if (!hasContentTokens(tokens)) {
|
|
1823
|
-
return makeResObj();
|
|
1824
|
-
}
|
|
1825
1826
|
const ast = parseTokens(tokens);
|
|
1827
|
+
assertObjectRootedInclude(ast, candidate);
|
|
1826
1828
|
return this.onBuildResObj(ast, {
|
|
1827
1829
|
...this.opts,
|
|
1828
1830
|
baseDir: nodePath.dirname(candidate),
|
|
@@ -1846,10 +1848,8 @@ var IncludeLoader = class {
|
|
|
1846
1848
|
return hoconValueToResObj(propertiesToHoconValue(content));
|
|
1847
1849
|
}
|
|
1848
1850
|
const tokens = tokenize(content);
|
|
1849
|
-
if (!hasContentTokens(tokens)) {
|
|
1850
|
-
return makeResObj();
|
|
1851
|
-
}
|
|
1852
1851
|
const ast = parseTokens(tokens);
|
|
1852
|
+
assertObjectRootedInclude(ast, candidate);
|
|
1853
1853
|
return this.onBuildResObjAsync(ast, {
|
|
1854
1854
|
...this.opts,
|
|
1855
1855
|
baseDir: nodePath.dirname(candidate),
|
|
@@ -2588,6 +2588,32 @@ var Config = class _Config {
|
|
|
2588
2588
|
if (v === void 0) return void 0;
|
|
2589
2589
|
return hoconToJs(v);
|
|
2590
2590
|
}
|
|
2591
|
+
/**
|
|
2592
|
+
* Returns the raw {@link HoconValue} node at `path` as a deeply-immutable
|
|
2593
|
+
* {@link ReadonlyHoconValue} view, or `undefined` if the path is absent. Unlike
|
|
2594
|
+
* `get` (which decodes to a plain JS value), this exposes the value tree for
|
|
2595
|
+
* structural introspection via the `value.ts` accessors (`asString`, `asObject`,
|
|
2596
|
+
* `isScalar`, …).
|
|
2597
|
+
*
|
|
2598
|
+
* `getValue('')` returns the root object node.
|
|
2599
|
+
*
|
|
2600
|
+
* Throws {@link NotResolvedError} when the node (or its subtree) is unresolved:
|
|
2601
|
+
* the `HoconValue` union has no placeholder variant, so an unresolved value
|
|
2602
|
+
* cannot be represented. This matches `getConfig` / `getList`.
|
|
2603
|
+
*/
|
|
2604
|
+
getValue(path2) {
|
|
2605
|
+
const v = this.lookupNode(path2);
|
|
2606
|
+
if (v === void 0) {
|
|
2607
|
+
if (!this._resolved && this._resObjRootSubtreeHasPlaceholders(path2)) {
|
|
2608
|
+
throw new NotResolvedError(path2);
|
|
2609
|
+
}
|
|
2610
|
+
return void 0;
|
|
2611
|
+
}
|
|
2612
|
+
if (v.kind !== "scalar" && !this._resolved && this._resObjRootSubtreeHasPlaceholders(path2)) {
|
|
2613
|
+
throw new NotResolvedError(path2);
|
|
2614
|
+
}
|
|
2615
|
+
return v;
|
|
2616
|
+
}
|
|
2591
2617
|
getString(path2) {
|
|
2592
2618
|
const v = this.requireScalar(path2);
|
|
2593
2619
|
if (v.valueType === "null") {
|
|
@@ -3000,8 +3026,14 @@ function defaultReadFileSync(filePath) {
|
|
|
3000
3026
|
}
|
|
3001
3027
|
function buildResolveContext(input, opts) {
|
|
3002
3028
|
const tokens = tokenize(input);
|
|
3003
|
-
assertNonEmptyDocument(tokens, "input");
|
|
3004
3029
|
const ast = parseTokens(tokens);
|
|
3030
|
+
if (ast.kind === "array") {
|
|
3031
|
+
const origin = opts.originDescription ?? "input";
|
|
3032
|
+
throw new ConfigError(
|
|
3033
|
+
`${origin}: ${ast.pos.line}:${ast.pos.col}: document has type array rather than object at file root (HOCON.md L989-991); the Config API requires an object root`,
|
|
3034
|
+
""
|
|
3035
|
+
);
|
|
3036
|
+
}
|
|
3005
3037
|
const resolveOpts = {
|
|
3006
3038
|
env: getEnv(opts),
|
|
3007
3039
|
baseDir: opts.baseDir,
|
package/dist/zod.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ZodType } from 'zod';
|
|
2
|
-
import { C as Config, a as ParseOptions } from './config-
|
|
2
|
+
import { C as Config, a as ParseOptions } from './config-D27s3_aG.cjs';
|
|
3
3
|
|
|
4
4
|
declare function validate<T>(config: Config, schema: ZodType<T>): T;
|
|
5
5
|
declare function getValidated<T>(config: Config, path: string, schema: ZodType<T>): T;
|
package/dist/zod.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ZodType } from 'zod';
|
|
2
|
-
import { C as Config, a as ParseOptions } from './config-
|
|
2
|
+
import { C as Config, a as ParseOptions } from './config-D27s3_aG.js';
|
|
3
3
|
|
|
4
4
|
declare function validate<T>(config: Config, schema: ZodType<T>): T;
|
|
5
5
|
declare function getValidated<T>(config: Config, path: string, schema: ZodType<T>): T;
|
package/dist/zod.js
CHANGED
|
@@ -80,7 +80,7 @@ function parseDuration(value, outputUnit = "ms") {
|
|
|
80
80
|
if (i === 0) return NaN;
|
|
81
81
|
const num = Number(trimmed.slice(0, i));
|
|
82
82
|
if (Number.isNaN(num)) return NaN;
|
|
83
|
-
const unit = trimHoconWs(trimmed.slice(i))
|
|
83
|
+
const unit = trimHoconWs(trimmed.slice(i));
|
|
84
84
|
const divisor = OUTPUT_DURATION_UNITS[outputUnit];
|
|
85
85
|
if (divisor === void 0) return NaN;
|
|
86
86
|
if (unit === "") {
|
|
@@ -644,20 +644,6 @@ function isFileNotFoundError(e) {
|
|
|
644
644
|
import { createRequire } from "module";
|
|
645
645
|
import * as nodePath from "path";
|
|
646
646
|
|
|
647
|
-
// src/internal/parser/empty-check.ts
|
|
648
|
-
function hasContentTokens(tokens) {
|
|
649
|
-
return tokens.some((t) => t.kind !== "eof" && t.kind !== "newline");
|
|
650
|
-
}
|
|
651
|
-
function assertNonEmptyDocument(tokens, sourceDescriptor) {
|
|
652
|
-
if (!hasContentTokens(tokens)) {
|
|
653
|
-
throw new ParseError(
|
|
654
|
-
`empty file is not a valid HOCON document (HOCON.md L130): ${sourceDescriptor}`,
|
|
655
|
-
1,
|
|
656
|
-
1
|
|
657
|
-
);
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
|
|
661
647
|
// src/internal/lexer/lexer.ts
|
|
662
648
|
function isHoconWhitespace(ch) {
|
|
663
649
|
const cp = ch.codePointAt(0) ?? -1;
|
|
@@ -1103,6 +1089,16 @@ var Parser = class {
|
|
|
1103
1089
|
}
|
|
1104
1090
|
return { kind: "object", fields: allFields, pos: first.pos };
|
|
1105
1091
|
}
|
|
1092
|
+
if (t.kind === "lbracket") {
|
|
1093
|
+
this.advance();
|
|
1094
|
+
const arr = this.parseArray();
|
|
1095
|
+
this.skip("newline");
|
|
1096
|
+
const remaining = this.peek();
|
|
1097
|
+
if (remaining.kind !== "eof") {
|
|
1098
|
+
throw new ParseError(`Unexpected token '${remaining.value}' after root array`, remaining.line, remaining.col);
|
|
1099
|
+
}
|
|
1100
|
+
return { ...arr, pos: { line: t.line, col: t.col } };
|
|
1101
|
+
}
|
|
1106
1102
|
return this.parseObject(false);
|
|
1107
1103
|
}
|
|
1108
1104
|
peek(offset = 0) {
|
|
@@ -1541,6 +1537,16 @@ var _require = createRequire(import.meta.url);
|
|
|
1541
1537
|
function isPnpPath(p) {
|
|
1542
1538
|
return p.includes(".yarn/cache") || p.includes(".pnp.cjs") || p.includes(".pnp.loader");
|
|
1543
1539
|
}
|
|
1540
|
+
function assertObjectRootedInclude(ast, sourcePath) {
|
|
1541
|
+
if (ast.kind === "array") {
|
|
1542
|
+
throw new ResolveError(
|
|
1543
|
+
`included file has array at file root \u2014 an included file must contain an object, not an array (HOCON.md L993-994): ${sourcePath}`,
|
|
1544
|
+
sourcePath,
|
|
1545
|
+
ast.pos.line,
|
|
1546
|
+
ast.pos.col
|
|
1547
|
+
);
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1544
1550
|
function validatePackageFile(file, identifier) {
|
|
1545
1551
|
if (file.length === 0) {
|
|
1546
1552
|
throw new ParseError(`include package("${identifier}", ...): file argument must be non-empty`, 0, 0);
|
|
@@ -1720,10 +1726,9 @@ var IncludeLoader = class {
|
|
|
1720
1726
|
const resolver = packageResolver ?? ((id, f, includingFile, bDir) => defaultPackageResolver(id, f, includingFile, bDir, resolveFrom));
|
|
1721
1727
|
const resolvedPath = resolver(identifier, file, void 0, baseDir);
|
|
1722
1728
|
const content = readFileSync2(resolvedPath);
|
|
1723
|
-
if (content.length === 0) return makeResObj();
|
|
1724
1729
|
const tokens = tokenize(content);
|
|
1725
|
-
assertNonEmptyDocument(tokens, resolvedPath);
|
|
1726
1730
|
const ast = parseTokens(tokens);
|
|
1731
|
+
assertObjectRootedInclude(ast, resolvedPath);
|
|
1727
1732
|
return this.onBuildResObj(ast, {
|
|
1728
1733
|
...this.opts,
|
|
1729
1734
|
baseDir: nodePath.dirname(resolvedPath),
|
|
@@ -1753,10 +1758,9 @@ var IncludeLoader = class {
|
|
|
1753
1758
|
const resolvedPath = resolver(identifier, file, void 0, baseDir);
|
|
1754
1759
|
const read = readFile ? async (p) => readFile(p) : async (p) => readFileSync2(p);
|
|
1755
1760
|
const content = await read(resolvedPath);
|
|
1756
|
-
if (content.length === 0) return makeResObj();
|
|
1757
1761
|
const tokens = tokenize(content);
|
|
1758
|
-
assertNonEmptyDocument(tokens, resolvedPath);
|
|
1759
1762
|
const ast = parseTokens(tokens);
|
|
1763
|
+
assertObjectRootedInclude(ast, resolvedPath);
|
|
1760
1764
|
return this.onBuildResObjAsync(ast, {
|
|
1761
1765
|
...this.opts,
|
|
1762
1766
|
baseDir: nodePath.dirname(resolvedPath),
|
|
@@ -1779,10 +1783,8 @@ var IncludeLoader = class {
|
|
|
1779
1783
|
return hoconValueToResObj(propertiesToHoconValue(content));
|
|
1780
1784
|
}
|
|
1781
1785
|
const tokens = tokenize(content);
|
|
1782
|
-
if (!hasContentTokens(tokens)) {
|
|
1783
|
-
return makeResObj();
|
|
1784
|
-
}
|
|
1785
1786
|
const ast = parseTokens(tokens);
|
|
1787
|
+
assertObjectRootedInclude(ast, candidate);
|
|
1786
1788
|
return this.onBuildResObj(ast, {
|
|
1787
1789
|
...this.opts,
|
|
1788
1790
|
baseDir: nodePath.dirname(candidate),
|
|
@@ -1806,10 +1808,8 @@ var IncludeLoader = class {
|
|
|
1806
1808
|
return hoconValueToResObj(propertiesToHoconValue(content));
|
|
1807
1809
|
}
|
|
1808
1810
|
const tokens = tokenize(content);
|
|
1809
|
-
if (!hasContentTokens(tokens)) {
|
|
1810
|
-
return makeResObj();
|
|
1811
|
-
}
|
|
1812
1811
|
const ast = parseTokens(tokens);
|
|
1812
|
+
assertObjectRootedInclude(ast, candidate);
|
|
1813
1813
|
return this.onBuildResObjAsync(ast, {
|
|
1814
1814
|
...this.opts,
|
|
1815
1815
|
baseDir: nodePath.dirname(candidate),
|
|
@@ -2548,6 +2548,32 @@ var Config = class _Config {
|
|
|
2548
2548
|
if (v === void 0) return void 0;
|
|
2549
2549
|
return hoconToJs(v);
|
|
2550
2550
|
}
|
|
2551
|
+
/**
|
|
2552
|
+
* Returns the raw {@link HoconValue} node at `path` as a deeply-immutable
|
|
2553
|
+
* {@link ReadonlyHoconValue} view, or `undefined` if the path is absent. Unlike
|
|
2554
|
+
* `get` (which decodes to a plain JS value), this exposes the value tree for
|
|
2555
|
+
* structural introspection via the `value.ts` accessors (`asString`, `asObject`,
|
|
2556
|
+
* `isScalar`, …).
|
|
2557
|
+
*
|
|
2558
|
+
* `getValue('')` returns the root object node.
|
|
2559
|
+
*
|
|
2560
|
+
* Throws {@link NotResolvedError} when the node (or its subtree) is unresolved:
|
|
2561
|
+
* the `HoconValue` union has no placeholder variant, so an unresolved value
|
|
2562
|
+
* cannot be represented. This matches `getConfig` / `getList`.
|
|
2563
|
+
*/
|
|
2564
|
+
getValue(path2) {
|
|
2565
|
+
const v = this.lookupNode(path2);
|
|
2566
|
+
if (v === void 0) {
|
|
2567
|
+
if (!this._resolved && this._resObjRootSubtreeHasPlaceholders(path2)) {
|
|
2568
|
+
throw new NotResolvedError(path2);
|
|
2569
|
+
}
|
|
2570
|
+
return void 0;
|
|
2571
|
+
}
|
|
2572
|
+
if (v.kind !== "scalar" && !this._resolved && this._resObjRootSubtreeHasPlaceholders(path2)) {
|
|
2573
|
+
throw new NotResolvedError(path2);
|
|
2574
|
+
}
|
|
2575
|
+
return v;
|
|
2576
|
+
}
|
|
2551
2577
|
getString(path2) {
|
|
2552
2578
|
const v = this.requireScalar(path2);
|
|
2553
2579
|
if (v.valueType === "null") {
|
|
@@ -2960,8 +2986,14 @@ function defaultReadFileSync(filePath) {
|
|
|
2960
2986
|
}
|
|
2961
2987
|
function buildResolveContext(input, opts) {
|
|
2962
2988
|
const tokens = tokenize(input);
|
|
2963
|
-
assertNonEmptyDocument(tokens, "input");
|
|
2964
2989
|
const ast = parseTokens(tokens);
|
|
2990
|
+
if (ast.kind === "array") {
|
|
2991
|
+
const origin = opts.originDescription ?? "input";
|
|
2992
|
+
throw new ConfigError(
|
|
2993
|
+
`${origin}: ${ast.pos.line}:${ast.pos.col}: document has type array rather than object at file root (HOCON.md L989-991); the Config API requires an object root`,
|
|
2994
|
+
""
|
|
2995
|
+
);
|
|
2996
|
+
}
|
|
2965
2997
|
const resolveOpts = {
|
|
2966
2998
|
env: getEnv(opts),
|
|
2967
2999
|
baseDir: opts.baseDir,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@o3co/ts.hocon",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "Full Lightbend HOCON spec-compliant parser and config library for TypeScript",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"hocon",
|
|
@@ -29,6 +29,15 @@
|
|
|
29
29
|
"files": [
|
|
30
30
|
"dist"
|
|
31
31
|
],
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsup",
|
|
34
|
+
"test": "vitest run",
|
|
35
|
+
"test:watch": "vitest",
|
|
36
|
+
"typecheck": "tsc --noEmit",
|
|
37
|
+
"coverage": "vitest run --coverage",
|
|
38
|
+
"bench": "vitest bench",
|
|
39
|
+
"docs": "typedoc"
|
|
40
|
+
},
|
|
32
41
|
"peerDependencies": {
|
|
33
42
|
"zod": ">=4.0.0"
|
|
34
43
|
},
|
|
@@ -39,18 +48,18 @@
|
|
|
39
48
|
},
|
|
40
49
|
"devDependencies": {
|
|
41
50
|
"@types/js-yaml": "^4.0.9",
|
|
42
|
-
"@types/node": "^25.5
|
|
43
|
-
"@vitest/coverage-v8": "4.1.
|
|
44
|
-
"js-yaml": "^4.
|
|
51
|
+
"@types/node": "^25.9.5",
|
|
52
|
+
"@vitest/coverage-v8": "4.1.10",
|
|
53
|
+
"js-yaml": "^4.3.0",
|
|
45
54
|
"tsup": "^8.5.1",
|
|
46
|
-
"typedoc": "^0.28.
|
|
55
|
+
"typedoc": "^0.28.20",
|
|
47
56
|
"typescript": "^5.9.3",
|
|
48
|
-
"vitest": "4.1.
|
|
49
|
-
"zod": "^4.3
|
|
57
|
+
"vitest": "4.1.10",
|
|
58
|
+
"zod": "^4.4.3"
|
|
50
59
|
},
|
|
51
60
|
"repository": {
|
|
52
61
|
"type": "git",
|
|
53
|
-
"url": "https://github.com/o3co/ts.hocon"
|
|
62
|
+
"url": "git+https://github.com/o3co/ts.hocon.git"
|
|
54
63
|
},
|
|
55
64
|
"license": "Apache-2.0",
|
|
56
65
|
"engines": {
|
|
@@ -58,13 +67,5 @@
|
|
|
58
67
|
},
|
|
59
68
|
"homepage": "https://o3co.github.io/ts.hocon/",
|
|
60
69
|
"bugs": "https://github.com/o3co/ts.hocon/issues",
|
|
61
|
-
"
|
|
62
|
-
|
|
63
|
-
"test": "vitest run",
|
|
64
|
-
"test:watch": "vitest",
|
|
65
|
-
"typecheck": "tsc --noEmit",
|
|
66
|
-
"coverage": "vitest run --coverage",
|
|
67
|
-
"bench": "vitest bench",
|
|
68
|
-
"docs": "typedoc"
|
|
69
|
-
}
|
|
70
|
-
}
|
|
70
|
+
"packageManager": "pnpm@10.30.2"
|
|
71
|
+
}
|