@earendil-works/pi-schema 0.0.0-stage → 1.5.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/CHANGELOG.md +13 -0
- package/LICENSES/typebox-LICENSE +23 -0
- package/README.md +295 -2
- package/dist/annotations.d.ts +44 -0
- package/dist/annotations.d.ts.map +1 -0
- package/dist/annotations.js +193 -0
- package/dist/annotations.js.map +1 -0
- package/dist/builders.d.ts +114 -0
- package/dist/builders.d.ts.map +1 -0
- package/dist/builders.js +98 -0
- package/dist/builders.js.map +1 -0
- package/dist/checks.d.ts +87 -0
- package/dist/checks.d.ts.map +1 -0
- package/dist/checks.js +447 -0
- package/dist/checks.js.map +1 -0
- package/dist/coerce.d.ts +53 -0
- package/dist/coerce.d.ts.map +1 -0
- package/dist/coerce.js +585 -0
- package/dist/coerce.js.map +1 -0
- package/dist/compiler.d.ts +53 -0
- package/dist/compiler.d.ts.map +1 -0
- package/dist/compiler.js +532 -0
- package/dist/compiler.js.map +1 -0
- package/dist/explain.d.ts +29 -0
- package/dist/explain.d.ts.map +1 -0
- package/dist/explain.js +381 -0
- package/dist/explain.js.map +1 -0
- package/dist/formats/index.d.ts +7 -0
- package/dist/formats/index.d.ts.map +1 -0
- package/dist/formats/index.js +51 -0
- package/dist/formats/index.js.map +1 -0
- package/dist/formats/typebox/date.d.ts +7 -0
- package/dist/formats/typebox/date.d.ts.map +1 -0
- package/dist/formats/typebox/date.js +47 -0
- package/dist/formats/typebox/date.js.map +1 -0
- package/dist/formats/typebox/date_time.d.ts +7 -0
- package/dist/formats/typebox/date_time.d.ts.map +1 -0
- package/dist/formats/typebox/date_time.js +39 -0
- package/dist/formats/typebox/date_time.js.map +1 -0
- package/dist/formats/typebox/duration.d.ts +6 -0
- package/dist/formats/typebox/duration.d.ts.map +1 -0
- package/dist/formats/typebox/duration.js +36 -0
- package/dist/formats/typebox/duration.js.map +1 -0
- package/dist/formats/typebox/email.d.ts +6 -0
- package/dist/formats/typebox/email.d.ts.map +1 -0
- package/dist/formats/typebox/email.js +36 -0
- package/dist/formats/typebox/email.js.map +1 -0
- package/dist/formats/typebox/hostname.d.ts +8 -0
- package/dist/formats/typebox/hostname.d.ts.map +1 -0
- package/dist/formats/typebox/hostname.js +38 -0
- package/dist/formats/typebox/hostname.js.map +1 -0
- package/dist/formats/typebox/idn_email.d.ts +6 -0
- package/dist/formats/typebox/idn_email.d.ts.map +1 -0
- package/dist/formats/typebox/idn_email.js +36 -0
- package/dist/formats/typebox/idn_email.js.map +1 -0
- package/dist/formats/typebox/idn_hostname.d.ts +9 -0
- package/dist/formats/typebox/idn_hostname.d.ts.map +1 -0
- package/dist/formats/typebox/idn_hostname.js +39 -0
- package/dist/formats/typebox/idn_hostname.js.map +1 -0
- package/dist/formats/typebox/idna/format/bidi.d.ts +4 -0
- package/dist/formats/typebox/idna/format/bidi.d.ts.map +1 -0
- package/dist/formats/typebox/idna/format/bidi.js +132 -0
- package/dist/formats/typebox/idna/format/bidi.js.map +1 -0
- package/dist/formats/typebox/idna/format/puny.d.ts +4 -0
- package/dist/formats/typebox/idna/format/puny.d.ts.map +1 -0
- package/dist/formats/typebox/idna/format/puny.js +172 -0
- package/dist/formats/typebox/idna/format/puny.js.map +1 -0
- package/dist/formats/typebox/idna/hostname.d.ts +2 -0
- package/dist/formats/typebox/idna/hostname.d.ts.map +1 -0
- package/dist/formats/typebox/idna/hostname.js +43 -0
- package/dist/formats/typebox/idna/hostname.js.map +1 -0
- package/dist/formats/typebox/idna/idn-hostname.d.ts +2 -0
- package/dist/formats/typebox/idna/idn-hostname.d.ts.map +1 -0
- package/dist/formats/typebox/idna/idn-hostname.js +71 -0
- package/dist/formats/typebox/idna/idn-hostname.js.map +1 -0
- package/dist/formats/typebox/idna/index.d.ts +3 -0
- package/dist/formats/typebox/idna/index.d.ts.map +1 -0
- package/dist/formats/typebox/idna/index.js +30 -0
- package/dist/formats/typebox/idna/index.js.map +1 -0
- package/dist/formats/typebox/idna/label/ascii.d.ts +2 -0
- package/dist/formats/typebox/idna/label/ascii.d.ts.map +1 -0
- package/dist/formats/typebox/idna/label/ascii.js +37 -0
- package/dist/formats/typebox/idna/label/ascii.js.map +1 -0
- package/dist/formats/typebox/idna/label/puny.d.ts +2 -0
- package/dist/formats/typebox/idna/label/puny.d.ts.map +1 -0
- package/dist/formats/typebox/idna/label/puny.js +56 -0
- package/dist/formats/typebox/idna/label/puny.js.map +1 -0
- package/dist/formats/typebox/idna/label/unicode.d.ts +2 -0
- package/dist/formats/typebox/idna/label/unicode.d.ts.map +1 -0
- package/dist/formats/typebox/idna/label/unicode.js +134 -0
- package/dist/formats/typebox/idna/label/unicode.js.map +1 -0
- package/dist/formats/typebox/idna/pattern/pattern.d.ts +31 -0
- package/dist/formats/typebox/idna/pattern/pattern.d.ts.map +1 -0
- package/dist/formats/typebox/idna/pattern/pattern.js +98 -0
- package/dist/formats/typebox/idna/pattern/pattern.js.map +1 -0
- package/dist/formats/typebox/ipv4.d.ts +6 -0
- package/dist/formats/typebox/ipv4.d.ts.map +1 -0
- package/dist/formats/typebox/ipv4.js +36 -0
- package/dist/formats/typebox/ipv4.js.map +1 -0
- package/dist/formats/typebox/ipv6.d.ts +6 -0
- package/dist/formats/typebox/ipv6.d.ts.map +1 -0
- package/dist/formats/typebox/ipv6.js +36 -0
- package/dist/formats/typebox/ipv6.js.map +1 -0
- package/dist/formats/typebox/iri.d.ts +6 -0
- package/dist/formats/typebox/iri.d.ts.map +1 -0
- package/dist/formats/typebox/iri.js +63 -0
- package/dist/formats/typebox/iri.js.map +1 -0
- package/dist/formats/typebox/iri_reference.d.ts +6 -0
- package/dist/formats/typebox/iri_reference.d.ts.map +1 -0
- package/dist/formats/typebox/iri_reference.js +40 -0
- package/dist/formats/typebox/iri_reference.js.map +1 -0
- package/dist/formats/typebox/json_pointer.d.ts +7 -0
- package/dist/formats/typebox/json_pointer.d.ts.map +1 -0
- package/dist/formats/typebox/json_pointer.js +37 -0
- package/dist/formats/typebox/json_pointer.js.map +1 -0
- package/dist/formats/typebox/json_pointer_uri_fragment.d.ts +7 -0
- package/dist/formats/typebox/json_pointer_uri_fragment.d.ts.map +1 -0
- package/dist/formats/typebox/json_pointer_uri_fragment.js +37 -0
- package/dist/formats/typebox/json_pointer_uri_fragment.js.map +1 -0
- package/dist/formats/typebox/regex.d.ts +6 -0
- package/dist/formats/typebox/regex.d.ts.map +1 -0
- package/dist/formats/typebox/regex.js +41 -0
- package/dist/formats/typebox/regex.js.map +1 -0
- package/dist/formats/typebox/relative_json_pointer.d.ts +7 -0
- package/dist/formats/typebox/relative_json_pointer.d.ts.map +1 -0
- package/dist/formats/typebox/relative_json_pointer.js +37 -0
- package/dist/formats/typebox/relative_json_pointer.js.map +1 -0
- package/dist/formats/typebox/time.d.ts +6 -0
- package/dist/formats/typebox/time.d.ts.map +1 -0
- package/dist/formats/typebox/time.js +60 -0
- package/dist/formats/typebox/time.js.map +1 -0
- package/dist/formats/typebox/uri.d.ts +6 -0
- package/dist/formats/typebox/uri.d.ts.map +1 -0
- package/dist/formats/typebox/uri.js +36 -0
- package/dist/formats/typebox/uri.js.map +1 -0
- package/dist/formats/typebox/uri_reference.d.ts +6 -0
- package/dist/formats/typebox/uri_reference.d.ts.map +1 -0
- package/dist/formats/typebox/uri_reference.js +36 -0
- package/dist/formats/typebox/uri_reference.js.map +1 -0
- package/dist/formats/typebox/uri_template.d.ts +6 -0
- package/dist/formats/typebox/uri_template.d.ts.map +1 -0
- package/dist/formats/typebox/uri_template.js +37 -0
- package/dist/formats/typebox/uri_template.js.map +1 -0
- package/dist/formats/typebox/url.d.ts +7 -0
- package/dist/formats/typebox/url.d.ts.map +1 -0
- package/dist/formats/typebox/url.js +36 -0
- package/dist/formats/typebox/url.js.map +1 -0
- package/dist/formats/typebox/uuid.d.ts +7 -0
- package/dist/formats/typebox/uuid.d.ts.map +1 -0
- package/dist/formats/typebox/uuid.js +37 -0
- package/dist/formats/typebox/uuid.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/resolver.d.ts +57 -0
- package/dist/resolver.d.ts.map +1 -0
- package/dist/resolver.js +202 -0
- package/dist/resolver.js.map +1 -0
- package/dist/shared.d.ts +9 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +33 -0
- package/dist/shared.js.map +1 -0
- package/dist/typebox-static.d.ts +48 -0
- package/dist/typebox-static.d.ts.map +1 -0
- package/dist/typebox-static.js +5 -0
- package/dist/typebox-static.js.map +1 -0
- package/dist/types.d.ts +48 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/validator.d.ts +39 -0
- package/dist/validator.d.ts.map +1 -0
- package/dist/validator.js +71 -0
- package/dist/validator.js.map +1 -0
- package/package.json +59 -5
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { CompileOptions, JsonSchema, SchemaObject } from "./types.ts";
|
|
2
|
+
/** A schema and the base URI its references resolve against. */
|
|
3
|
+
export interface Resolved {
|
|
4
|
+
readonly node: unknown;
|
|
5
|
+
readonly base: string;
|
|
6
|
+
}
|
|
7
|
+
/** The base URI of a root schema without `$id`; relative references resolve against it. */
|
|
8
|
+
export declare const DEFAULT_BASE = "https://json-schema.invalid/root.json";
|
|
9
|
+
/**
|
|
10
|
+
* Resolves references in the root schema and the registered documents. The schema is indexed on the first
|
|
11
|
+
* reference, so a schema without references is never walked for identifiers.
|
|
12
|
+
*/
|
|
13
|
+
export declare class Resolver {
|
|
14
|
+
readonly draft: "2020-12" | "7";
|
|
15
|
+
readonly rootBase: string;
|
|
16
|
+
/** Schema object to the base URI it was indexed under. */
|
|
17
|
+
readonly baseOf: Map<object, string>;
|
|
18
|
+
/** URI (with a `#name` fragment) of every `$dynamicAnchor`, to its schema. */
|
|
19
|
+
readonly dynamicAnchors: Map<string, SchemaObject>;
|
|
20
|
+
private readonly root;
|
|
21
|
+
private readonly options;
|
|
22
|
+
private readonly registry;
|
|
23
|
+
private readonly rejectUnresolved;
|
|
24
|
+
/** The references that named nothing and resolved to `false` (with `unresolvedRefs: "reject"`). */
|
|
25
|
+
private readonly unresolved;
|
|
26
|
+
private readonly resources;
|
|
27
|
+
private readonly anchors;
|
|
28
|
+
private indexed;
|
|
29
|
+
constructor(root: JsonSchema, options: CompileOptions);
|
|
30
|
+
/**
|
|
31
|
+
* The base URI of `node`, whose parent has `base`: its `$id` changes it, unless draft 7 makes the `$id` inert. Once
|
|
32
|
+
* the schema is indexed, the indexed base (a resolved reference target already has its own).
|
|
33
|
+
*/
|
|
34
|
+
baseFor(node: SchemaObject, base: string): string;
|
|
35
|
+
/** Indexes the root schema and the registered documents' URIs (once). */
|
|
36
|
+
ensureIndexed(): void;
|
|
37
|
+
/**
|
|
38
|
+
* The schema a reference names, with the base URI it compiles under. Throws if it names nothing, or, with
|
|
39
|
+
* `unresolvedRefs: "reject"`, resolves to `false`, the schema no value passes.
|
|
40
|
+
*/
|
|
41
|
+
resolve(ref: string, base: string): Resolved;
|
|
42
|
+
/** Whether `ref`, from `base`, named nothing and resolved to `false`. */
|
|
43
|
+
isUnresolved(ref: string, base: string): boolean;
|
|
44
|
+
private find;
|
|
45
|
+
/** Whether `node` is the root of the schema resource at `base`. */
|
|
46
|
+
isResource(node: object, base: string): boolean;
|
|
47
|
+
private index;
|
|
48
|
+
private load;
|
|
49
|
+
private pointer;
|
|
50
|
+
}
|
|
51
|
+
/** Whether `value` is a schema object (not `true`/`false`, an array or a primitive). */
|
|
52
|
+
export declare function isSchemaObject(value: unknown): value is SchemaObject;
|
|
53
|
+
/** `ref` resolved against `base`. URNs and other bases a URL parser rejects take fragment-only references. */
|
|
54
|
+
export declare function resolveUri(ref: string, base: string): string;
|
|
55
|
+
/** `uri` without its fragment. */
|
|
56
|
+
export declare function documentOf(uri: string): string;
|
|
57
|
+
//# sourceMappingURL=resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolver.d.ts","sourceRoot":"","sources":["../src/resolver.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE3E,gEAAgE;AAChE,MAAM,WAAW,QAAQ;IACxB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACtB;AAED,2FAA2F;AAC3F,eAAO,MAAM,YAAY,0CAA0C,CAAC;AAEpE;;;GAGG;AACH,qBAAa,QAAQ;IACpB,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,GAAG,CAAC;IAChC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,0DAA0D;IAC1D,QAAQ,CAAC,MAAM,sBAA6B;IAC5C,8EAA8E;IAC9E,QAAQ,CAAC,cAAc,4BAAmC;IAC1D,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAa;IAClC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiB;IACzC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiC;IAC1D,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAU;IAC3C,mGAAmG;IACnG,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAqB;IAChD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA8B;IACxD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmC;IAC3D,OAAO,CAAC,OAAO,CAAS;IAExB,YAAY,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,cAAc,EAMpD;IAED;;;OAGG;IACH,OAAO,CAAC,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAOhD;IAED,yEAAyE;IACzE,aAAa,IAAI,IAAI,CAQpB;IAED;;;OAGG;IACH,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,QAAQ,CAS3C;IAED,yEAAyE;IACzE,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAE/C;IAED,OAAO,CAAC,IAAI;IAaZ,mEAAmE;IACnE,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAG9C;IAGD,OAAO,CAAC,KAAK;IAkCb,OAAO,CAAC,IAAI;IAQZ,OAAO,CAAC,OAAO;CAcf;AAqBD,wFAAwF;AACxF,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,YAAY,CAEpE;AAED,8GAA8G;AAC9G,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAM5D;AAED,kCAAkC;AAClC,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAG9C"}
|
package/dist/resolver.js
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
// Reference resolution of one compile: schema resources, anchors and JSON Pointers over the root schema and the
|
|
2
|
+
// registered documents. A Resolver lives only while a schema compiles (or while its errors are explained).
|
|
3
|
+
/** The base URI of a root schema without `$id`; relative references resolve against it. */
|
|
4
|
+
export const DEFAULT_BASE = "https://json-schema.invalid/root.json";
|
|
5
|
+
/**
|
|
6
|
+
* Resolves references in the root schema and the registered documents. The schema is indexed on the first
|
|
7
|
+
* reference, so a schema without references is never walked for identifiers.
|
|
8
|
+
*/
|
|
9
|
+
export class Resolver {
|
|
10
|
+
draft;
|
|
11
|
+
rootBase;
|
|
12
|
+
/** Schema object to the base URI it was indexed under. */
|
|
13
|
+
baseOf = new Map();
|
|
14
|
+
/** URI (with a `#name` fragment) of every `$dynamicAnchor`, to its schema. */
|
|
15
|
+
dynamicAnchors = new Map();
|
|
16
|
+
root;
|
|
17
|
+
options;
|
|
18
|
+
registry = new Map();
|
|
19
|
+
rejectUnresolved;
|
|
20
|
+
/** The references that named nothing and resolved to `false` (with `unresolvedRefs: "reject"`). */
|
|
21
|
+
unresolved = new Set();
|
|
22
|
+
resources = new Map();
|
|
23
|
+
anchors = new Map();
|
|
24
|
+
indexed = false;
|
|
25
|
+
constructor(root, options) {
|
|
26
|
+
this.draft = options.draft === "7" ? "7" : "2020-12";
|
|
27
|
+
this.root = root;
|
|
28
|
+
this.options = options;
|
|
29
|
+
this.rejectUnresolved = options.unresolvedRefs === "reject";
|
|
30
|
+
this.rootBase = isSchemaObject(root) ? this.baseFor(root, DEFAULT_BASE) : DEFAULT_BASE;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The base URI of `node`, whose parent has `base`: its `$id` changes it, unless draft 7 makes the `$id` inert. Once
|
|
34
|
+
* the schema is indexed, the indexed base (a resolved reference target already has its own).
|
|
35
|
+
*/
|
|
36
|
+
baseFor(node, base) {
|
|
37
|
+
const indexed = this.baseOf.get(node);
|
|
38
|
+
if (indexed !== undefined)
|
|
39
|
+
return indexed;
|
|
40
|
+
const id = node.$id;
|
|
41
|
+
if (typeof id !== "string")
|
|
42
|
+
return base;
|
|
43
|
+
if (this.draft === "7" && (typeof node.$ref === "string" || id.startsWith("#")))
|
|
44
|
+
return base;
|
|
45
|
+
return documentOf(resolveUri(id, base));
|
|
46
|
+
}
|
|
47
|
+
/** Indexes the root schema and the registered documents' URIs (once). */
|
|
48
|
+
ensureIndexed() {
|
|
49
|
+
if (this.indexed)
|
|
50
|
+
return;
|
|
51
|
+
this.indexed = true;
|
|
52
|
+
for (const [uri, schema] of Object.entries(this.options.schemas ?? {})) {
|
|
53
|
+
this.registry.set(documentOf(resolveUri(uri, DEFAULT_BASE)), schema);
|
|
54
|
+
}
|
|
55
|
+
this.resources.set(DEFAULT_BASE, this.root);
|
|
56
|
+
this.index(this.root, DEFAULT_BASE);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The schema a reference names, with the base URI it compiles under. Throws if it names nothing, or, with
|
|
60
|
+
* `unresolvedRefs: "reject"`, resolves to `false`, the schema no value passes.
|
|
61
|
+
*/
|
|
62
|
+
resolve(ref, base) {
|
|
63
|
+
this.ensureIndexed();
|
|
64
|
+
if (!this.rejectUnresolved)
|
|
65
|
+
return this.find(ref, base);
|
|
66
|
+
try {
|
|
67
|
+
return this.find(ref, base);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
this.unresolved.add(resolveUri(ref, base));
|
|
71
|
+
return { node: false, base };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/** Whether `ref`, from `base`, named nothing and resolved to `false`. */
|
|
75
|
+
isUnresolved(ref, base) {
|
|
76
|
+
return this.unresolved.has(resolveUri(ref, base));
|
|
77
|
+
}
|
|
78
|
+
find(ref, base) {
|
|
79
|
+
const uri = resolveUri(ref, base);
|
|
80
|
+
const doc = documentOf(uri);
|
|
81
|
+
const fragment = uri.length > doc.length ? decodeURIComponent(uri.slice(doc.length + 1)) : "";
|
|
82
|
+
const root = this.resources.has(doc) ? this.resources.get(doc) : this.load(doc);
|
|
83
|
+
if (root === undefined)
|
|
84
|
+
throw new Error(`Unresolved $ref ${ref} (no schema registered for ${doc})`);
|
|
85
|
+
if (fragment === "")
|
|
86
|
+
return { node: root, base: doc };
|
|
87
|
+
if (fragment.startsWith("/"))
|
|
88
|
+
return this.pointer(root, doc, fragment);
|
|
89
|
+
const target = this.anchors.get(`${doc}#${fragment}`);
|
|
90
|
+
if (target === undefined)
|
|
91
|
+
throw new Error(`Unresolved $ref ${ref} (no anchor ${fragment} in ${doc})`);
|
|
92
|
+
return { node: target, base: this.baseOf.get(target) ?? doc };
|
|
93
|
+
}
|
|
94
|
+
/** Whether `node` is the root of the schema resource at `base`. */
|
|
95
|
+
isResource(node, base) {
|
|
96
|
+
this.ensureIndexed();
|
|
97
|
+
return this.resources.get(base) === node;
|
|
98
|
+
}
|
|
99
|
+
// Walks only keywords that hold subschemas: an `$id` inside `enum`, `const` or an unknown keyword names nothing.
|
|
100
|
+
index(node, base) {
|
|
101
|
+
if (!isSchemaObject(node) || this.baseOf.has(node))
|
|
102
|
+
return;
|
|
103
|
+
let b = base;
|
|
104
|
+
// In draft 7 a `$ref` makes every sibling keyword, `$id` included, inert.
|
|
105
|
+
const refOnly = this.draft === "7" && typeof node.$ref === "string";
|
|
106
|
+
if (!refOnly && typeof node.$id === "string") {
|
|
107
|
+
if (this.draft === "7" && node.$id.startsWith("#"))
|
|
108
|
+
this.anchors.set(documentOf(b) + node.$id, node);
|
|
109
|
+
else {
|
|
110
|
+
const uri = resolveUri(node.$id, b);
|
|
111
|
+
b = documentOf(uri);
|
|
112
|
+
this.resources.set(b, node);
|
|
113
|
+
if (uri.length > b.length + 1)
|
|
114
|
+
this.anchors.set(uri, node);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
this.baseOf.set(node, b);
|
|
118
|
+
if (refOnly)
|
|
119
|
+
return;
|
|
120
|
+
if (this.draft !== "7") {
|
|
121
|
+
if (typeof node.$anchor === "string")
|
|
122
|
+
this.anchors.set(`${b}#${node.$anchor}`, node);
|
|
123
|
+
if (typeof node.$dynamicAnchor === "string") {
|
|
124
|
+
this.anchors.set(`${b}#${node.$dynamicAnchor}`, node);
|
|
125
|
+
this.dynamicAnchors.set(`${b}#${node.$dynamicAnchor}`, node);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
for (const key of SCHEMA_KEYWORDS)
|
|
129
|
+
this.index(node[key], b);
|
|
130
|
+
for (const key of MAP_KEYWORDS) {
|
|
131
|
+
const map = node[key];
|
|
132
|
+
if (isSchemaObject(map))
|
|
133
|
+
for (const k of Object.keys(map))
|
|
134
|
+
this.index(map[k], b);
|
|
135
|
+
}
|
|
136
|
+
for (const key of ARRAY_KEYWORDS) {
|
|
137
|
+
const list = node[key];
|
|
138
|
+
if (Array.isArray(list))
|
|
139
|
+
for (const s of list)
|
|
140
|
+
this.index(s, b);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
load(doc) {
|
|
144
|
+
const schema = this.registry.get(doc);
|
|
145
|
+
if (schema === undefined)
|
|
146
|
+
return undefined;
|
|
147
|
+
this.resources.set(doc, schema);
|
|
148
|
+
this.index(schema, doc);
|
|
149
|
+
return schema;
|
|
150
|
+
}
|
|
151
|
+
pointer(root, base, fragment) {
|
|
152
|
+
let node = root;
|
|
153
|
+
let b = base;
|
|
154
|
+
for (const raw of fragment.split("/").slice(1)) {
|
|
155
|
+
const key = raw.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
156
|
+
node =
|
|
157
|
+
typeof node === "object" && node !== null && Object.hasOwn(node, key)
|
|
158
|
+
? node[key]
|
|
159
|
+
: undefined;
|
|
160
|
+
if (node === undefined)
|
|
161
|
+
throw new Error(`Unresolved $ref #${fragment} in ${base}`);
|
|
162
|
+
if (typeof node === "object" && node !== null)
|
|
163
|
+
b = this.baseOf.get(node) ?? b;
|
|
164
|
+
}
|
|
165
|
+
return { node, base: b };
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const SCHEMA_KEYWORDS = [
|
|
169
|
+
"additionalProperties",
|
|
170
|
+
"propertyNames",
|
|
171
|
+
"contains",
|
|
172
|
+
"not",
|
|
173
|
+
"if",
|
|
174
|
+
"then",
|
|
175
|
+
"else",
|
|
176
|
+
"unevaluatedProperties",
|
|
177
|
+
"unevaluatedItems",
|
|
178
|
+
"additionalItems",
|
|
179
|
+
"contentSchema",
|
|
180
|
+
"items",
|
|
181
|
+
];
|
|
182
|
+
const MAP_KEYWORDS = ["properties", "patternProperties", "$defs", "definitions", "dependentSchemas", "dependencies"];
|
|
183
|
+
const ARRAY_KEYWORDS = ["allOf", "anyOf", "oneOf", "prefixItems", "items"];
|
|
184
|
+
/** Whether `value` is a schema object (not `true`/`false`, an array or a primitive). */
|
|
185
|
+
export function isSchemaObject(value) {
|
|
186
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
187
|
+
}
|
|
188
|
+
/** `ref` resolved against `base`. URNs and other bases a URL parser rejects take fragment-only references. */
|
|
189
|
+
export function resolveUri(ref, base) {
|
|
190
|
+
try {
|
|
191
|
+
return new URL(ref, base).href;
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
return ref.startsWith("#") ? documentOf(base) + ref : ref;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/** `uri` without its fragment. */
|
|
198
|
+
export function documentOf(uri) {
|
|
199
|
+
const hash = uri.indexOf("#");
|
|
200
|
+
return hash < 0 ? uri : uri.slice(0, hash);
|
|
201
|
+
}
|
|
202
|
+
//# sourceMappingURL=resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolver.js","sourceRoot":"","sources":["../src/resolver.ts"],"names":[],"mappings":"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","sourcesContent":["// Reference resolution of one compile: schema resources, anchors and JSON Pointers over the root schema and the\n// registered documents. A Resolver lives only while a schema compiles (or while its errors are explained).\n\nimport type { CompileOptions, JsonSchema, SchemaObject } from \"./types.ts\";\n\n/** A schema and the base URI its references resolve against. */\nexport interface Resolved {\n\treadonly node: unknown;\n\treadonly base: string;\n}\n\n/** The base URI of a root schema without `$id`; relative references resolve against it. */\nexport const DEFAULT_BASE = \"https://json-schema.invalid/root.json\";\n\n/**\n * Resolves references in the root schema and the registered documents. The schema is indexed on the first\n * reference, so a schema without references is never walked for identifiers.\n */\nexport class Resolver {\n\treadonly draft: \"2020-12\" | \"7\";\n\treadonly rootBase: string;\n\t/** Schema object to the base URI it was indexed under. */\n\treadonly baseOf = new Map<object, string>();\n\t/** URI (with a `#name` fragment) of every `$dynamicAnchor`, to its schema. */\n\treadonly dynamicAnchors = new Map<string, SchemaObject>();\n\tprivate readonly root: JsonSchema;\n\tprivate readonly options: CompileOptions;\n\tprivate readonly registry = new Map<string, JsonSchema>();\n\tprivate readonly rejectUnresolved: boolean;\n\t/** The references that named nothing and resolved to `false` (with `unresolvedRefs: \"reject\"`). */\n\tprivate readonly unresolved = new Set<string>();\n\tprivate readonly resources = new Map<string, unknown>();\n\tprivate readonly anchors = new Map<string, SchemaObject>();\n\tprivate indexed = false;\n\n\tconstructor(root: JsonSchema, options: CompileOptions) {\n\t\tthis.draft = options.draft === \"7\" ? \"7\" : \"2020-12\";\n\t\tthis.root = root;\n\t\tthis.options = options;\n\t\tthis.rejectUnresolved = options.unresolvedRefs === \"reject\";\n\t\tthis.rootBase = isSchemaObject(root) ? this.baseFor(root, DEFAULT_BASE) : DEFAULT_BASE;\n\t}\n\n\t/**\n\t * The base URI of `node`, whose parent has `base`: its `$id` changes it, unless draft 7 makes the `$id` inert. Once\n\t * the schema is indexed, the indexed base (a resolved reference target already has its own).\n\t */\n\tbaseFor(node: SchemaObject, base: string): string {\n\t\tconst indexed = this.baseOf.get(node);\n\t\tif (indexed !== undefined) return indexed;\n\t\tconst id = node.$id;\n\t\tif (typeof id !== \"string\") return base;\n\t\tif (this.draft === \"7\" && (typeof node.$ref === \"string\" || id.startsWith(\"#\"))) return base;\n\t\treturn documentOf(resolveUri(id, base));\n\t}\n\n\t/** Indexes the root schema and the registered documents' URIs (once). */\n\tensureIndexed(): void {\n\t\tif (this.indexed) return;\n\t\tthis.indexed = true;\n\t\tfor (const [uri, schema] of Object.entries(this.options.schemas ?? {})) {\n\t\t\tthis.registry.set(documentOf(resolveUri(uri, DEFAULT_BASE)), schema);\n\t\t}\n\t\tthis.resources.set(DEFAULT_BASE, this.root);\n\t\tthis.index(this.root, DEFAULT_BASE);\n\t}\n\n\t/**\n\t * The schema a reference names, with the base URI it compiles under. Throws if it names nothing, or, with\n\t * `unresolvedRefs: \"reject\"`, resolves to `false`, the schema no value passes.\n\t */\n\tresolve(ref: string, base: string): Resolved {\n\t\tthis.ensureIndexed();\n\t\tif (!this.rejectUnresolved) return this.find(ref, base);\n\t\ttry {\n\t\t\treturn this.find(ref, base);\n\t\t} catch {\n\t\t\tthis.unresolved.add(resolveUri(ref, base));\n\t\t\treturn { node: false, base };\n\t\t}\n\t}\n\n\t/** Whether `ref`, from `base`, named nothing and resolved to `false`. */\n\tisUnresolved(ref: string, base: string): boolean {\n\t\treturn this.unresolved.has(resolveUri(ref, base));\n\t}\n\n\tprivate find(ref: string, base: string): Resolved {\n\t\tconst uri = resolveUri(ref, base);\n\t\tconst doc = documentOf(uri);\n\t\tconst fragment = uri.length > doc.length ? decodeURIComponent(uri.slice(doc.length + 1)) : \"\";\n\t\tconst root = this.resources.has(doc) ? this.resources.get(doc) : this.load(doc);\n\t\tif (root === undefined) throw new Error(`Unresolved $ref ${ref} (no schema registered for ${doc})`);\n\t\tif (fragment === \"\") return { node: root, base: doc };\n\t\tif (fragment.startsWith(\"/\")) return this.pointer(root, doc, fragment);\n\t\tconst target = this.anchors.get(`${doc}#${fragment}`);\n\t\tif (target === undefined) throw new Error(`Unresolved $ref ${ref} (no anchor ${fragment} in ${doc})`);\n\t\treturn { node: target, base: this.baseOf.get(target) ?? doc };\n\t}\n\n\t/** Whether `node` is the root of the schema resource at `base`. */\n\tisResource(node: object, base: string): boolean {\n\t\tthis.ensureIndexed();\n\t\treturn this.resources.get(base) === node;\n\t}\n\n\t// Walks only keywords that hold subschemas: an `$id` inside `enum`, `const` or an unknown keyword names nothing.\n\tprivate index(node: unknown, base: string): void {\n\t\tif (!isSchemaObject(node) || this.baseOf.has(node)) return;\n\t\tlet b = base;\n\t\t// In draft 7 a `$ref` makes every sibling keyword, `$id` included, inert.\n\t\tconst refOnly = this.draft === \"7\" && typeof node.$ref === \"string\";\n\t\tif (!refOnly && typeof node.$id === \"string\") {\n\t\t\tif (this.draft === \"7\" && node.$id.startsWith(\"#\")) this.anchors.set(documentOf(b) + node.$id, node);\n\t\t\telse {\n\t\t\t\tconst uri = resolveUri(node.$id, b);\n\t\t\t\tb = documentOf(uri);\n\t\t\t\tthis.resources.set(b, node);\n\t\t\t\tif (uri.length > b.length + 1) this.anchors.set(uri, node);\n\t\t\t}\n\t\t}\n\t\tthis.baseOf.set(node, b);\n\t\tif (refOnly) return;\n\t\tif (this.draft !== \"7\") {\n\t\t\tif (typeof node.$anchor === \"string\") this.anchors.set(`${b}#${node.$anchor}`, node);\n\t\t\tif (typeof node.$dynamicAnchor === \"string\") {\n\t\t\t\tthis.anchors.set(`${b}#${node.$dynamicAnchor}`, node);\n\t\t\t\tthis.dynamicAnchors.set(`${b}#${node.$dynamicAnchor}`, node);\n\t\t\t}\n\t\t}\n\t\tfor (const key of SCHEMA_KEYWORDS) this.index(node[key], b);\n\t\tfor (const key of MAP_KEYWORDS) {\n\t\t\tconst map = node[key];\n\t\t\tif (isSchemaObject(map)) for (const k of Object.keys(map)) this.index(map[k], b);\n\t\t}\n\t\tfor (const key of ARRAY_KEYWORDS) {\n\t\t\tconst list = node[key];\n\t\t\tif (Array.isArray(list)) for (const s of list) this.index(s, b);\n\t\t}\n\t}\n\n\tprivate load(doc: string): JsonSchema | undefined {\n\t\tconst schema = this.registry.get(doc);\n\t\tif (schema === undefined) return undefined;\n\t\tthis.resources.set(doc, schema);\n\t\tthis.index(schema, doc);\n\t\treturn schema;\n\t}\n\n\tprivate pointer(root: unknown, base: string, fragment: string): Resolved {\n\t\tlet node = root;\n\t\tlet b = base;\n\t\tfor (const raw of fragment.split(\"/\").slice(1)) {\n\t\t\tconst key = raw.replace(/~1/g, \"/\").replace(/~0/g, \"~\");\n\t\t\tnode =\n\t\t\t\ttypeof node === \"object\" && node !== null && Object.hasOwn(node, key)\n\t\t\t\t\t? (node as SchemaObject)[key]\n\t\t\t\t\t: undefined;\n\t\t\tif (node === undefined) throw new Error(`Unresolved $ref #${fragment} in ${base}`);\n\t\t\tif (typeof node === \"object\" && node !== null) b = this.baseOf.get(node) ?? b;\n\t\t}\n\t\treturn { node, base: b };\n\t}\n}\n\nconst SCHEMA_KEYWORDS = [\n\t\"additionalProperties\",\n\t\"propertyNames\",\n\t\"contains\",\n\t\"not\",\n\t\"if\",\n\t\"then\",\n\t\"else\",\n\t\"unevaluatedProperties\",\n\t\"unevaluatedItems\",\n\t\"additionalItems\",\n\t\"contentSchema\",\n\t\"items\",\n];\n\nconst MAP_KEYWORDS = [\"properties\", \"patternProperties\", \"$defs\", \"definitions\", \"dependentSchemas\", \"dependencies\"];\n\nconst ARRAY_KEYWORDS = [\"allOf\", \"anyOf\", \"oneOf\", \"prefixItems\", \"items\"];\n\n/** Whether `value` is a schema object (not `true`/`false`, an array or a primitive). */\nexport function isSchemaObject(value: unknown): value is SchemaObject {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/** `ref` resolved against `base`. URNs and other bases a URL parser rejects take fragment-only references. */\nexport function resolveUri(ref: string, base: string): string {\n\ttry {\n\t\treturn new URL(ref, base).href;\n\t} catch {\n\t\treturn ref.startsWith(\"#\") ? documentOf(base) + ref : ref;\n\t}\n}\n\n/** `uri` without its fragment. */\nexport function documentOf(uri: string): string {\n\tconst hash = uri.indexOf(\"#\");\n\treturn hash < 0 ? uri : uri.slice(0, hash);\n}\n"]}
|
package/dist/shared.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The regular expression of a `pattern` source, shared by source. Patterns are ECMAScript regular expressions with
|
|
3
|
+
* the `u` flag; a source that is invalid with it (some older schemas escape characters `u` rejects) compiles
|
|
4
|
+
* without it.
|
|
5
|
+
*/
|
|
6
|
+
export declare function sharedRegex(source: string): RegExp;
|
|
7
|
+
/** The number of cached regular expressions (for tests). */
|
|
8
|
+
export declare function sharedRegexCount(): number;
|
|
9
|
+
//# sourceMappingURL=shared.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../src/shared.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAYlD;AAQD,4DAA4D;AAC5D,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC"}
|
package/dist/shared.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Regular expressions shared across all compiled schemas while one still uses them: tool schemas repeat a few
|
|
2
|
+
// patterns many times, and a RegExp is large once it has run. The cache holds its values weakly and drops an entry
|
|
3
|
+
// once its RegExp is collected, so it keeps nothing alive.
|
|
4
|
+
/**
|
|
5
|
+
* The regular expression of a `pattern` source, shared by source. Patterns are ECMAScript regular expressions with
|
|
6
|
+
* the `u` flag; a source that is invalid with it (some older schemas escape characters `u` rejects) compiles
|
|
7
|
+
* without it.
|
|
8
|
+
*/
|
|
9
|
+
export function sharedRegex(source) {
|
|
10
|
+
const hit = regexes.get(source)?.deref();
|
|
11
|
+
if (hit !== undefined)
|
|
12
|
+
return hit;
|
|
13
|
+
let re;
|
|
14
|
+
try {
|
|
15
|
+
re = new RegExp(source, "u");
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
re = new RegExp(source);
|
|
19
|
+
}
|
|
20
|
+
regexes.set(source, new WeakRef(re));
|
|
21
|
+
regexCleanup.register(re, source);
|
|
22
|
+
return re;
|
|
23
|
+
}
|
|
24
|
+
const regexes = new Map();
|
|
25
|
+
const regexCleanup = new FinalizationRegistry((source) => {
|
|
26
|
+
if (regexes.get(source)?.deref() === undefined)
|
|
27
|
+
regexes.delete(source);
|
|
28
|
+
});
|
|
29
|
+
/** The number of cached regular expressions (for tests). */
|
|
30
|
+
export function sharedRegexCount() {
|
|
31
|
+
return regexes.size;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=shared.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.js","sourceRoot":"","sources":["../src/shared.ts"],"names":[],"mappings":"AAAA,8GAA8G;AAC9G,mHAAmH;AACnH,2DAA2D;AAE3D;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,MAAc;IACzC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC;IACzC,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,GAAG,CAAC;IAClC,IAAI,EAAU,CAAC;IACf,IAAI,CAAC;QACJ,EAAE,GAAG,IAAI,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC9B,CAAC;IAAC,MAAM,CAAC;QACR,EAAE,GAAG,IAAI,MAAM,CAAC,MAAM,CAAC,CAAC;IACzB,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;IACrC,YAAY,CAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;IAClC,OAAO,EAAE,CAAC;AACX,CAAC;AAED,MAAM,OAAO,GAAG,IAAI,GAAG,EAA2B,CAAC;AAEnD,MAAM,YAAY,GAAG,IAAI,oBAAoB,CAAS,CAAC,MAAM,EAAE,EAAE;IAChE,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,KAAK,SAAS;QAAE,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;AACxE,CAAC,CAAC,CAAC;AAEH,4DAA4D;AAC5D,MAAM,UAAU,gBAAgB;IAC/B,OAAO,OAAO,CAAC,IAAI,CAAC;AACrB,CAAC","sourcesContent":["// Regular expressions shared across all compiled schemas while one still uses them: tool schemas repeat a few\n// patterns many times, and a RegExp is large once it has run. The cache holds its values weakly and drops an entry\n// once its RegExp is collected, so it keeps nothing alive.\n\n/**\n * The regular expression of a `pattern` source, shared by source. Patterns are ECMAScript regular expressions with\n * the `u` flag; a source that is invalid with it (some older schemas escape characters `u` rejects) compiles\n * without it.\n */\nexport function sharedRegex(source: string): RegExp {\n\tconst hit = regexes.get(source)?.deref();\n\tif (hit !== undefined) return hit;\n\tlet re: RegExp;\n\ttry {\n\t\tre = new RegExp(source, \"u\");\n\t} catch {\n\t\tre = new RegExp(source);\n\t}\n\tregexes.set(source, new WeakRef(re));\n\tregexCleanup.register(re, source);\n\treturn re;\n}\n\nconst regexes = new Map<string, WeakRef<RegExp>>();\n\nconst regexCleanup = new FinalizationRegistry<string>((source) => {\n\tif (regexes.get(source)?.deref() === undefined) regexes.delete(source);\n});\n\n/** The number of cached regular expressions (for tests). */\nexport function sharedRegexCount(): number {\n\treturn regexes.size;\n}\n"]}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Static } from "./builders.ts";
|
|
2
|
+
/** The value type of a TypeBox schema type by its `'~kind'`, or its `Type.Unsafe` type; `unknown` for other kinds. */
|
|
3
|
+
export type TypeBoxStatic<S> = S extends {
|
|
4
|
+
readonly "~kind": infer K;
|
|
5
|
+
} ? KindStatic<S, K> : S extends {
|
|
6
|
+
readonly "~unsafe": infer T;
|
|
7
|
+
} ? T : unknown;
|
|
8
|
+
interface Primitives {
|
|
9
|
+
String: string;
|
|
10
|
+
Number: number;
|
|
11
|
+
Integer: number;
|
|
12
|
+
Boolean: boolean;
|
|
13
|
+
Null: null;
|
|
14
|
+
Unknown: unknown;
|
|
15
|
+
Any: any;
|
|
16
|
+
}
|
|
17
|
+
type KindStatic<S, K> = K extends keyof Primitives ? Primitives[K] : K extends "Literal" ? S extends {
|
|
18
|
+
readonly const: infer V;
|
|
19
|
+
} ? V : unknown : K extends "Array" ? S extends {
|
|
20
|
+
readonly items: infer I;
|
|
21
|
+
} ? Static<I>[] : unknown : K extends "Object" ? S extends {
|
|
22
|
+
readonly properties: infer P;
|
|
23
|
+
} ? ObjectStatic<P> : unknown : K extends "Union" ? S extends {
|
|
24
|
+
readonly anyOf: readonly (infer B)[];
|
|
25
|
+
} ? Static<B> : unknown : K extends "Record" ? S extends {
|
|
26
|
+
readonly patternProperties: infer P;
|
|
27
|
+
} ? Record<RecordKey<keyof P>, Static<P[keyof P]>> : unknown : unknown;
|
|
28
|
+
type Optional = {
|
|
29
|
+
readonly "~optional": true;
|
|
30
|
+
};
|
|
31
|
+
type Readonly = {
|
|
32
|
+
readonly "~readonly": true;
|
|
33
|
+
};
|
|
34
|
+
type ObjectStatic<P> = keyof P extends never ? object : Simplify<{
|
|
35
|
+
-readonly [K in keyof P as P[K] extends Optional | Readonly ? never : K]: Static<P[K]>;
|
|
36
|
+
} & {
|
|
37
|
+
-readonly [K in keyof P as P[K] extends Optional ? (P[K] extends Readonly ? never : K) : never]?: Static<P[K]>;
|
|
38
|
+
} & {
|
|
39
|
+
readonly [K in keyof P as P[K] extends Readonly ? (P[K] extends Optional ? never : K) : never]: Static<P[K]>;
|
|
40
|
+
} & {
|
|
41
|
+
readonly [K in keyof P as P[K] extends Optional ? (P[K] extends Readonly ? K : never) : never]?: Static<P[K]>;
|
|
42
|
+
}>;
|
|
43
|
+
type Simplify<T> = {
|
|
44
|
+
[K in keyof T]: T[K];
|
|
45
|
+
} & {};
|
|
46
|
+
type RecordKey<Pattern> = Pattern extends "^-?(?:0|[1-9][0-9]*)$" | "^-?(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?$" ? number : string;
|
|
47
|
+
export {};
|
|
48
|
+
//# sourceMappingURL=typebox-static.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"typebox-static.d.ts","sourceRoot":"","sources":["../src/typebox-static.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// The static types of TypeBox-built schemas, read from the type shapes TypeBox 1.x declares (`'~kind'`, `'~optional'`,
|
|
2
|
+
// `'~readonly'`, `'~unsafe'`) without importing TypeBox, so `Static` also types tool arguments of TypeBox schemas.
|
|
3
|
+
// Only the common kinds are mapped; every other kind (Enum, Tuple, Intersect, Cyclic, Ref, ...) is `unknown`.
|
|
4
|
+
export {};
|
|
5
|
+
//# sourceMappingURL=typebox-static.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"typebox-static.js","sourceRoot":"","sources":["../src/typebox-static.ts"],"names":[],"mappings":"AAAA,uHAAuH;AACvH,mHAAmH;AACnH,8GAA8G","sourcesContent":["// The static types of TypeBox-built schemas, read from the type shapes TypeBox 1.x declares (`'~kind'`, `'~optional'`,\n// `'~readonly'`, `'~unsafe'`) without importing TypeBox, so `Static` also types tool arguments of TypeBox schemas.\n// Only the common kinds are mapped; every other kind (Enum, Tuple, Intersect, Cyclic, Ref, ...) is `unknown`.\n\nimport type { Static } from \"./builders.ts\";\n\n/** The value type of a TypeBox schema type by its `'~kind'`, or its `Type.Unsafe` type; `unknown` for other kinds. */\nexport type TypeBoxStatic<S> = S extends { readonly \"~kind\": infer K }\n\t? KindStatic<S, K>\n\t: S extends { readonly \"~unsafe\": infer T }\n\t\t? T\n\t\t: unknown;\n\ninterface Primitives {\n\tString: string;\n\tNumber: number;\n\tInteger: number;\n\tBoolean: boolean;\n\tNull: null;\n\tUnknown: unknown;\n\tAny: any;\n}\n\ntype KindStatic<S, K> = K extends keyof Primitives\n\t? Primitives[K]\n\t: K extends \"Literal\"\n\t\t? S extends { readonly const: infer V }\n\t\t\t? V\n\t\t\t: unknown\n\t\t: K extends \"Array\"\n\t\t\t? S extends { readonly items: infer I }\n\t\t\t\t? Static<I>[]\n\t\t\t\t: unknown\n\t\t\t: K extends \"Object\"\n\t\t\t\t? S extends { readonly properties: infer P }\n\t\t\t\t\t? ObjectStatic<P>\n\t\t\t\t\t: unknown\n\t\t\t\t: K extends \"Union\"\n\t\t\t\t\t? S extends { readonly anyOf: readonly (infer B)[] }\n\t\t\t\t\t\t? Static<B>\n\t\t\t\t\t\t: unknown\n\t\t\t\t\t: K extends \"Record\"\n\t\t\t\t\t\t? S extends { readonly patternProperties: infer P }\n\t\t\t\t\t\t\t? Record<RecordKey<keyof P>, Static<P[keyof P]>>\n\t\t\t\t\t\t\t: unknown\n\t\t\t\t\t\t: unknown;\n\ntype Optional = { readonly \"~optional\": true };\n\ntype Readonly = { readonly \"~readonly\": true };\n\n// As TypeBox: no properties is `object`; `'~optional'` properties may be omitted, `'~readonly'` ones are readonly.\ntype ObjectStatic<P> = keyof P extends never\n\t? object\n\t: Simplify<\n\t\t\t{ -readonly [K in keyof P as P[K] extends Optional | Readonly ? never : K]: Static<P[K]> } & {\n\t\t\t\t-readonly [K in keyof P as P[K] extends Optional ? (P[K] extends Readonly ? never : K) : never]?: Static<\n\t\t\t\t\tP[K]\n\t\t\t\t>;\n\t\t\t} & {\n\t\t\t\treadonly [K in keyof P as P[K] extends Readonly ? (P[K] extends Optional ? never : K) : never]: Static<\n\t\t\t\t\tP[K]\n\t\t\t\t>;\n\t\t\t} & {\n\t\t\t\treadonly [K in keyof P as P[K] extends Optional ? (P[K] extends Readonly ? K : never) : never]?: Static<\n\t\t\t\t\tP[K]\n\t\t\t\t>;\n\t\t\t}\n\t\t>;\n\ntype Simplify<T> = { [K in keyof T]: T[K] } & {};\n\n// TypeBox's key patterns for integer and number keys; every other pattern types its keys as strings.\ntype RecordKey<Pattern> = Pattern extends \"^-?(?:0|[1-9][0-9]*)$\" | \"^-?(?:0|[1-9][0-9]*)(?:\\\\.[0-9]+)?$\"\n\t? number\n\t: string;\n"]}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A JSON Schema: `true`, `false` or a schema object. Schemas come from tool definitions, MCP servers and other
|
|
3
|
+
* untrusted sources, so compile() reads every keyword defensively instead of trusting a static shape.
|
|
4
|
+
*/
|
|
5
|
+
export type JsonSchema = boolean | object;
|
|
6
|
+
/** A format check: whether `value` (a string) has the format. */
|
|
7
|
+
export type FormatCheck = (value: string) => boolean;
|
|
8
|
+
/** Options of compile(). */
|
|
9
|
+
export interface CompileOptions {
|
|
10
|
+
/** The draft whose semantics apply: "2020-12" (default) or "7". */
|
|
11
|
+
readonly draft?: "2020-12" | "7";
|
|
12
|
+
/**
|
|
13
|
+
* Format name to check. Without it `format` is an annotation, as draft 2020-12 specifies. The standard formats are
|
|
14
|
+
* in `@earendil-works/pi-schema/formats`.
|
|
15
|
+
*/
|
|
16
|
+
readonly formats?: Readonly<Record<string, FormatCheck>>;
|
|
17
|
+
/** Schema documents by URI, for `$ref`s to other documents. */
|
|
18
|
+
readonly schemas?: Readonly<Record<string, JsonSchema>>;
|
|
19
|
+
/** Add `coerce()` and `parse()` to the validator, for loosely typed input (see the README's Coercion section). */
|
|
20
|
+
readonly coerce?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* What a `$ref` that names nothing (a missing definition, a document not in `schemas`) does: `"throw"` (default)
|
|
23
|
+
* makes compile() throw; `"reject"` compiles it as `false`, so only values at that place fail, with the error
|
|
24
|
+
* "unresolved reference".
|
|
25
|
+
*/
|
|
26
|
+
readonly unresolvedRefs?: "throw" | "reject";
|
|
27
|
+
}
|
|
28
|
+
/** The result of `parse()`: the coerced value when it is valid, else the errors of the coerced value. */
|
|
29
|
+
export type ParseResult<T> = {
|
|
30
|
+
readonly ok: true;
|
|
31
|
+
readonly value: T;
|
|
32
|
+
} | {
|
|
33
|
+
readonly ok: false;
|
|
34
|
+
readonly errors: ValidationError[];
|
|
35
|
+
};
|
|
36
|
+
/** One failure of a value: where it is (a JSON Pointer, "" for the value itself), the failing keyword and a message. */
|
|
37
|
+
export interface ValidationError {
|
|
38
|
+
readonly path: string;
|
|
39
|
+
readonly keyword: string;
|
|
40
|
+
readonly message: string;
|
|
41
|
+
}
|
|
42
|
+
/** A schema object as compile() reads it: any keyword, of any value, checked where it is used. */
|
|
43
|
+
export type SchemaObject = {
|
|
44
|
+
readonly [keyword: string]: unknown;
|
|
45
|
+
};
|
|
46
|
+
/** A compiled boolean check. */
|
|
47
|
+
export type Check = (value: unknown) => boolean;
|
|
48
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,MAAM,CAAC;AAE1C,iEAAiE;AACjE,MAAM,MAAM,WAAW,GAAG,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;AAErD,4BAA4B;AAC5B,MAAM,WAAW,cAAc;IAC9B,mEAAmE;IACnE,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,GAAG,GAAG,CAAC;IACjC;;;OAGG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC;IACzD,+DAA+D;IAC/D,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC;IACxD,kHAAkH;IAClH,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;OAIG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;CAC7C;AAED,yGAAyG;AACzG,MAAM,MAAM,WAAW,CAAC,CAAC,IACtB;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAA;CAAE,GACxC;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,eAAe,EAAE,CAAA;CAAE,CAAC;AAE9D,wHAAwH;AACxH,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CACzB;AAED,kGAAkG;AAClG,MAAM,MAAM,YAAY,GAAG;IAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,CAAC;AAEnE,gCAAgC;AAChC,MAAM,MAAM,KAAK,GAAG,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,uGAAuG","sourcesContent":["// Public types: the schema accepted by compile(), its options, and the errors a failed value produces.\n\n/**\n * A JSON Schema: `true`, `false` or a schema object. Schemas come from tool definitions, MCP servers and other\n * untrusted sources, so compile() reads every keyword defensively instead of trusting a static shape.\n */\nexport type JsonSchema = boolean | object;\n\n/** A format check: whether `value` (a string) has the format. */\nexport type FormatCheck = (value: string) => boolean;\n\n/** Options of compile(). */\nexport interface CompileOptions {\n\t/** The draft whose semantics apply: \"2020-12\" (default) or \"7\". */\n\treadonly draft?: \"2020-12\" | \"7\";\n\t/**\n\t * Format name to check. Without it `format` is an annotation, as draft 2020-12 specifies. The standard formats are\n\t * in `@earendil-works/pi-schema/formats`.\n\t */\n\treadonly formats?: Readonly<Record<string, FormatCheck>>;\n\t/** Schema documents by URI, for `$ref`s to other documents. */\n\treadonly schemas?: Readonly<Record<string, JsonSchema>>;\n\t/** Add `coerce()` and `parse()` to the validator, for loosely typed input (see the README's Coercion section). */\n\treadonly coerce?: boolean;\n\t/**\n\t * What a `$ref` that names nothing (a missing definition, a document not in `schemas`) does: `\"throw\"` (default)\n\t * makes compile() throw; `\"reject\"` compiles it as `false`, so only values at that place fail, with the error\n\t * \"unresolved reference\".\n\t */\n\treadonly unresolvedRefs?: \"throw\" | \"reject\";\n}\n\n/** The result of `parse()`: the coerced value when it is valid, else the errors of the coerced value. */\nexport type ParseResult<T> =\n\t| { readonly ok: true; readonly value: T }\n\t| { readonly ok: false; readonly errors: ValidationError[] };\n\n/** One failure of a value: where it is (a JSON Pointer, \"\" for the value itself), the failing keyword and a message. */\nexport interface ValidationError {\n\treadonly path: string;\n\treadonly keyword: string;\n\treadonly message: string;\n}\n\n/** A schema object as compile() reads it: any keyword, of any value, checked where it is used. */\nexport type SchemaObject = { readonly [keyword: string]: unknown };\n\n/** A compiled boolean check. */\nexport type Check = (value: unknown) => boolean;\n"]}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Static } from "./builders.ts";
|
|
2
|
+
import type { Check, CompileOptions, JsonSchema, ParseResult, ValidationError } from "./types.ts";
|
|
3
|
+
/**
|
|
4
|
+
* Compiles `schema` into a Validator. Throws if the schema is malformed or a `$ref` names nothing (register other
|
|
5
|
+
* documents with `options.schemas`). The schema is read, never modified. With `coerce: true` the validator also
|
|
6
|
+
* coerces and parses loosely typed input.
|
|
7
|
+
*/
|
|
8
|
+
export declare function compile<S extends JsonSchema>(schema: S, options: CompileOptions & {
|
|
9
|
+
readonly coerce: true;
|
|
10
|
+
}): CoercingValidator<Static<S>>;
|
|
11
|
+
export declare function compile<S extends JsonSchema>(schema: S, options?: CompileOptions): Validator<Static<S>>;
|
|
12
|
+
/** A compiled schema whose valid values have type `T` (`unknown` unless the schema was built with the builders). */
|
|
13
|
+
export declare class Validator<T = unknown> {
|
|
14
|
+
/** Whether `value` is valid. A plain function: it may be passed around without its validator. */
|
|
15
|
+
readonly check: (value: unknown) => value is T;
|
|
16
|
+
protected readonly schema: JsonSchema;
|
|
17
|
+
protected readonly options: CompileOptions;
|
|
18
|
+
private explainer;
|
|
19
|
+
constructor(schema: JsonSchema, options: CompileOptions, check: Check);
|
|
20
|
+
/**
|
|
21
|
+
* Why `value` is invalid: one error per failing keyword, `[]` when it is valid. Built on the first failure and
|
|
22
|
+
* kept, so a validator whose values always pass never pays for it.
|
|
23
|
+
*/
|
|
24
|
+
errors(value: unknown): ValidationError[];
|
|
25
|
+
}
|
|
26
|
+
/** A validator compiled with `coerce: true`: it also coerces loosely typed input towards the schema. */
|
|
27
|
+
export declare class CoercingValidator<T = unknown> extends Validator<T> {
|
|
28
|
+
private coercer;
|
|
29
|
+
/**
|
|
30
|
+
* `value` coerced towards the schema: numeric strings to numbers, `"true"` to `true`, a single value to a one-item
|
|
31
|
+
* array, an optional `null` dropped, the first union branch the value converts to (the README lists every rule).
|
|
32
|
+
* Only the changed paths are copied; `value` comes back itself when nothing changes, and is never modified. Values
|
|
33
|
+
* that cannot be coerced stay as they are. The rules are built on the first call.
|
|
34
|
+
*/
|
|
35
|
+
coerce(value: unknown): unknown;
|
|
36
|
+
/** Coerces `value`, then checks it: the coerced value when it is valid, else its errors. */
|
|
37
|
+
parse(value: unknown): ParseResult<T>;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=validator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../src/validator.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAI5C,OAAO,KAAK,EAAE,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElG;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,CAAC,SAAS,UAAU,EAC3C,MAAM,EAAE,CAAC,EACT,OAAO,EAAE,cAAc,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAA;CAAE,GACjD,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;AAChC,wBAAgB,OAAO,CAAC,CAAC,SAAS,UAAU,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;AAazG,oHAAoH;AACpH,qBAAa,SAAS,CAAC,CAAC,GAAG,OAAO;IACjC,iGAAiG;IACjG,QAAQ,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,KAAK,IAAI,CAAC,CAAC;IAC/C,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IACtC,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IAC3C,OAAO,CAAC,SAAS,CAAoC;IAErD,YAAY,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,KAAK,EAKpE;IAED;;;OAGG;IACH,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,eAAe,EAAE,CAIxC;CACD;AAED,wGAAwG;AACxG,qBAAa,iBAAiB,CAAC,CAAC,GAAG,OAAO,CAAE,SAAQ,SAAS,CAAC,CAAC,CAAC;IAC/D,OAAO,CAAC,OAAO,CAAkC;IAEjD;;;;;OAKG;IACH,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAG9B;IAED,4FAA4F;IAC5F,KAAK,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,CAAC,CAAC,CAAC,CAGpC;CACD"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// compile(): a schema to a Validator, whose `check` is the fast boolean path and whose `errors` explains failures.
|
|
2
|
+
import { Coercer } from "./coerce.js";
|
|
3
|
+
import { Compiler, RESTART } from "./compiler.js";
|
|
4
|
+
import { Explainer } from "./explain.js";
|
|
5
|
+
export function compile(schema, options = NO_OPTIONS) {
|
|
6
|
+
const check = compileCheck(schema, options);
|
|
7
|
+
return options.coerce === true
|
|
8
|
+
? new CoercingValidator(schema, options, check)
|
|
9
|
+
: new Validator(schema, options, check);
|
|
10
|
+
}
|
|
11
|
+
const NO_OPTIONS = Object.freeze({});
|
|
12
|
+
/** A compiled schema whose valid values have type `T` (`unknown` unless the schema was built with the builders). */
|
|
13
|
+
export class Validator {
|
|
14
|
+
/** Whether `value` is valid. A plain function: it may be passed around without its validator. */
|
|
15
|
+
check;
|
|
16
|
+
schema;
|
|
17
|
+
options;
|
|
18
|
+
explainer = undefined;
|
|
19
|
+
constructor(schema, options, check) {
|
|
20
|
+
// A compiled check is exactly the test that a value has the schema's static type.
|
|
21
|
+
this.check = check;
|
|
22
|
+
this.schema = schema;
|
|
23
|
+
this.options = options;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Why `value` is invalid: one error per failing keyword, `[]` when it is valid. Built on the first failure and
|
|
27
|
+
* kept, so a validator whose values always pass never pays for it.
|
|
28
|
+
*/
|
|
29
|
+
errors(value) {
|
|
30
|
+
if (this.check(value))
|
|
31
|
+
return [];
|
|
32
|
+
this.explainer ??= new Explainer(compiler(this.schema, this.options), this.schema);
|
|
33
|
+
return this.explainer.errors(value);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** A validator compiled with `coerce: true`: it also coerces loosely typed input towards the schema. */
|
|
37
|
+
export class CoercingValidator extends Validator {
|
|
38
|
+
coercer = undefined;
|
|
39
|
+
/**
|
|
40
|
+
* `value` coerced towards the schema: numeric strings to numbers, `"true"` to `true`, a single value to a one-item
|
|
41
|
+
* array, an optional `null` dropped, the first union branch the value converts to (the README lists every rule).
|
|
42
|
+
* Only the changed paths are copied; `value` comes back itself when nothing changes, and is never modified. Values
|
|
43
|
+
* that cannot be coerced stay as they are. The rules are built on the first call.
|
|
44
|
+
*/
|
|
45
|
+
coerce(value) {
|
|
46
|
+
this.coercer ??= new Coercer(compiler(this.schema, this.options), this.schema);
|
|
47
|
+
return this.coercer.coerce(value);
|
|
48
|
+
}
|
|
49
|
+
/** Coerces `value`, then checks it: the coerced value when it is valid, else its errors. */
|
|
50
|
+
parse(value) {
|
|
51
|
+
const coerced = this.coerce(value);
|
|
52
|
+
return this.check(coerced) ? { ok: true, value: coerced } : { ok: false, errors: this.errors(coerced) };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function compileCheck(schema, options) {
|
|
56
|
+
return compiler(schema, options).root();
|
|
57
|
+
}
|
|
58
|
+
// Most schemas compile without tracking the dynamic scope; one with a bookended $dynamicRef compiles again with it.
|
|
59
|
+
function compiler(schema, options) {
|
|
60
|
+
const plain = new Compiler(schema, options, false);
|
|
61
|
+
try {
|
|
62
|
+
plain.root();
|
|
63
|
+
return plain;
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
if (error !== RESTART)
|
|
67
|
+
throw error;
|
|
68
|
+
return new Compiler(schema, options, true);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=validator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validator.js","sourceRoot":"","sources":["../src/validator.ts"],"names":[],"mappings":"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","sourcesContent":["// compile(): a schema to a Validator, whose `check` is the fast boolean path and whose `errors` explains failures.\n\nimport type { Static } from \"./builders.ts\";\nimport { Coercer } from \"./coerce.ts\";\nimport { Compiler, RESTART } from \"./compiler.ts\";\nimport { Explainer } from \"./explain.ts\";\nimport type { Check, CompileOptions, JsonSchema, ParseResult, ValidationError } from \"./types.ts\";\n\n/**\n * Compiles `schema` into a Validator. Throws if the schema is malformed or a `$ref` names nothing (register other\n * documents with `options.schemas`). The schema is read, never modified. With `coerce: true` the validator also\n * coerces and parses loosely typed input.\n */\nexport function compile<S extends JsonSchema>(\n\tschema: S,\n\toptions: CompileOptions & { readonly coerce: true },\n): CoercingValidator<Static<S>>;\nexport function compile<S extends JsonSchema>(schema: S, options?: CompileOptions): Validator<Static<S>>;\nexport function compile<S extends JsonSchema>(\n\tschema: S,\n\toptions: CompileOptions = NO_OPTIONS,\n): Validator<Static<S>> | CoercingValidator<Static<S>> {\n\tconst check = compileCheck(schema, options);\n\treturn options.coerce === true\n\t\t? new CoercingValidator(schema, options, check)\n\t\t: new Validator(schema, options, check);\n}\n\nconst NO_OPTIONS: CompileOptions = Object.freeze({});\n\n/** A compiled schema whose valid values have type `T` (`unknown` unless the schema was built with the builders). */\nexport class Validator<T = unknown> {\n\t/** Whether `value` is valid. A plain function: it may be passed around without its validator. */\n\treadonly check: (value: unknown) => value is T;\n\tprotected readonly schema: JsonSchema;\n\tprotected readonly options: CompileOptions;\n\tprivate explainer: Explainer | undefined = undefined;\n\n\tconstructor(schema: JsonSchema, options: CompileOptions, check: Check) {\n\t\t// A compiled check is exactly the test that a value has the schema's static type.\n\t\tthis.check = check as (value: unknown) => value is T;\n\t\tthis.schema = schema;\n\t\tthis.options = options;\n\t}\n\n\t/**\n\t * Why `value` is invalid: one error per failing keyword, `[]` when it is valid. Built on the first failure and\n\t * kept, so a validator whose values always pass never pays for it.\n\t */\n\terrors(value: unknown): ValidationError[] {\n\t\tif (this.check(value)) return [];\n\t\tthis.explainer ??= new Explainer(compiler(this.schema, this.options), this.schema);\n\t\treturn this.explainer.errors(value);\n\t}\n}\n\n/** A validator compiled with `coerce: true`: it also coerces loosely typed input towards the schema. */\nexport class CoercingValidator<T = unknown> extends Validator<T> {\n\tprivate coercer: Coercer | undefined = undefined;\n\n\t/**\n\t * `value` coerced towards the schema: numeric strings to numbers, `\"true\"` to `true`, a single value to a one-item\n\t * array, an optional `null` dropped, the first union branch the value converts to (the README lists every rule).\n\t * Only the changed paths are copied; `value` comes back itself when nothing changes, and is never modified. Values\n\t * that cannot be coerced stay as they are. The rules are built on the first call.\n\t */\n\tcoerce(value: unknown): unknown {\n\t\tthis.coercer ??= new Coercer(compiler(this.schema, this.options), this.schema);\n\t\treturn this.coercer.coerce(value);\n\t}\n\n\t/** Coerces `value`, then checks it: the coerced value when it is valid, else its errors. */\n\tparse(value: unknown): ParseResult<T> {\n\t\tconst coerced = this.coerce(value);\n\t\treturn this.check(coerced) ? { ok: true, value: coerced } : { ok: false, errors: this.errors(coerced) };\n\t}\n}\n\nfunction compileCheck(schema: JsonSchema, options: CompileOptions): Check {\n\treturn compiler(schema, options).root();\n}\n\n// Most schemas compile without tracking the dynamic scope; one with a bookended $dynamicRef compiles again with it.\nfunction compiler(schema: JsonSchema, options: CompileOptions): Compiler {\n\tconst plain = new Compiler(schema, options, false);\n\ttry {\n\t\tplain.root();\n\t\treturn plain;\n\t} catch (error) {\n\t\tif (error !== RESTART) throw error;\n\t\treturn new Compiler(schema, options, true);\n\t}\n}\n"]}
|