@maroonedog/luq 2.5.0 → 2.6.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 +23 -7
- package/dist/chain/slot-type-guard.js +20 -6
- package/dist/chain/slot-type-guard.mjs +20 -6
- package/dist/compile/resolve-presence.js +36 -5
- package/dist/compile/resolve-presence.mjs +36 -5
- package/dist/json-schema/assert-supported-dialect.d.ts +37 -0
- package/dist/json-schema/assert-supported-dialect.js +113 -0
- package/dist/json-schema/assert-supported-dialect.mjs +109 -0
- package/dist/json-schema/build-from-schema.d.ts +22 -3
- package/dist/json-schema/build-from-schema.js +27 -5
- package/dist/json-schema/build-from-schema.mjs +27 -5
- package/dist/json-schema/extensions/json-schema/index.d.ts +2 -2
- package/dist/json-schema/extensions/json-schema/index.js +3 -1
- package/dist/json-schema/extensions/json-schema/index.mjs +1 -1
- package/dist/json-schema/extensions/json-schema/json-schema.d.ts +2 -2
- package/dist/json-schema/extensions/json-schema/json-schema.js +3 -0
- package/dist/json-schema/extensions/json-schema/json-schema.mjs +4 -1
- package/dist/json-schema/extensions/json-schema-full-feature/index.d.ts +3 -1
- package/dist/json-schema/extensions/json-schema-full-feature/index.js +18 -1
- package/dist/json-schema/extensions/json-schema-full-feature/index.mjs +11 -0
- package/dist/json-schema/extensions/json-schema-full-feature/json-schema-full-feature.d.ts +9 -1
- package/dist/json-schema/extensions/json-schema-full-feature/json-schema-full-feature.js +9 -2
- package/dist/json-schema/extensions/json-schema-full-feature/json-schema-full-feature.mjs +9 -2
- package/dist/json-schema/index.d.ts +3 -0
- package/dist/json-schema/index.js +7 -1
- package/dist/json-schema/index.mjs +2 -0
- package/dist/json-schema/keyword-map-core.js +3 -1
- package/dist/json-schema/keyword-map-core.mjs +3 -1
- package/dist/json-schema/keyword-map.js +3 -0
- package/dist/json-schema/keyword-map.mjs +3 -0
- package/dist/json-schema/unsupported-dialect-error.d.ts +21 -0
- package/dist/json-schema/unsupported-dialect-error.js +58 -0
- package/dist/json-schema/unsupported-dialect-error.mjs +54 -0
- package/package.json +6 -3
package/README.md
CHANGED
|
@@ -42,6 +42,7 @@ compile error, not a rule that quietly never fires.
|
|
|
42
42
|
| Descending into a built-in (`"when.getTime"` on a `Date`) | compile error |
|
|
43
43
|
| A method that does not exist inside an element sub-chain | compile error |
|
|
44
44
|
| A JSON Schema keyword bound to a chain method that does not exist | compile error |
|
|
45
|
+
| A JSON Schema document declaring 2019-09 or 2020-12 | throws when the validator is built, rather than being read as Draft-07 |
|
|
45
46
|
| A documented example drifting from the API | fails CI |
|
|
46
47
|
|
|
47
48
|
That matters most when the code calling this library is generated rather than
|
|
@@ -136,13 +137,24 @@ const result = orderValidator.validate({ reference: "ab", quantity: 0 });
|
|
|
136
137
|
if (!result.valid) {
|
|
137
138
|
for (const issue of result.issues) {
|
|
138
139
|
// issue.path "reference" — where, with array indices filled in
|
|
139
|
-
// issue.code "stringMin" — which rule
|
|
140
|
+
// issue.code "stringMin" — which rule; the vocabulary is pinned
|
|
140
141
|
// issue.message — the text, overridable per call
|
|
141
142
|
// issue.severity "error" — only "error" makes the value invalid
|
|
142
143
|
}
|
|
143
144
|
}
|
|
144
145
|
```
|
|
145
146
|
|
|
147
|
+
`issue.code` is the machine-readable half: a plugin's own name by default,
|
|
148
|
+
overridden per rule with `{ code }`. The set is not a convention. The codes the
|
|
149
|
+
library reports are enumerated in `config/issue-code.lock.json`, derived from
|
|
150
|
+
the source by `npm run generate:issue-codes` and checked by
|
|
151
|
+
`npm run check:issue-code-lock`, so renaming one is a diff in a committed file
|
|
152
|
+
and a deliberate act rather than a silent break. The lock records who reports
|
|
153
|
+
each code, because some codes are shared on purpose — a missing root and a
|
|
154
|
+
missing field both report `required`. Codes a gate carries (`skip`,
|
|
155
|
+
`validateIf`) sit in a separate list: they are accepted from a caller but no
|
|
156
|
+
issue can ever carry one.
|
|
157
|
+
|
|
146
158
|
`.v(path, chain)` declares rules for one field. A path you do not declare is
|
|
147
159
|
not validated, not required and not read, so covering a type partly is a normal
|
|
148
160
|
state rather than a half-finished one.
|
|
@@ -295,7 +307,7 @@ const forStringMin = PLUGIN_MANIFEST.filter((entry) =>
|
|
|
295
307
|
| [Getting started](https://luq.dev/docs/getting-started) | the builder, defaults, `normalize`, reading a result |
|
|
296
308
|
| [Core concepts](https://luq.dev/docs/core-concepts) | field paths, slots, presence, transforms |
|
|
297
309
|
| [Plugins](https://luq.dev/plugins) | every subpath, symbol, chain method and slot |
|
|
298
|
-
| [JSON Schema](https://luq.dev/json-schema) | reading a document in, writing one back out, measured
|
|
310
|
+
| [JSON Schema](https://luq.dev/json-schema) | reading a Draft-07 document in, writing one back out, measured conformance, and why a newer-dialect document is refused |
|
|
299
311
|
| [Standard Schema](https://luq.dev/standard-schema) | tRPC, TanStack Form, Hono, react-hook-form — and what does not cross that boundary |
|
|
300
312
|
| [Benchmarks](https://luq.dev/benchmarks) | bundle size and throughput, with the method |
|
|
301
313
|
| [Luq or zod?](https://luq.dev/luq-or-zod) | when schema-first is the better answer |
|
|
@@ -311,6 +323,8 @@ Breaking changes happen in a major and nowhere else, an API being removed is
|
|
|
311
323
|
deprecated one major ahead, and each major ships with the codemod needed to
|
|
312
324
|
cross it.
|
|
313
325
|
|
|
326
|
+
- **[CHANGELOG.md](https://github.com/maroonedog/luq/blob/master/CHANGELOG.md)** — every released version, what a caller
|
|
327
|
+
sees change in each, and which ones need reading before you take them
|
|
314
328
|
- **[CONTRIBUTING.md](https://github.com/maroonedog/luq/blob/master/CONTRIBUTING.md)** — `npm run verify` is the whole
|
|
315
329
|
contract; the gates and what each one refuses
|
|
316
330
|
- **[SECURITY.md](https://github.com/maroonedog/luq/blob/master/SECURITY.md)** — reporting, zero runtime dependencies, the
|
|
@@ -320,11 +334,13 @@ cross it.
|
|
|
320
334
|
|
|
321
335
|
## About the "universal platform" goal
|
|
322
336
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
337
|
+
The first published line — `0.1.0-alpha` through `0.1.2-alpha`, which the
|
|
338
|
+
migration guide and the docs site both call "1.x" although no `1.x` was ever
|
|
339
|
+
published — described a `.luq` DSL that would generate validators for other
|
|
340
|
+
languages, against dated milestones. Those dates have passed and none of it
|
|
341
|
+
shipped, so the plan has been withdrawn rather than moved: no part of it is in
|
|
342
|
+
this package, and this release makes no claim about when any of it will exist.
|
|
343
|
+
What is in the box is the TypeScript validation library described above.
|
|
328
344
|
|
|
329
345
|
## License
|
|
330
346
|
|
|
@@ -3,16 +3,30 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.slotTypeGuard = slotTypeGuard;
|
|
4
4
|
const types_1 = require("../types");
|
|
5
5
|
const create_rule_1 = require("../plugin-kit/create-rule");
|
|
6
|
+
/**
|
|
7
|
+
* Each code is SPELLED here rather than interpolated from the slot name.
|
|
8
|
+
*
|
|
9
|
+
* `${slot}Type` produced the same six strings, and produced them nowhere a
|
|
10
|
+
* reader could find them: config/issue-code.lock.json enumerates every code
|
|
11
|
+
* the library can report, and a code that only exists once a template has been
|
|
12
|
+
* evaluated cannot be enumerated, so these six were missing from the published
|
|
13
|
+
* vocabulary while being among the most commonly reported codes there are.
|
|
14
|
+
*/
|
|
6
15
|
const SLOT_TYPES = Object.freeze({
|
|
7
|
-
string: { accepts: types_1.isString, noun: "a string" },
|
|
8
|
-
number: { accepts: types_1.isNumber, noun: "a number" },
|
|
16
|
+
string: { accepts: types_1.isString, code: "stringType", noun: "a string" },
|
|
17
|
+
number: { accepts: types_1.isNumber, code: "numberType", noun: "a number" },
|
|
9
18
|
boolean: {
|
|
10
19
|
accepts: (value) => typeof value === "boolean",
|
|
20
|
+
code: "booleanType",
|
|
11
21
|
noun: "a boolean",
|
|
12
22
|
},
|
|
13
|
-
date: {
|
|
14
|
-
|
|
15
|
-
|
|
23
|
+
date: {
|
|
24
|
+
accepts: (value) => value instanceof Date,
|
|
25
|
+
code: "dateType",
|
|
26
|
+
noun: "a Date",
|
|
27
|
+
},
|
|
28
|
+
array: { accepts: types_1.isArray, code: "arrayType", noun: "an array" },
|
|
29
|
+
object: { accepts: types_1.isPlainObject, code: "objectType", noun: "an object" },
|
|
16
30
|
});
|
|
17
31
|
/**
|
|
18
32
|
* The rule `b.<slot>` starts its chain with, or null for a slot that claims
|
|
@@ -26,7 +40,7 @@ function slotTypeGuard(slot, severity) {
|
|
|
26
40
|
if (slotType === undefined)
|
|
27
41
|
return null;
|
|
28
42
|
return (0, create_rule_1.check)({
|
|
29
|
-
code:
|
|
43
|
+
code: slotType.code,
|
|
30
44
|
severity,
|
|
31
45
|
run: (value) => value === undefined || value === null || slotType.accepts(value)
|
|
32
46
|
? types_1.PASS
|
|
@@ -1,15 +1,29 @@
|
|
|
1
1
|
import { PASS, fail, isArray, isNumber, isPlainObject, isString, } from "../types/index.mjs";
|
|
2
2
|
import { check } from "../plugin-kit/create-rule.mjs";
|
|
3
|
+
/**
|
|
4
|
+
* Each code is SPELLED here rather than interpolated from the slot name.
|
|
5
|
+
*
|
|
6
|
+
* `${slot}Type` produced the same six strings, and produced them nowhere a
|
|
7
|
+
* reader could find them: config/issue-code.lock.json enumerates every code
|
|
8
|
+
* the library can report, and a code that only exists once a template has been
|
|
9
|
+
* evaluated cannot be enumerated, so these six were missing from the published
|
|
10
|
+
* vocabulary while being among the most commonly reported codes there are.
|
|
11
|
+
*/
|
|
3
12
|
const SLOT_TYPES = Object.freeze({
|
|
4
|
-
string: { accepts: isString, noun: "a string" },
|
|
5
|
-
number: { accepts: isNumber, noun: "a number" },
|
|
13
|
+
string: { accepts: isString, code: "stringType", noun: "a string" },
|
|
14
|
+
number: { accepts: isNumber, code: "numberType", noun: "a number" },
|
|
6
15
|
boolean: {
|
|
7
16
|
accepts: (value) => typeof value === "boolean",
|
|
17
|
+
code: "booleanType",
|
|
8
18
|
noun: "a boolean",
|
|
9
19
|
},
|
|
10
|
-
date: {
|
|
11
|
-
|
|
12
|
-
|
|
20
|
+
date: {
|
|
21
|
+
accepts: (value) => value instanceof Date,
|
|
22
|
+
code: "dateType",
|
|
23
|
+
noun: "a Date",
|
|
24
|
+
},
|
|
25
|
+
array: { accepts: isArray, code: "arrayType", noun: "an array" },
|
|
26
|
+
object: { accepts: isPlainObject, code: "objectType", noun: "an object" },
|
|
13
27
|
});
|
|
14
28
|
/**
|
|
15
29
|
* The rule `b.<slot>` starts its chain with, or null for a slot that claims
|
|
@@ -23,7 +37,7 @@ export function slotTypeGuard(slot, severity) {
|
|
|
23
37
|
if (slotType === undefined)
|
|
24
38
|
return null;
|
|
25
39
|
return check({
|
|
26
|
-
code:
|
|
40
|
+
code: slotType.code,
|
|
27
41
|
severity,
|
|
28
42
|
run: (value) => value === undefined || value === null || slotType.accepts(value)
|
|
29
43
|
? PASS
|
|
@@ -38,13 +38,43 @@ function countRestrictions(rule) {
|
|
|
38
38
|
(rule.allowNull ? 0 : 1) +
|
|
39
39
|
(rule.emptyStringIsMissing ? 1 : 0));
|
|
40
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* Which of two equally restrictive rules is reported, decided on WHAT each one
|
|
43
|
+
* forbids: undefined first, then null, then the empty string.
|
|
44
|
+
*
|
|
45
|
+
* The order is declared here rather than falling out of something else. It
|
|
46
|
+
* runs from the broadest absence to the narrowest — a field that is not there
|
|
47
|
+
* at all, a field that is there holding null, a field holding "" — so the
|
|
48
|
+
* identity a caller sees names the largest thing that was missing.
|
|
49
|
+
*
|
|
50
|
+
* Nothing in this comparison reads a code. A code is a published name, pinned
|
|
51
|
+
* in config/issue-code.lock.json, and re-spelling one must not move which rule
|
|
52
|
+
* a failure is reported under; comparing the two codes made exactly that
|
|
53
|
+
* happen, silently, from a one-word rename.
|
|
54
|
+
*
|
|
55
|
+
* Two rules forbidding the identical three things are indistinguishable to
|
|
56
|
+
* every reader of the policy, so neither outranks the other and the one
|
|
57
|
+
* declared first stays. That is the only case where the order the methods were
|
|
58
|
+
* typed in is observable at all.
|
|
59
|
+
*/
|
|
60
|
+
function outranksOnRestriction(rule, incumbent) {
|
|
61
|
+
if (rule.allowUndefined !== incumbent.allowUndefined) {
|
|
62
|
+
return !rule.allowUndefined;
|
|
63
|
+
}
|
|
64
|
+
if (rule.allowNull !== incumbent.allowNull)
|
|
65
|
+
return !rule.allowNull;
|
|
66
|
+
if (rule.emptyStringIsMissing !== incumbent.emptyStringIsMissing) {
|
|
67
|
+
return rule.emptyStringIsMissing;
|
|
68
|
+
}
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
41
71
|
/**
|
|
42
72
|
* The identity a failure is reported under: the rule that forbids the most,
|
|
43
|
-
* ties broken by
|
|
73
|
+
* ties broken by which absence it forbids.
|
|
44
74
|
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
75
|
+
* Both halves of the decision read only the three presence flags, so the whole
|
|
76
|
+
* policy — not merely its flags — is independent of the order the rules
|
|
77
|
+
* arrived in. `.optional().nullable()` and `.nullable().optional()` are
|
|
48
78
|
* deep-equal, which is the property the tests assert.
|
|
49
79
|
*/
|
|
50
80
|
function selectStrictestPresence(rules) {
|
|
@@ -55,7 +85,8 @@ function selectStrictestPresence(rules) {
|
|
|
55
85
|
continue;
|
|
56
86
|
}
|
|
57
87
|
const difference = countRestrictions(rule) - countRestrictions(strictest);
|
|
58
|
-
if (difference > 0 ||
|
|
88
|
+
if (difference > 0 ||
|
|
89
|
+
(difference === 0 && outranksOnRestriction(rule, strictest))) {
|
|
59
90
|
strictest = rule;
|
|
60
91
|
}
|
|
61
92
|
}
|
|
@@ -34,13 +34,43 @@ function countRestrictions(rule) {
|
|
|
34
34
|
(rule.allowNull ? 0 : 1) +
|
|
35
35
|
(rule.emptyStringIsMissing ? 1 : 0));
|
|
36
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Which of two equally restrictive rules is reported, decided on WHAT each one
|
|
39
|
+
* forbids: undefined first, then null, then the empty string.
|
|
40
|
+
*
|
|
41
|
+
* The order is declared here rather than falling out of something else. It
|
|
42
|
+
* runs from the broadest absence to the narrowest — a field that is not there
|
|
43
|
+
* at all, a field that is there holding null, a field holding "" — so the
|
|
44
|
+
* identity a caller sees names the largest thing that was missing.
|
|
45
|
+
*
|
|
46
|
+
* Nothing in this comparison reads a code. A code is a published name, pinned
|
|
47
|
+
* in config/issue-code.lock.json, and re-spelling one must not move which rule
|
|
48
|
+
* a failure is reported under; comparing the two codes made exactly that
|
|
49
|
+
* happen, silently, from a one-word rename.
|
|
50
|
+
*
|
|
51
|
+
* Two rules forbidding the identical three things are indistinguishable to
|
|
52
|
+
* every reader of the policy, so neither outranks the other and the one
|
|
53
|
+
* declared first stays. That is the only case where the order the methods were
|
|
54
|
+
* typed in is observable at all.
|
|
55
|
+
*/
|
|
56
|
+
function outranksOnRestriction(rule, incumbent) {
|
|
57
|
+
if (rule.allowUndefined !== incumbent.allowUndefined) {
|
|
58
|
+
return !rule.allowUndefined;
|
|
59
|
+
}
|
|
60
|
+
if (rule.allowNull !== incumbent.allowNull)
|
|
61
|
+
return !rule.allowNull;
|
|
62
|
+
if (rule.emptyStringIsMissing !== incumbent.emptyStringIsMissing) {
|
|
63
|
+
return rule.emptyStringIsMissing;
|
|
64
|
+
}
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
37
67
|
/**
|
|
38
68
|
* The identity a failure is reported under: the rule that forbids the most,
|
|
39
|
-
* ties broken by
|
|
69
|
+
* ties broken by which absence it forbids.
|
|
40
70
|
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
71
|
+
* Both halves of the decision read only the three presence flags, so the whole
|
|
72
|
+
* policy — not merely its flags — is independent of the order the rules
|
|
73
|
+
* arrived in. `.optional().nullable()` and `.nullable().optional()` are
|
|
44
74
|
* deep-equal, which is the property the tests assert.
|
|
45
75
|
*/
|
|
46
76
|
function selectStrictestPresence(rules) {
|
|
@@ -51,7 +81,8 @@ function selectStrictestPresence(rules) {
|
|
|
51
81
|
continue;
|
|
52
82
|
}
|
|
53
83
|
const difference = countRestrictions(rule) - countRestrictions(strictest);
|
|
54
|
-
if (difference > 0 ||
|
|
84
|
+
if (difference > 0 ||
|
|
85
|
+
(difference === 0 && outranksOnRestriction(rule, strictest))) {
|
|
55
86
|
strictest = rule;
|
|
56
87
|
}
|
|
57
88
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the caller may say about the dialect. `JsonSchemaOptions` extends this,
|
|
3
|
+
* so the chain method and the conversion function offer the same word.
|
|
4
|
+
*/
|
|
5
|
+
export interface DialectOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Read the document under Draft-07 rules whatever its `$schema` declares.
|
|
8
|
+
*
|
|
9
|
+
* The reading it buys is Draft-07's, exactly: `$ref` replaces the node it
|
|
10
|
+
* sits in, `format` asserts, and an array under `items` is the tuple form.
|
|
11
|
+
* A 2019-09 or 2020-12 document that relies on any of those meaning what the
|
|
12
|
+
* NEWER draft says will be enforced as less, or as something else, than it
|
|
13
|
+
* states.
|
|
14
|
+
*/
|
|
15
|
+
readonly assumeDraft07?: boolean | undefined;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The released dialects Luq recognises the NAME of and does not implement,
|
|
19
|
+
* each with what it does that Draft-07 does not. They are written out so that
|
|
20
|
+
* "this is a dialect we know and did not build" and "we have never heard of
|
|
21
|
+
* this URI" stay different answers — the same distinction NON_DRAFT07_KEYWORDS
|
|
22
|
+
* draws between an out-of-dialect keyword and an invented one.
|
|
23
|
+
*/
|
|
24
|
+
export declare const NON_DRAFT07_DIALECTS: Readonly<Record<string, string>>;
|
|
25
|
+
/**
|
|
26
|
+
* Reads the root `$schema` and refuses a dialect Luq does not implement.
|
|
27
|
+
*
|
|
28
|
+
* Called at both front doors, immediately after "is this a schema at all" and
|
|
29
|
+
* before any keyword is read, so no validator that enforces less than its
|
|
30
|
+
* document states can be reached by either route.
|
|
31
|
+
*
|
|
32
|
+
* A non-string `$schema` is a malformed VALUE rather than an unimplemented
|
|
33
|
+
* dialect — the meta-schema types it as a string — so it goes to the class that
|
|
34
|
+
* answers that question, and it is refused under the opt-out too: the opt-out
|
|
35
|
+
* says "read this dialect as Draft-07", and a number names no dialect.
|
|
36
|
+
*/
|
|
37
|
+
export declare function assertSupportedDialect(document: unknown, options: DialectOptions): void;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NON_DRAFT07_DIALECTS = void 0;
|
|
4
|
+
exports.assertSupportedDialect = assertSupportedDialect;
|
|
5
|
+
// ===========================================================================
|
|
6
|
+
// L8 src/json-schema/assert-supported-dialect.ts — the DIALECT gate, read
|
|
7
|
+
// once at the document root before any keyword is.
|
|
8
|
+
//
|
|
9
|
+
// WHY A DEFAULT REFUSAL AND NOT A DIVERGENCE DETECTOR. Refusing only documents
|
|
10
|
+
// that actually USE something 2019-09 changed sounds more precise and is the
|
|
11
|
+
// worse trade. The divergences are not all spellable as keyword names: `$ref`
|
|
12
|
+
// gains siblings, `format` and the two `content*` keywords stop asserting by
|
|
13
|
+
// default, and 2020-12 moves the tuple form of `items` to `prefixItems`. A
|
|
14
|
+
// detector would therefore have to carry a second semantic model of two
|
|
15
|
+
// dialects this library does not implement, and every case it fails to think
|
|
16
|
+
// of is a SILENT wrong answer — which is the exact failure this file exists to
|
|
17
|
+
// stop. The `$ref`-sibling case was itself invisible until it was hunted for.
|
|
18
|
+
// `$schema` is the document's own statement of what it means, so that is what
|
|
19
|
+
// is read.
|
|
20
|
+
//
|
|
21
|
+
// WHAT THE DEFAULT COSTS, and why the opt-out exists. A newer `$schema` over a
|
|
22
|
+
// body that happens to be Draft-07-compatible builds correctly today, and
|
|
23
|
+
// people copy the newest `$schema` line into documents that use nothing new.
|
|
24
|
+
// Refusing outright would break those. `assumeDraft07` gives them a one-word
|
|
25
|
+
// fix that is written at the CALL SITE, in the caller's own source, where a
|
|
26
|
+
// reviewer reads it — so the reading is a stated decision rather than a silent
|
|
27
|
+
// default. There is no way to get the wrong answer without having written the
|
|
28
|
+
// word down.
|
|
29
|
+
//
|
|
30
|
+
// A document with NO `$schema` is untouched. That is most real documents and
|
|
31
|
+
// all 929 cases of the conformance corpus, and it is not a guess about the
|
|
32
|
+
// dialect: nothing was declared, so nothing contradicts Draft-07.
|
|
33
|
+
//
|
|
34
|
+
// THE ROOT ONLY. `externalDocuments` is a map the caller loads, and the suite's
|
|
35
|
+
// own map holds 37 documents across four dialects of which a given case follows
|
|
36
|
+
// two or three; refusing at registration would refuse documents nothing reads.
|
|
37
|
+
// A `$schema` deeper in a document is likewise not the document's dialect.
|
|
38
|
+
// ===========================================================================
|
|
39
|
+
const types_1 = require("../types");
|
|
40
|
+
const malformed_schema_error_1 = require("./malformed-schema-error");
|
|
41
|
+
const unsupported_dialect_error_1 = require("./unsupported-dialect-error");
|
|
42
|
+
/**
|
|
43
|
+
* Comparable form of a meta-schema URI: case folded, scheme dropped, and a
|
|
44
|
+
* trailing "#" or "/" removed.
|
|
45
|
+
*
|
|
46
|
+
* The scheme is dropped because the two families disagree about it — Draft-07
|
|
47
|
+
* publishes `http://json-schema.org/draft-07/schema#` and 2020-12 publishes
|
|
48
|
+
* `https://json-schema.org/draft/2020-12/schema` — and a document that writes
|
|
49
|
+
* `https` for the first or `http` for the second is naming the same dialect,
|
|
50
|
+
* not a different one. Matching the published string exactly would make the
|
|
51
|
+
* gate depend on which of two equally common spellings a document picked.
|
|
52
|
+
*/
|
|
53
|
+
function normaliseDialectUri(uri) {
|
|
54
|
+
return uri
|
|
55
|
+
.trim()
|
|
56
|
+
.toLowerCase()
|
|
57
|
+
.replace(/^https?:\/\//, "")
|
|
58
|
+
.replace(/[#/]+$/, "");
|
|
59
|
+
}
|
|
60
|
+
const DRAFT07_NORMALISED = normaliseDialectUri("http://json-schema.org/draft-07/schema#");
|
|
61
|
+
/**
|
|
62
|
+
* The released dialects Luq recognises the NAME of and does not implement,
|
|
63
|
+
* each with what it does that Draft-07 does not. They are written out so that
|
|
64
|
+
* "this is a dialect we know and did not build" and "we have never heard of
|
|
65
|
+
* this URI" stay different answers — the same distinction NON_DRAFT07_KEYWORDS
|
|
66
|
+
* draws between an out-of-dialect keyword and an invented one.
|
|
67
|
+
*/
|
|
68
|
+
exports.NON_DRAFT07_DIALECTS = Object.freeze({
|
|
69
|
+
"json-schema.org/schema": "the unversioned alias, which names whichever draft the site publishes " +
|
|
70
|
+
"as current rather than a fixed one",
|
|
71
|
+
"json-schema.org/draft-03/schema": "Draft-03, which spells `required` as a boolean on each property",
|
|
72
|
+
"json-schema.org/draft-04/schema": "Draft-04, whose `exclusiveMaximum` is a boolean modifier, not a bound",
|
|
73
|
+
"json-schema.org/draft-06/schema": "Draft-06, which has no `$comment` and reads `$ref` the Draft-07 way",
|
|
74
|
+
"json-schema.org/draft/2019-09/schema": "2019-09, where `$ref` is an ordinary applicator and its siblings are " +
|
|
75
|
+
"applied, and `format` asserts nothing unless the vocabulary asks",
|
|
76
|
+
"json-schema.org/draft/2020-12/schema": "2020-12, where `$ref` siblings are applied, the tuple form of `items` " +
|
|
77
|
+
"is spelled `prefixItems`, and `format` asserts nothing by default",
|
|
78
|
+
});
|
|
79
|
+
const UNRECOGNISED_DIALECT = "a meta-schema Luq has no reading for, so what its keywords mean is unknown";
|
|
80
|
+
const DIALECT_VALUE_REASON = "the value must be a URI string";
|
|
81
|
+
/**
|
|
82
|
+
* Reads the root `$schema` and refuses a dialect Luq does not implement.
|
|
83
|
+
*
|
|
84
|
+
* Called at both front doors, immediately after "is this a schema at all" and
|
|
85
|
+
* before any keyword is read, so no validator that enforces less than its
|
|
86
|
+
* document states can be reached by either route.
|
|
87
|
+
*
|
|
88
|
+
* A non-string `$schema` is a malformed VALUE rather than an unimplemented
|
|
89
|
+
* dialect — the meta-schema types it as a string — so it goes to the class that
|
|
90
|
+
* answers that question, and it is refused under the opt-out too: the opt-out
|
|
91
|
+
* says "read this dialect as Draft-07", and a number names no dialect.
|
|
92
|
+
*/
|
|
93
|
+
function assertSupportedDialect(document, options) {
|
|
94
|
+
// Read off an untyped object rather than through `Draft07SchemaObject`,
|
|
95
|
+
// which types `$schema` as a string: the document is whatever JSON.parse
|
|
96
|
+
// produced, and a `$schema` that is not a string is exactly what this has to
|
|
97
|
+
// be able to see. The §4.4 boolean form and every non-object fall out here,
|
|
98
|
+
// declaring no dialect.
|
|
99
|
+
if (!(0, types_1.isPlainObject)(document))
|
|
100
|
+
return;
|
|
101
|
+
const declared = document["$schema"];
|
|
102
|
+
if (declared === undefined)
|
|
103
|
+
return;
|
|
104
|
+
if (typeof declared !== "string") {
|
|
105
|
+
throw new malformed_schema_error_1.MalformedSchemaError("$schema", DIALECT_VALUE_REASON, declared);
|
|
106
|
+
}
|
|
107
|
+
const normalised = normaliseDialectUri(declared);
|
|
108
|
+
if (normalised === DRAFT07_NORMALISED)
|
|
109
|
+
return;
|
|
110
|
+
if (options.assumeDraft07 === true)
|
|
111
|
+
return;
|
|
112
|
+
throw new unsupported_dialect_error_1.UnsupportedDialectError(declared, exports.NON_DRAFT07_DIALECTS[normalised] ?? UNRECOGNISED_DIALECT);
|
|
113
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// ===========================================================================
|
|
2
|
+
// L8 src/json-schema/assert-supported-dialect.ts — the DIALECT gate, read
|
|
3
|
+
// once at the document root before any keyword is.
|
|
4
|
+
//
|
|
5
|
+
// WHY A DEFAULT REFUSAL AND NOT A DIVERGENCE DETECTOR. Refusing only documents
|
|
6
|
+
// that actually USE something 2019-09 changed sounds more precise and is the
|
|
7
|
+
// worse trade. The divergences are not all spellable as keyword names: `$ref`
|
|
8
|
+
// gains siblings, `format` and the two `content*` keywords stop asserting by
|
|
9
|
+
// default, and 2020-12 moves the tuple form of `items` to `prefixItems`. A
|
|
10
|
+
// detector would therefore have to carry a second semantic model of two
|
|
11
|
+
// dialects this library does not implement, and every case it fails to think
|
|
12
|
+
// of is a SILENT wrong answer — which is the exact failure this file exists to
|
|
13
|
+
// stop. The `$ref`-sibling case was itself invisible until it was hunted for.
|
|
14
|
+
// `$schema` is the document's own statement of what it means, so that is what
|
|
15
|
+
// is read.
|
|
16
|
+
//
|
|
17
|
+
// WHAT THE DEFAULT COSTS, and why the opt-out exists. A newer `$schema` over a
|
|
18
|
+
// body that happens to be Draft-07-compatible builds correctly today, and
|
|
19
|
+
// people copy the newest `$schema` line into documents that use nothing new.
|
|
20
|
+
// Refusing outright would break those. `assumeDraft07` gives them a one-word
|
|
21
|
+
// fix that is written at the CALL SITE, in the caller's own source, where a
|
|
22
|
+
// reviewer reads it — so the reading is a stated decision rather than a silent
|
|
23
|
+
// default. There is no way to get the wrong answer without having written the
|
|
24
|
+
// word down.
|
|
25
|
+
//
|
|
26
|
+
// A document with NO `$schema` is untouched. That is most real documents and
|
|
27
|
+
// all 929 cases of the conformance corpus, and it is not a guess about the
|
|
28
|
+
// dialect: nothing was declared, so nothing contradicts Draft-07.
|
|
29
|
+
//
|
|
30
|
+
// THE ROOT ONLY. `externalDocuments` is a map the caller loads, and the suite's
|
|
31
|
+
// own map holds 37 documents across four dialects of which a given case follows
|
|
32
|
+
// two or three; refusing at registration would refuse documents nothing reads.
|
|
33
|
+
// A `$schema` deeper in a document is likewise not the document's dialect.
|
|
34
|
+
// ===========================================================================
|
|
35
|
+
import { isPlainObject } from "../types/index.mjs";
|
|
36
|
+
import { MalformedSchemaError } from "./malformed-schema-error.mjs";
|
|
37
|
+
import { UnsupportedDialectError } from "./unsupported-dialect-error.mjs";
|
|
38
|
+
/**
|
|
39
|
+
* Comparable form of a meta-schema URI: case folded, scheme dropped, and a
|
|
40
|
+
* trailing "#" or "/" removed.
|
|
41
|
+
*
|
|
42
|
+
* The scheme is dropped because the two families disagree about it — Draft-07
|
|
43
|
+
* publishes `http://json-schema.org/draft-07/schema#` and 2020-12 publishes
|
|
44
|
+
* `https://json-schema.org/draft/2020-12/schema` — and a document that writes
|
|
45
|
+
* `https` for the first or `http` for the second is naming the same dialect,
|
|
46
|
+
* not a different one. Matching the published string exactly would make the
|
|
47
|
+
* gate depend on which of two equally common spellings a document picked.
|
|
48
|
+
*/
|
|
49
|
+
function normaliseDialectUri(uri) {
|
|
50
|
+
return uri
|
|
51
|
+
.trim()
|
|
52
|
+
.toLowerCase()
|
|
53
|
+
.replace(/^https?:\/\//, "")
|
|
54
|
+
.replace(/[#/]+$/, "");
|
|
55
|
+
}
|
|
56
|
+
const DRAFT07_NORMALISED = normaliseDialectUri("http://json-schema.org/draft-07/schema#");
|
|
57
|
+
/**
|
|
58
|
+
* The released dialects Luq recognises the NAME of and does not implement,
|
|
59
|
+
* each with what it does that Draft-07 does not. They are written out so that
|
|
60
|
+
* "this is a dialect we know and did not build" and "we have never heard of
|
|
61
|
+
* this URI" stay different answers — the same distinction NON_DRAFT07_KEYWORDS
|
|
62
|
+
* draws between an out-of-dialect keyword and an invented one.
|
|
63
|
+
*/
|
|
64
|
+
export const NON_DRAFT07_DIALECTS = Object.freeze({
|
|
65
|
+
"json-schema.org/schema": "the unversioned alias, which names whichever draft the site publishes " +
|
|
66
|
+
"as current rather than a fixed one",
|
|
67
|
+
"json-schema.org/draft-03/schema": "Draft-03, which spells `required` as a boolean on each property",
|
|
68
|
+
"json-schema.org/draft-04/schema": "Draft-04, whose `exclusiveMaximum` is a boolean modifier, not a bound",
|
|
69
|
+
"json-schema.org/draft-06/schema": "Draft-06, which has no `$comment` and reads `$ref` the Draft-07 way",
|
|
70
|
+
"json-schema.org/draft/2019-09/schema": "2019-09, where `$ref` is an ordinary applicator and its siblings are " +
|
|
71
|
+
"applied, and `format` asserts nothing unless the vocabulary asks",
|
|
72
|
+
"json-schema.org/draft/2020-12/schema": "2020-12, where `$ref` siblings are applied, the tuple form of `items` " +
|
|
73
|
+
"is spelled `prefixItems`, and `format` asserts nothing by default",
|
|
74
|
+
});
|
|
75
|
+
const UNRECOGNISED_DIALECT = "a meta-schema Luq has no reading for, so what its keywords mean is unknown";
|
|
76
|
+
const DIALECT_VALUE_REASON = "the value must be a URI string";
|
|
77
|
+
/**
|
|
78
|
+
* Reads the root `$schema` and refuses a dialect Luq does not implement.
|
|
79
|
+
*
|
|
80
|
+
* Called at both front doors, immediately after "is this a schema at all" and
|
|
81
|
+
* before any keyword is read, so no validator that enforces less than its
|
|
82
|
+
* document states can be reached by either route.
|
|
83
|
+
*
|
|
84
|
+
* A non-string `$schema` is a malformed VALUE rather than an unimplemented
|
|
85
|
+
* dialect — the meta-schema types it as a string — so it goes to the class that
|
|
86
|
+
* answers that question, and it is refused under the opt-out too: the opt-out
|
|
87
|
+
* says "read this dialect as Draft-07", and a number names no dialect.
|
|
88
|
+
*/
|
|
89
|
+
export function assertSupportedDialect(document, options) {
|
|
90
|
+
// Read off an untyped object rather than through `Draft07SchemaObject`,
|
|
91
|
+
// which types `$schema` as a string: the document is whatever JSON.parse
|
|
92
|
+
// produced, and a `$schema` that is not a string is exactly what this has to
|
|
93
|
+
// be able to see. The §4.4 boolean form and every non-object fall out here,
|
|
94
|
+
// declaring no dialect.
|
|
95
|
+
if (!isPlainObject(document))
|
|
96
|
+
return;
|
|
97
|
+
const declared = document["$schema"];
|
|
98
|
+
if (declared === undefined)
|
|
99
|
+
return;
|
|
100
|
+
if (typeof declared !== "string") {
|
|
101
|
+
throw new MalformedSchemaError("$schema", DIALECT_VALUE_REASON, declared);
|
|
102
|
+
}
|
|
103
|
+
const normalised = normaliseDialectUri(declared);
|
|
104
|
+
if (normalised === DRAFT07_NORMALISED)
|
|
105
|
+
return;
|
|
106
|
+
if (options.assumeDraft07 === true)
|
|
107
|
+
return;
|
|
108
|
+
throw new UnsupportedDialectError(declared, NON_DRAFT07_DIALECTS[normalised] ?? UNRECOGNISED_DIALECT);
|
|
109
|
+
}
|
|
@@ -2,6 +2,7 @@ import type { FieldBuilderSurface } from "../builder/builder-surface.types";
|
|
|
2
2
|
import type { FieldEntry } from "../builder/field-entry.types";
|
|
3
3
|
import type { Validator } from "../builder/validator.types";
|
|
4
4
|
import type { GlobalConfig } from "../types/global-config";
|
|
5
|
+
import type { DialectOptions } from "./assert-supported-dialect";
|
|
5
6
|
import type { JsonSchemaBag } from "./json-schema-bag.types";
|
|
6
7
|
/** A value that is not a schema at all. Named, and refused at the boundary. */
|
|
7
8
|
export declare class NotASchemaError extends Error {
|
|
@@ -9,10 +10,28 @@ export declare class NotASchemaError extends Error {
|
|
|
9
10
|
constructor(received: unknown);
|
|
10
11
|
}
|
|
11
12
|
/** One pending `.v()` per declared path, with its callback still unrun. */
|
|
12
|
-
export declare function buildFieldEntries(bag: JsonSchemaBag, schema: unknown): readonly FieldEntry[];
|
|
13
|
+
export declare function buildFieldEntries(bag: JsonSchemaBag, schema: unknown, options?: DialectOptions): readonly FieldEntry[];
|
|
13
14
|
/** The erased front door: the declarations, with nothing compiled yet. */
|
|
14
|
-
export declare function buildFromSchema(bag: JsonSchemaBag, schema: unknown, config?: GlobalConfig): FieldBuilderSurface;
|
|
15
|
+
export declare function buildFromSchema(bag: JsonSchemaBag, schema: unknown, config?: GlobalConfig, options?: DialectOptions): FieldBuilderSurface;
|
|
15
16
|
/**
|
|
17
|
+
* Converts a JSON Schema document into a validator.
|
|
18
|
+
*
|
|
19
|
+
* THE INPUT LIMIT IS DRAFT-07, and it is a limit on what may be handed in, not
|
|
20
|
+
* only a statement of how much of Draft-07 is covered. A document declaring
|
|
21
|
+
* 2019-09 or 2020-12 in its root `$schema` is REFUSED with an
|
|
22
|
+
* `UnsupportedDialectError` rather than read as Draft-07, because the two
|
|
23
|
+
* dialects disagree about what an unchanged keyword means: from 2019-09 on
|
|
24
|
+
* `$ref` is an ordinary applicator whose siblings are applied, while Draft-07
|
|
25
|
+
* §8.3 replaces the node, so `{"$ref": "#/$defs/name", "minLength": 5}` read as
|
|
26
|
+
* Draft-07 loses the `minLength` and accepts a value the document forbids. A
|
|
27
|
+
* document with NO `$schema` is read as Draft-07 and always has been. Pass
|
|
28
|
+
* `{ assumeDraft07: true }` as the fourth argument to read a newer-dialect
|
|
29
|
+
* document under Draft-07 rules deliberately, with that reading's consequences.
|
|
30
|
+
*
|
|
31
|
+
* `T` defaults to `Record<string, unknown>`, never `any`; under the default NO
|
|
32
|
+
* declared path is checked. See the header of this file, which owns that
|
|
33
|
+
* escape hatch.
|
|
34
|
+
*
|
|
16
35
|
* The declared type is put back on by `eraseSchemaValidator`, the fourth
|
|
17
36
|
* function in src/core/type-erasure.ts — the one file the code standard allows
|
|
18
37
|
* to assert. Step 25 expressed this as an OVERLOAD PAIR instead, because
|
|
@@ -20,4 +39,4 @@ export declare function buildFromSchema(bag: JsonSchemaBag, schema: unknown, con
|
|
|
20
39
|
* TypeScript on lenient compatibility rules and so was never audited anywhere.
|
|
21
40
|
* The call below is, in the file where every escape hatch is reviewed together.
|
|
22
41
|
*/
|
|
23
|
-
export declare function fromJsonSchema<T extends object = Record<string, unknown>>(bag: JsonSchemaBag, schema: unknown, config?: GlobalConfig): Validator<T>;
|
|
42
|
+
export declare function fromJsonSchema<T extends object = Record<string, unknown>>(bag: JsonSchemaBag, schema: unknown, config?: GlobalConfig, options?: DialectOptions): Validator<T>;
|
|
@@ -6,6 +6,7 @@ exports.buildFromSchema = buildFromSchema;
|
|
|
6
6
|
exports.fromJsonSchema = fromJsonSchema;
|
|
7
7
|
const create_field_builder_1 = require("../builder/create-field-builder");
|
|
8
8
|
const type_erasure_1 = require("../core/type-erasure");
|
|
9
|
+
const assert_supported_dialect_1 = require("./assert-supported-dialect");
|
|
9
10
|
const create_structural_context_1 = require("./create-structural-context");
|
|
10
11
|
const declare_presence_1 = require("./declare-presence");
|
|
11
12
|
const ref_scope_1 = require("./ref-scope");
|
|
@@ -35,9 +36,12 @@ function collectDeclaredRules(declaration, bag, root, chain) {
|
|
|
35
36
|
]);
|
|
36
37
|
}
|
|
37
38
|
/** One pending `.v()` per declared path, with its callback still unrun. */
|
|
38
|
-
function buildFieldEntries(bag, schema) {
|
|
39
|
+
function buildFieldEntries(bag, schema, options = {}) {
|
|
39
40
|
if (!(0, draft07_types_1.isDraft07Schema)(schema))
|
|
40
41
|
throw new NotASchemaError(schema);
|
|
42
|
+
// Before any keyword is read: what the document's keywords MEAN depends on
|
|
43
|
+
// the dialect it declares, and Luq reads every one of them as Draft-07.
|
|
44
|
+
(0, assert_supported_dialect_1.assertSupportedDialect)(schema, options);
|
|
41
45
|
return (0, flatten_schema_1.flattenSchema)(schema, schema_to_declarations_1.readChildSchemas).map((declaration) => ({
|
|
42
46
|
path: declaration.path,
|
|
43
47
|
defaultOf: null,
|
|
@@ -53,10 +57,28 @@ function buildFieldEntries(bag, schema) {
|
|
|
53
57
|
}));
|
|
54
58
|
}
|
|
55
59
|
/** The erased front door: the declarations, with nothing compiled yet. */
|
|
56
|
-
function buildFromSchema(bag, schema, config) {
|
|
57
|
-
return (0, create_field_builder_1.createFieldBuilderSurface)(bag, config, buildFieldEntries(bag, schema));
|
|
60
|
+
function buildFromSchema(bag, schema, config, options) {
|
|
61
|
+
return (0, create_field_builder_1.createFieldBuilderSurface)(bag, config, buildFieldEntries(bag, schema, options));
|
|
58
62
|
}
|
|
59
63
|
/**
|
|
64
|
+
* Converts a JSON Schema document into a validator.
|
|
65
|
+
*
|
|
66
|
+
* THE INPUT LIMIT IS DRAFT-07, and it is a limit on what may be handed in, not
|
|
67
|
+
* only a statement of how much of Draft-07 is covered. A document declaring
|
|
68
|
+
* 2019-09 or 2020-12 in its root `$schema` is REFUSED with an
|
|
69
|
+
* `UnsupportedDialectError` rather than read as Draft-07, because the two
|
|
70
|
+
* dialects disagree about what an unchanged keyword means: from 2019-09 on
|
|
71
|
+
* `$ref` is an ordinary applicator whose siblings are applied, while Draft-07
|
|
72
|
+
* §8.3 replaces the node, so `{"$ref": "#/$defs/name", "minLength": 5}` read as
|
|
73
|
+
* Draft-07 loses the `minLength` and accepts a value the document forbids. A
|
|
74
|
+
* document with NO `$schema` is read as Draft-07 and always has been. Pass
|
|
75
|
+
* `{ assumeDraft07: true }` as the fourth argument to read a newer-dialect
|
|
76
|
+
* document under Draft-07 rules deliberately, with that reading's consequences.
|
|
77
|
+
*
|
|
78
|
+
* `T` defaults to `Record<string, unknown>`, never `any`; under the default NO
|
|
79
|
+
* declared path is checked. See the header of this file, which owns that
|
|
80
|
+
* escape hatch.
|
|
81
|
+
*
|
|
60
82
|
* The declared type is put back on by `eraseSchemaValidator`, the fourth
|
|
61
83
|
* function in src/core/type-erasure.ts — the one file the code standard allows
|
|
62
84
|
* to assert. Step 25 expressed this as an OVERLOAD PAIR instead, because
|
|
@@ -64,7 +86,7 @@ function buildFromSchema(bag, schema, config) {
|
|
|
64
86
|
* TypeScript on lenient compatibility rules and so was never audited anywhere.
|
|
65
87
|
* The call below is, in the file where every escape hatch is reviewed together.
|
|
66
88
|
*/
|
|
67
|
-
function fromJsonSchema(bag, schema, config) {
|
|
68
|
-
const planBacked = buildFromSchema(bag, schema, config).build();
|
|
89
|
+
function fromJsonSchema(bag, schema, config, options) {
|
|
90
|
+
const planBacked = buildFromSchema(bag, schema, config, options).build();
|
|
69
91
|
return (0, type_erasure_1.eraseSchemaValidator)(planBacked);
|
|
70
92
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createFieldBuilderSurface } from "../builder/create-field-builder.mjs";
|
|
2
2
|
import { eraseSchemaValidator } from "../core/type-erasure.mjs";
|
|
3
|
+
import { assertSupportedDialect } from "./assert-supported-dialect.mjs";
|
|
3
4
|
import { createStructuralContext } from "./create-structural-context.mjs";
|
|
4
5
|
import { declarePresenceRules } from "./declare-presence.mjs";
|
|
5
6
|
import { createLocalScope } from "./ref-scope.mjs";
|
|
@@ -28,9 +29,12 @@ function collectDeclaredRules(declaration, bag, root, chain) {
|
|
|
28
29
|
]);
|
|
29
30
|
}
|
|
30
31
|
/** One pending `.v()` per declared path, with its callback still unrun. */
|
|
31
|
-
export function buildFieldEntries(bag, schema) {
|
|
32
|
+
export function buildFieldEntries(bag, schema, options = {}) {
|
|
32
33
|
if (!isDraft07Schema(schema))
|
|
33
34
|
throw new NotASchemaError(schema);
|
|
35
|
+
// Before any keyword is read: what the document's keywords MEAN depends on
|
|
36
|
+
// the dialect it declares, and Luq reads every one of them as Draft-07.
|
|
37
|
+
assertSupportedDialect(schema, options);
|
|
34
38
|
return flattenSchema(schema, readChildSchemas).map((declaration) => ({
|
|
35
39
|
path: declaration.path,
|
|
36
40
|
defaultOf: null,
|
|
@@ -46,10 +50,28 @@ export function buildFieldEntries(bag, schema) {
|
|
|
46
50
|
}));
|
|
47
51
|
}
|
|
48
52
|
/** The erased front door: the declarations, with nothing compiled yet. */
|
|
49
|
-
export function buildFromSchema(bag, schema, config) {
|
|
50
|
-
return createFieldBuilderSurface(bag, config, buildFieldEntries(bag, schema));
|
|
53
|
+
export function buildFromSchema(bag, schema, config, options) {
|
|
54
|
+
return createFieldBuilderSurface(bag, config, buildFieldEntries(bag, schema, options));
|
|
51
55
|
}
|
|
52
56
|
/**
|
|
57
|
+
* Converts a JSON Schema document into a validator.
|
|
58
|
+
*
|
|
59
|
+
* THE INPUT LIMIT IS DRAFT-07, and it is a limit on what may be handed in, not
|
|
60
|
+
* only a statement of how much of Draft-07 is covered. A document declaring
|
|
61
|
+
* 2019-09 or 2020-12 in its root `$schema` is REFUSED with an
|
|
62
|
+
* `UnsupportedDialectError` rather than read as Draft-07, because the two
|
|
63
|
+
* dialects disagree about what an unchanged keyword means: from 2019-09 on
|
|
64
|
+
* `$ref` is an ordinary applicator whose siblings are applied, while Draft-07
|
|
65
|
+
* §8.3 replaces the node, so `{"$ref": "#/$defs/name", "minLength": 5}` read as
|
|
66
|
+
* Draft-07 loses the `minLength` and accepts a value the document forbids. A
|
|
67
|
+
* document with NO `$schema` is read as Draft-07 and always has been. Pass
|
|
68
|
+
* `{ assumeDraft07: true }` as the fourth argument to read a newer-dialect
|
|
69
|
+
* document under Draft-07 rules deliberately, with that reading's consequences.
|
|
70
|
+
*
|
|
71
|
+
* `T` defaults to `Record<string, unknown>`, never `any`; under the default NO
|
|
72
|
+
* declared path is checked. See the header of this file, which owns that
|
|
73
|
+
* escape hatch.
|
|
74
|
+
*
|
|
53
75
|
* The declared type is put back on by `eraseSchemaValidator`, the fourth
|
|
54
76
|
* function in src/core/type-erasure.ts — the one file the code standard allows
|
|
55
77
|
* to assert. Step 25 expressed this as an OVERLOAD PAIR instead, because
|
|
@@ -57,7 +79,7 @@ export function buildFromSchema(bag, schema, config) {
|
|
|
57
79
|
* TypeScript on lenient compatibility rules and so was never audited anywhere.
|
|
58
80
|
* The call below is, in the file where every escape hatch is reviewed together.
|
|
59
81
|
*/
|
|
60
|
-
export function fromJsonSchema(bag, schema, config) {
|
|
61
|
-
const planBacked = buildFromSchema(bag, schema, config).build();
|
|
82
|
+
export function fromJsonSchema(bag, schema, config, options) {
|
|
83
|
+
const planBacked = buildFromSchema(bag, schema, config, options).build();
|
|
62
84
|
return eraseSchemaValidator(planBacked);
|
|
63
85
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { SCHEMA_BRANCH_LABEL, collectDocumentRules, jsonSchemaPlugin, } from "./json-schema";
|
|
2
2
|
export type { JsonSchemaOptions } from "./json-schema";
|
|
3
|
-
export { MalformedSchemaError, NotASchemaError, UnsupportedKeywordError, RefResolutionError, buildFieldEntries, buildFromSchema, fromJsonSchema, isDraft07Schema, listBoundPluginNames, listDraft07Keywords, listFormatNames, } from "../../index";
|
|
4
|
-
export type { Draft07Schema, Draft07SchemaObject, JsonSchemaBag, } from "../../index";
|
|
3
|
+
export { DRAFT07_DIALECT_URI, MalformedSchemaError, NotASchemaError, UnsupportedDialectError, UnsupportedKeywordError, RefResolutionError, buildFieldEntries, buildFromSchema, fromJsonSchema, isDraft07Schema, listBoundPluginNames, listDraft07Keywords, listFormatNames, } from "../../index";
|
|
4
|
+
export type { DialectOptions, Draft07Schema, Draft07SchemaObject, JsonSchemaBag, } from "../../index";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.listFormatNames = exports.listDraft07Keywords = exports.listBoundPluginNames = exports.isDraft07Schema = exports.fromJsonSchema = exports.buildFromSchema = exports.buildFieldEntries = exports.RefResolutionError = exports.UnsupportedKeywordError = exports.NotASchemaError = exports.MalformedSchemaError = exports.jsonSchemaPlugin = exports.collectDocumentRules = exports.SCHEMA_BRANCH_LABEL = void 0;
|
|
3
|
+
exports.listFormatNames = exports.listDraft07Keywords = exports.listBoundPluginNames = exports.isDraft07Schema = exports.fromJsonSchema = exports.buildFromSchema = exports.buildFieldEntries = exports.RefResolutionError = exports.UnsupportedKeywordError = exports.UnsupportedDialectError = exports.NotASchemaError = exports.MalformedSchemaError = exports.DRAFT07_DIALECT_URI = exports.jsonSchemaPlugin = exports.collectDocumentRules = exports.SCHEMA_BRANCH_LABEL = void 0;
|
|
4
4
|
// ===========================================================================
|
|
5
5
|
// The public subpath `@maroonedog/luq/plugins/jsonSchema`. Re-exports only.
|
|
6
6
|
//
|
|
@@ -15,8 +15,10 @@ Object.defineProperty(exports, "SCHEMA_BRANCH_LABEL", { enumerable: true, get: f
|
|
|
15
15
|
Object.defineProperty(exports, "collectDocumentRules", { enumerable: true, get: function () { return json_schema_1.collectDocumentRules; } });
|
|
16
16
|
Object.defineProperty(exports, "jsonSchemaPlugin", { enumerable: true, get: function () { return json_schema_1.jsonSchemaPlugin; } });
|
|
17
17
|
var index_1 = require("../../index");
|
|
18
|
+
Object.defineProperty(exports, "DRAFT07_DIALECT_URI", { enumerable: true, get: function () { return index_1.DRAFT07_DIALECT_URI; } });
|
|
18
19
|
Object.defineProperty(exports, "MalformedSchemaError", { enumerable: true, get: function () { return index_1.MalformedSchemaError; } });
|
|
19
20
|
Object.defineProperty(exports, "NotASchemaError", { enumerable: true, get: function () { return index_1.NotASchemaError; } });
|
|
21
|
+
Object.defineProperty(exports, "UnsupportedDialectError", { enumerable: true, get: function () { return index_1.UnsupportedDialectError; } });
|
|
20
22
|
Object.defineProperty(exports, "UnsupportedKeywordError", { enumerable: true, get: function () { return index_1.UnsupportedKeywordError; } });
|
|
21
23
|
Object.defineProperty(exports, "RefResolutionError", { enumerable: true, get: function () { return index_1.RefResolutionError; } });
|
|
22
24
|
Object.defineProperty(exports, "buildFieldEntries", { enumerable: true, get: function () { return index_1.buildFieldEntries; } });
|
|
@@ -8,4 +8,4 @@
|
|
|
8
8
|
// a consumer of this subpath pays only for the plugins they import themselves.
|
|
9
9
|
// ===========================================================================
|
|
10
10
|
export { SCHEMA_BRANCH_LABEL, collectDocumentRules, jsonSchemaPlugin, } from "./json-schema.mjs";
|
|
11
|
-
export { MalformedSchemaError, NotASchemaError, UnsupportedKeywordError, RefResolutionError, buildFieldEntries, buildFromSchema, fromJsonSchema, isDraft07Schema, listBoundPluginNames, listDraft07Keywords, listFormatNames, } from "../../index.mjs";
|
|
11
|
+
export { DRAFT07_DIALECT_URI, MalformedSchemaError, NotASchemaError, UnsupportedDialectError, UnsupportedKeywordError, RefResolutionError, buildFieldEntries, buildFromSchema, fromJsonSchema, isDraft07Schema, listBoundPluginNames, listDraft07Keywords, listFormatNames, } from "../../index.mjs";
|
|
@@ -2,7 +2,7 @@ import type { Rule } from "../../../plugin-kit/compiled-rule";
|
|
|
2
2
|
import { type MessageContextExtra, type TypeName } from "../../../types";
|
|
3
3
|
import type { RuleBuildContext } from "../../../plugin-kit/rule-build-context";
|
|
4
4
|
import type { Unchanged } from "../../../plugin-kit/marker.types";
|
|
5
|
-
import { type JsonSchemaBag } from "../../index";
|
|
5
|
+
import { type DialectOptions, type JsonSchemaBag } from "../../index";
|
|
6
6
|
/** The label the whole document carries inside the composite it becomes. */
|
|
7
7
|
export declare const SCHEMA_BRANCH_LABEL = "schema";
|
|
8
8
|
/**
|
|
@@ -17,7 +17,7 @@ export declare const SCHEMA_BRANCH_LABEL = "schema";
|
|
|
17
17
|
* The caller fetches, reads from disk, or bundles — whichever is right for
|
|
18
18
|
* their deployment — and hands over what they already have.
|
|
19
19
|
*/
|
|
20
|
-
export interface JsonSchemaOptions {
|
|
20
|
+
export interface JsonSchemaOptions extends DialectOptions {
|
|
21
21
|
readonly externalDocuments?: Readonly<Record<string, unknown>> | undefined;
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
@@ -66,6 +66,9 @@ const NO_EXTERNAL_DOCUMENTS = Object.freeze({});
|
|
|
66
66
|
function collectDocumentRules(ctx, document, bag, options = {}) {
|
|
67
67
|
if (!(0, index_1.isDraft07Schema)(document))
|
|
68
68
|
throw new index_1.NotASchemaError(document);
|
|
69
|
+
// Before any keyword is read, and in the same place the function front door
|
|
70
|
+
// reads it: the dialect decides what the keywords below MEAN.
|
|
71
|
+
(0, index_1.assertSupportedDialect)(document, options);
|
|
69
72
|
const root = document;
|
|
70
73
|
const scope = (0, index_1.createDocumentScope)(root, options.externalDocuments ?? NO_EXTERNAL_DOCUMENTS);
|
|
71
74
|
// The seed's `chain` is a ChainBuildContext, whose three members a
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
import { branch, composite } from "../../../plugin-kit/create-rule.mjs";
|
|
39
39
|
import { definePlugin } from "../../../plugin-kit/plugin-definition.mjs";
|
|
40
40
|
import { PASS } from "../../../types/index.mjs";
|
|
41
|
-
import { NotASchemaError, collectSubSchemaRules, createStructuralContext, createDocumentScope, isDraft07Schema, resolveSchemaNodeInScope, } from "../../index.mjs";
|
|
41
|
+
import { NotASchemaError, assertSupportedDialect, collectSubSchemaRules, createStructuralContext, createDocumentScope, isDraft07Schema, resolveSchemaNodeInScope, } from "../../index.mjs";
|
|
42
42
|
/** Every slot: a JSON Schema document constrains a value of any shape. */
|
|
43
43
|
const SCHEMA_SLOTS = [
|
|
44
44
|
"string",
|
|
@@ -62,6 +62,9 @@ const NO_EXTERNAL_DOCUMENTS = Object.freeze({});
|
|
|
62
62
|
export function collectDocumentRules(ctx, document, bag, options = {}) {
|
|
63
63
|
if (!isDraft07Schema(document))
|
|
64
64
|
throw new NotASchemaError(document);
|
|
65
|
+
// Before any keyword is read, and in the same place the function front door
|
|
66
|
+
// reads it: the dialect decides what the keywords below MEAN.
|
|
67
|
+
assertSupportedDialect(document, options);
|
|
65
68
|
const root = document;
|
|
66
69
|
const scope = createDocumentScope(root, options.externalDocuments ?? NO_EXTERNAL_DOCUMENTS);
|
|
67
70
|
// The seed's `chain` is a ChainBuildContext, whose three members a
|
|
@@ -2,4 +2,6 @@ export { fromJsonSchema, jsonSchemaFullFeaturePlugin, } from "./json-schema-full
|
|
|
2
2
|
export { jsonSchemaBag } from "./bundled-plugins";
|
|
3
3
|
export { UNBOUND_BUNDLED_PLUGIN_NAMES, findStaleUnboundDeclarations, findUnbundledBoundPlugins, findUnexplainedBundledPlugins, listBundledPluginNames, } from "./bundle-coverage";
|
|
4
4
|
export type { JsonSchemaOptions } from "../json-schema";
|
|
5
|
-
export
|
|
5
|
+
export { DRAFT07_DIALECT_URI, UnsupportedDialectError, } from "../../unsupported-dialect-error";
|
|
6
|
+
export { MalformedSchemaError, NotASchemaError, RefResolutionError, UnsupportedKeywordError, } from "../../index";
|
|
7
|
+
export type { DialectOptions, JsonSchemaBag } from "../../index";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.listBundledPluginNames = exports.findUnexplainedBundledPlugins = exports.findUnbundledBoundPlugins = exports.findStaleUnboundDeclarations = exports.UNBOUND_BUNDLED_PLUGIN_NAMES = exports.jsonSchemaBag = exports.jsonSchemaFullFeaturePlugin = exports.fromJsonSchema = void 0;
|
|
3
|
+
exports.UnsupportedKeywordError = exports.RefResolutionError = exports.NotASchemaError = exports.MalformedSchemaError = exports.UnsupportedDialectError = exports.DRAFT07_DIALECT_URI = exports.listBundledPluginNames = exports.findUnexplainedBundledPlugins = exports.findUnbundledBoundPlugins = exports.findStaleUnboundDeclarations = exports.UNBOUND_BUNDLED_PLUGIN_NAMES = exports.jsonSchemaBag = exports.jsonSchemaFullFeaturePlugin = exports.fromJsonSchema = void 0;
|
|
4
4
|
// ===========================================================================
|
|
5
5
|
// The public subpath `@maroonedog/luq/plugins/jsonSchemaFullFeature`.
|
|
6
6
|
// Re-exports only.
|
|
@@ -23,3 +23,20 @@ Object.defineProperty(exports, "findStaleUnboundDeclarations", { enumerable: tru
|
|
|
23
23
|
Object.defineProperty(exports, "findUnbundledBoundPlugins", { enumerable: true, get: function () { return bundle_coverage_1.findUnbundledBoundPlugins; } });
|
|
24
24
|
Object.defineProperty(exports, "findUnexplainedBundledPlugins", { enumerable: true, get: function () { return bundle_coverage_1.findUnexplainedBundledPlugins; } });
|
|
25
25
|
Object.defineProperty(exports, "listBundledPluginNames", { enumerable: true, get: function () { return bundle_coverage_1.listBundledPluginNames; } });
|
|
26
|
+
// Every error `fromJsonSchema` can throw, so a caller can tell them apart by
|
|
27
|
+
// class rather than by reading the message.
|
|
28
|
+
//
|
|
29
|
+
// This subpath is the one-import route and the one the documentation points at,
|
|
30
|
+
// which made it the worst place to be missing them: a document that is not
|
|
31
|
+
// Draft-07 at all, one the meta-schema forbids, one carrying a keyword Luq
|
|
32
|
+
// cannot honour and one that is not a schema are four different things to do
|
|
33
|
+
// about it, and a caller who can only match on message text has to re-derive
|
|
34
|
+
// that distinction from prose that is free to change.
|
|
35
|
+
var unsupported_dialect_error_1 = require("../../unsupported-dialect-error");
|
|
36
|
+
Object.defineProperty(exports, "DRAFT07_DIALECT_URI", { enumerable: true, get: function () { return unsupported_dialect_error_1.DRAFT07_DIALECT_URI; } });
|
|
37
|
+
Object.defineProperty(exports, "UnsupportedDialectError", { enumerable: true, get: function () { return unsupported_dialect_error_1.UnsupportedDialectError; } });
|
|
38
|
+
var index_1 = require("../../index");
|
|
39
|
+
Object.defineProperty(exports, "MalformedSchemaError", { enumerable: true, get: function () { return index_1.MalformedSchemaError; } });
|
|
40
|
+
Object.defineProperty(exports, "NotASchemaError", { enumerable: true, get: function () { return index_1.NotASchemaError; } });
|
|
41
|
+
Object.defineProperty(exports, "RefResolutionError", { enumerable: true, get: function () { return index_1.RefResolutionError; } });
|
|
42
|
+
Object.defineProperty(exports, "UnsupportedKeywordError", { enumerable: true, get: function () { return index_1.UnsupportedKeywordError; } });
|
|
@@ -12,3 +12,14 @@
|
|
|
12
12
|
export { fromJsonSchema, jsonSchemaFullFeaturePlugin, } from "./json-schema-full-feature.mjs";
|
|
13
13
|
export { jsonSchemaBag } from "./bundled-plugins.mjs";
|
|
14
14
|
export { UNBOUND_BUNDLED_PLUGIN_NAMES, findStaleUnboundDeclarations, findUnbundledBoundPlugins, findUnexplainedBundledPlugins, listBundledPluginNames, } from "./bundle-coverage.mjs";
|
|
15
|
+
// Every error `fromJsonSchema` can throw, so a caller can tell them apart by
|
|
16
|
+
// class rather than by reading the message.
|
|
17
|
+
//
|
|
18
|
+
// This subpath is the one-import route and the one the documentation points at,
|
|
19
|
+
// which made it the worst place to be missing them: a document that is not
|
|
20
|
+
// Draft-07 at all, one the meta-schema forbids, one carrying a keyword Luq
|
|
21
|
+
// cannot honour and one that is not a schema are four different things to do
|
|
22
|
+
// about it, and a caller who can only match on message text has to re-derive
|
|
23
|
+
// that distinction from prose that is free to change.
|
|
24
|
+
export { DRAFT07_DIALECT_URI, UnsupportedDialectError, } from "../../unsupported-dialect-error.mjs";
|
|
25
|
+
export { MalformedSchemaError, NotASchemaError, RefResolutionError, UnsupportedKeywordError, } from "../../index.mjs";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { MessageContextExtra, TypeName } from "../../../types";
|
|
2
2
|
import type { Unchanged } from "../../../plugin-kit/marker.types";
|
|
3
3
|
import type { JsonSchemaOptions } from "../json-schema";
|
|
4
|
+
import type { DialectOptions } from "../../index";
|
|
4
5
|
import type { GlobalConfig } from "../../../types/global-config";
|
|
5
6
|
export declare const jsonSchemaFullFeaturePlugin: import("../../../plugin-kit/plugin-definition").PluginDefinition<"jsonSchemaFullFeature", "jsonSchemaFullFeature", readonly TypeName[], {
|
|
6
7
|
args: readonly [document: unknown, options?: JsonSchemaOptions];
|
|
@@ -17,8 +18,15 @@ export declare const jsonSchemaFullFeaturePlugin: import("../../../plugin-kit/pl
|
|
|
17
18
|
* NO declared path is checked — see build-from-schema.ts, which owns that
|
|
18
19
|
* escape hatch and its single overload.
|
|
19
20
|
*
|
|
21
|
+
* THE INPUT LIMIT IS DRAFT-07. A document whose root `$schema` names 2019-09 or
|
|
22
|
+
* 2020-12 is refused with an `UnsupportedDialectError` rather than read as
|
|
23
|
+
* Draft-07, because the dialects disagree about what unchanged keywords mean;
|
|
24
|
+
* a document with no `$schema` is read as Draft-07 as before. Pass
|
|
25
|
+
* `{ assumeDraft07: true }` as the third argument to take the Draft-07 reading
|
|
26
|
+
* deliberately. build-from-schema.ts states the divergence in full.
|
|
27
|
+
*
|
|
20
28
|
* The return type is INFERRED rather than written: naming `Validator<T>` would
|
|
21
29
|
* mean importing src/builder, which tier `extension` forbids. The inferred type
|
|
22
30
|
* is that same `Validator<T>` and the type test asserts it.
|
|
23
31
|
*/
|
|
24
|
-
export declare function fromJsonSchema<T extends object = Record<string, unknown>>(schema: unknown, config?: GlobalConfig): import("../../..").Validator<T, T>;
|
|
32
|
+
export declare function fromJsonSchema<T extends object = Record<string, unknown>>(schema: unknown, config?: GlobalConfig, options?: DialectOptions): import("../../..").Validator<T, T>;
|
|
@@ -57,10 +57,17 @@ exports.jsonSchemaFullFeaturePlugin = (0, plugin_definition_1.definePlugin)()({
|
|
|
57
57
|
* NO declared path is checked — see build-from-schema.ts, which owns that
|
|
58
58
|
* escape hatch and its single overload.
|
|
59
59
|
*
|
|
60
|
+
* THE INPUT LIMIT IS DRAFT-07. A document whose root `$schema` names 2019-09 or
|
|
61
|
+
* 2020-12 is refused with an `UnsupportedDialectError` rather than read as
|
|
62
|
+
* Draft-07, because the dialects disagree about what unchanged keywords mean;
|
|
63
|
+
* a document with no `$schema` is read as Draft-07 as before. Pass
|
|
64
|
+
* `{ assumeDraft07: true }` as the third argument to take the Draft-07 reading
|
|
65
|
+
* deliberately. build-from-schema.ts states the divergence in full.
|
|
66
|
+
*
|
|
60
67
|
* The return type is INFERRED rather than written: naming `Validator<T>` would
|
|
61
68
|
* mean importing src/builder, which tier `extension` forbids. The inferred type
|
|
62
69
|
* is that same `Validator<T>` and the type test asserts it.
|
|
63
70
|
*/
|
|
64
|
-
function fromJsonSchema(schema, config) {
|
|
65
|
-
return (0, index_1.fromJsonSchema)(bundled_plugins_1.jsonSchemaBag, schema, config);
|
|
71
|
+
function fromJsonSchema(schema, config, options) {
|
|
72
|
+
return (0, index_1.fromJsonSchema)(bundled_plugins_1.jsonSchemaBag, schema, config, options);
|
|
66
73
|
}
|
|
@@ -53,10 +53,17 @@ export const jsonSchemaFullFeaturePlugin = /*#__PURE__*/ definePlugin()({
|
|
|
53
53
|
* NO declared path is checked — see build-from-schema.ts, which owns that
|
|
54
54
|
* escape hatch and its single overload.
|
|
55
55
|
*
|
|
56
|
+
* THE INPUT LIMIT IS DRAFT-07. A document whose root `$schema` names 2019-09 or
|
|
57
|
+
* 2020-12 is refused with an `UnsupportedDialectError` rather than read as
|
|
58
|
+
* Draft-07, because the dialects disagree about what unchanged keywords mean;
|
|
59
|
+
* a document with no `$schema` is read as Draft-07 as before. Pass
|
|
60
|
+
* `{ assumeDraft07: true }` as the third argument to take the Draft-07 reading
|
|
61
|
+
* deliberately. build-from-schema.ts states the divergence in full.
|
|
62
|
+
*
|
|
56
63
|
* The return type is INFERRED rather than written: naming `Validator<T>` would
|
|
57
64
|
* mean importing src/builder, which tier `extension` forbids. The inferred type
|
|
58
65
|
* is that same `Validator<T>` and the type test asserts it.
|
|
59
66
|
*/
|
|
60
|
-
export function fromJsonSchema(schema, config) {
|
|
61
|
-
return convertWithBag(jsonSchemaBag, schema, config);
|
|
67
|
+
export function fromJsonSchema(schema, config, options) {
|
|
68
|
+
return convertWithBag(jsonSchemaBag, schema, config, options);
|
|
62
69
|
}
|
|
@@ -18,6 +18,9 @@ export type { SchemaRegistry } from "./schema-registry";
|
|
|
18
18
|
export { createSchemaRegistry } from "./schema-registry";
|
|
19
19
|
export { UnsupportedKeywordError } from "./unsupported-keyword-error";
|
|
20
20
|
export { MalformedSchemaError } from "./malformed-schema-error";
|
|
21
|
+
export { DRAFT07_DIALECT_URI, UnsupportedDialectError, } from "./unsupported-dialect-error";
|
|
22
|
+
export { NON_DRAFT07_DIALECTS, assertSupportedDialect, } from "./assert-supported-dialect";
|
|
23
|
+
export type { DialectOptions } from "./assert-supported-dialect";
|
|
21
24
|
export { isDraft07Schema, isSchemaObject } from "./draft07.types";
|
|
22
25
|
export type { Draft07Schema, Draft07SchemaObject, Draft07TypeKeyword, } from "./draft07.types";
|
|
23
26
|
export { assertKeywordSupported, countKeywordHandlings, findKeywordHandling, isDraft07Keyword, listBoundPluginNames, listDraft07Keywords, } from "./keyword-map";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.listFormatNames = exports.findFormatHandling = exports.listDraft07Keywords = exports.listBoundPluginNames = exports.isDraft07Keyword = exports.findKeywordHandling = exports.countKeywordHandlings = exports.assertKeywordSupported = exports.isSchemaObject = exports.isDraft07Schema = exports.MalformedSchemaError = exports.UnsupportedKeywordError = exports.createSchemaRegistry = exports.createLocalScope = exports.createDocumentScope = exports.resolveRefInScope = exports.resolveRef = exports.isResolvableRef = exports.RefResolutionError = exports.declarePresenceRules = exports.declareRequiredProperties = exports.createStructuralContext = exports.toSchemaBranch = exports.collectSubSchemaRules = exports.readChildSchemas = exports.isStructuralKeyword = exports.expandSchemaRules = exports.STRUCTURAL_EXPANSIONS = exports.joinDeclaredPath = exports.flattenSchema = exports.toSchemaObject = exports.resolveSchemaNodeInScope = exports.resolveSchemaNode = exports.readRefPointer = exports.isDefinitionContainer = exports.collectDefinitionNames = exports.DEFINITION_CONTAINERS = exports.fromJsonSchema = exports.buildFromSchema = exports.buildFieldEntries = exports.NotASchemaError = void 0;
|
|
3
|
+
exports.listFormatNames = exports.findFormatHandling = exports.listDraft07Keywords = exports.listBoundPluginNames = exports.isDraft07Keyword = exports.findKeywordHandling = exports.countKeywordHandlings = exports.assertKeywordSupported = exports.isSchemaObject = exports.isDraft07Schema = exports.assertSupportedDialect = exports.NON_DRAFT07_DIALECTS = exports.UnsupportedDialectError = exports.DRAFT07_DIALECT_URI = exports.MalformedSchemaError = exports.UnsupportedKeywordError = exports.createSchemaRegistry = exports.createLocalScope = exports.createDocumentScope = exports.resolveRefInScope = exports.resolveRef = exports.isResolvableRef = exports.RefResolutionError = exports.declarePresenceRules = exports.declareRequiredProperties = exports.createStructuralContext = exports.toSchemaBranch = exports.collectSubSchemaRules = exports.readChildSchemas = exports.isStructuralKeyword = exports.expandSchemaRules = exports.STRUCTURAL_EXPANSIONS = exports.joinDeclaredPath = exports.flattenSchema = exports.toSchemaObject = exports.resolveSchemaNodeInScope = exports.resolveSchemaNode = exports.readRefPointer = exports.isDefinitionContainer = exports.collectDefinitionNames = exports.DEFINITION_CONTAINERS = exports.fromJsonSchema = exports.buildFromSchema = exports.buildFieldEntries = exports.NotASchemaError = void 0;
|
|
4
4
|
// ===========================================================================
|
|
5
5
|
// L8 src/json-schema/index.ts — re-exports only. Nothing is defined here.
|
|
6
6
|
//
|
|
@@ -52,6 +52,12 @@ var unsupported_keyword_error_1 = require("./unsupported-keyword-error");
|
|
|
52
52
|
Object.defineProperty(exports, "UnsupportedKeywordError", { enumerable: true, get: function () { return unsupported_keyword_error_1.UnsupportedKeywordError; } });
|
|
53
53
|
var malformed_schema_error_1 = require("./malformed-schema-error");
|
|
54
54
|
Object.defineProperty(exports, "MalformedSchemaError", { enumerable: true, get: function () { return malformed_schema_error_1.MalformedSchemaError; } });
|
|
55
|
+
var unsupported_dialect_error_1 = require("./unsupported-dialect-error");
|
|
56
|
+
Object.defineProperty(exports, "DRAFT07_DIALECT_URI", { enumerable: true, get: function () { return unsupported_dialect_error_1.DRAFT07_DIALECT_URI; } });
|
|
57
|
+
Object.defineProperty(exports, "UnsupportedDialectError", { enumerable: true, get: function () { return unsupported_dialect_error_1.UnsupportedDialectError; } });
|
|
58
|
+
var assert_supported_dialect_1 = require("./assert-supported-dialect");
|
|
59
|
+
Object.defineProperty(exports, "NON_DRAFT07_DIALECTS", { enumerable: true, get: function () { return assert_supported_dialect_1.NON_DRAFT07_DIALECTS; } });
|
|
60
|
+
Object.defineProperty(exports, "assertSupportedDialect", { enumerable: true, get: function () { return assert_supported_dialect_1.assertSupportedDialect; } });
|
|
55
61
|
var draft07_types_1 = require("./draft07.types");
|
|
56
62
|
Object.defineProperty(exports, "isDraft07Schema", { enumerable: true, get: function () { return draft07_types_1.isDraft07Schema; } });
|
|
57
63
|
Object.defineProperty(exports, "isSchemaObject", { enumerable: true, get: function () { return draft07_types_1.isSchemaObject; } });
|
|
@@ -18,6 +18,8 @@ export { createDocumentScope, createLocalScope } from "./ref-scope.mjs";
|
|
|
18
18
|
export { createSchemaRegistry } from "./schema-registry.mjs";
|
|
19
19
|
export { UnsupportedKeywordError } from "./unsupported-keyword-error.mjs";
|
|
20
20
|
export { MalformedSchemaError } from "./malformed-schema-error.mjs";
|
|
21
|
+
export { DRAFT07_DIALECT_URI, UnsupportedDialectError, } from "./unsupported-dialect-error.mjs";
|
|
22
|
+
export { NON_DRAFT07_DIALECTS, assertSupportedDialect, } from "./assert-supported-dialect.mjs";
|
|
21
23
|
export { isDraft07Schema, isSchemaObject } from "./draft07.types.mjs";
|
|
22
24
|
export { assertKeywordSupported, countKeywordHandlings, findKeywordHandling, isDraft07Keyword, listBoundPluginNames, listDraft07Keywords, } from "./keyword-map.mjs";
|
|
23
25
|
export { findFormatHandling, listFormatNames } from "./format-map.mjs";
|
|
@@ -25,7 +25,9 @@ const ANNOTATION_ONLY = "an annotation with no validation effect in Draft-07";
|
|
|
25
25
|
*/
|
|
26
26
|
exports.constBinding = (0, bind_keyword_1.bindKeyword)("any", "literal", literal_1.literalPlugin, (v) => [v]);
|
|
27
27
|
exports.coreKeywordMap = {
|
|
28
|
-
$schema: (0, bind_keyword_1.unsupported)("read
|
|
28
|
+
$schema: (0, bind_keyword_1.unsupported)("read at the document ROOT by assert-supported-dialect and then " +
|
|
29
|
+
"discarded: Luq implements Draft-07 and never switches dialect, so a " +
|
|
30
|
+
"root `$schema` naming another one is refused rather than ignored"),
|
|
29
31
|
$id: (0, bind_keyword_1.unsupported)("no base-URI resolution; resolve-ref accepts local $ref only"),
|
|
30
32
|
$comment: (0, bind_keyword_1.unsupported)(ANNOTATION_ONLY),
|
|
31
33
|
$ref: (0, bind_keyword_1.structural)("resolved by resolve-ref before any keyword is read"),
|
|
@@ -22,7 +22,9 @@ const ANNOTATION_ONLY = "an annotation with no validation effect in Draft-07";
|
|
|
22
22
|
*/
|
|
23
23
|
export const constBinding = bindKeyword("any", "literal", literalPlugin, (v) => [v]);
|
|
24
24
|
export const coreKeywordMap = {
|
|
25
|
-
$schema: unsupported("read
|
|
25
|
+
$schema: unsupported("read at the document ROOT by assert-supported-dialect and then " +
|
|
26
|
+
"discarded: Luq implements Draft-07 and never switches dialect, so a " +
|
|
27
|
+
"root `$schema` naming another one is refused rather than ignored"),
|
|
26
28
|
$id: unsupported("no base-URI resolution; resolve-ref accepts local $ref only"),
|
|
27
29
|
$comment: unsupported(ANNOTATION_ONLY),
|
|
28
30
|
$ref: structural("resolved by resolve-ref before any keyword is read"),
|
|
@@ -32,7 +32,10 @@ exports.NON_DRAFT07_KEYWORDS = {
|
|
|
32
32
|
$defs: "2019-09 spelling of `definitions`; resolve-ref follows it anyway",
|
|
33
33
|
$anchor: "2019-09 anchors; resolve-ref resolves JSON Pointers only",
|
|
34
34
|
$dynamicRef: "2020-12 dynamic references",
|
|
35
|
+
$dynamicAnchor: "2020-12 dynamic anchors; the target half of $dynamicRef",
|
|
35
36
|
$recursiveRef: "2019-09 recursive references",
|
|
37
|
+
$recursiveAnchor: "2019-09 recursive anchors; the target half of $recursiveRef",
|
|
38
|
+
$vocabulary: "2019-09 vocabulary declaration; Luq has one fixed vocabulary",
|
|
36
39
|
dependentRequired: "2019-09 split of `dependencies`; use `dependencies`",
|
|
37
40
|
dependentSchemas: "2019-09 split of `dependencies`; use `dependencies`",
|
|
38
41
|
minContains: "2019-09 bound on `contains`; Draft-07's is fixed at one",
|
|
@@ -23,7 +23,10 @@ export const NON_DRAFT07_KEYWORDS = {
|
|
|
23
23
|
$defs: "2019-09 spelling of `definitions`; resolve-ref follows it anyway",
|
|
24
24
|
$anchor: "2019-09 anchors; resolve-ref resolves JSON Pointers only",
|
|
25
25
|
$dynamicRef: "2020-12 dynamic references",
|
|
26
|
+
$dynamicAnchor: "2020-12 dynamic anchors; the target half of $dynamicRef",
|
|
26
27
|
$recursiveRef: "2019-09 recursive references",
|
|
28
|
+
$recursiveAnchor: "2019-09 recursive anchors; the target half of $recursiveRef",
|
|
29
|
+
$vocabulary: "2019-09 vocabulary declaration; Luq has one fixed vocabulary",
|
|
27
30
|
dependentRequired: "2019-09 split of `dependencies`; use `dependencies`",
|
|
28
31
|
dependentSchemas: "2019-09 split of `dependencies`; use `dependencies`",
|
|
29
32
|
minContains: "2019-09 bound on `contains`; Draft-07's is fixed at one",
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** The dialect this library implements, in the spelling the draft publishes. */
|
|
2
|
+
export declare const DRAFT07_DIALECT_URI = "http://json-schema.org/draft-07/schema#";
|
|
3
|
+
/**
|
|
4
|
+
* Raised when a document's root `$schema` names a dialect Luq does not
|
|
5
|
+
* implement. Thrown at build time, before a validator that would read the
|
|
6
|
+
* document under the wrong dialect's rules can exist.
|
|
7
|
+
*
|
|
8
|
+
* `reason` follows the convention the other two refusals use: a sentence
|
|
9
|
+
* fragment with no terminal punctuation, saying what the dialect does that
|
|
10
|
+
* Draft-07 does not, so that two refusals from two different dialects read as
|
|
11
|
+
* one library rather than as two unrelated messages.
|
|
12
|
+
*/
|
|
13
|
+
export declare class UnsupportedDialectError extends Error {
|
|
14
|
+
/** The `$schema` value the document wrote, verbatim. */
|
|
15
|
+
readonly declared: string;
|
|
16
|
+
/** The dialect Luq reads every document under. */
|
|
17
|
+
readonly implemented: string;
|
|
18
|
+
/** What that dialect does differently, or that it is unrecognised. */
|
|
19
|
+
readonly reason: string;
|
|
20
|
+
constructor(declared: string, reason: string);
|
|
21
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ===========================================================================
|
|
3
|
+
// L8 src/json-schema/unsupported-dialect-error.ts
|
|
4
|
+
//
|
|
5
|
+
// The third refusal, and the only one with no keyword to hang on.
|
|
6
|
+
// UnsupportedKeywordError refuses a keyword NAME; MalformedSchemaError refuses
|
|
7
|
+
// a keyword VALUE. Both need the document to write something recognisable, and
|
|
8
|
+
// there is one divergence that writes nothing at all:
|
|
9
|
+
//
|
|
10
|
+
// {"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
11
|
+
// "$defs": {"name": {"type": "string"}},
|
|
12
|
+
// "properties": {"nick": {"$ref": "#/$defs/name", "minLength": 5}}}
|
|
13
|
+
//
|
|
14
|
+
// Every keyword there is a Draft-07 keyword spelled the Draft-07 way, so the
|
|
15
|
+
// keyword table sees nothing to refuse. The meaning is not Draft-07: §8.3 has
|
|
16
|
+
// `$ref` REPLACE the object it appears in, and from 2019-09 on `$ref` is an
|
|
17
|
+
// ordinary applicator whose siblings are applied. Read as Draft-07 the document
|
|
18
|
+
// above builds a validator that accepts `{"nick":"ab"}` — the `minLength` is
|
|
19
|
+
// gone, silently, and nothing in the result says a constraint went missing.
|
|
20
|
+
// `$schema` is the only signal the document offers, so it is the one read.
|
|
21
|
+
//
|
|
22
|
+
// It stays a separate class from the other two, neither extending the other,
|
|
23
|
+
// for the reason written in malformed-schema-error.ts: each answers a different
|
|
24
|
+
// question. "Luq cannot honour this keyword", "this document is not valid
|
|
25
|
+
// Draft-07", and now "this document is not Draft-07 AT ALL" are three distinct
|
|
26
|
+
// facts, and a caller catching one must not silently catch another.
|
|
27
|
+
// ===========================================================================
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.UnsupportedDialectError = exports.DRAFT07_DIALECT_URI = void 0;
|
|
30
|
+
/** The dialect this library implements, in the spelling the draft publishes. */
|
|
31
|
+
exports.DRAFT07_DIALECT_URI = "http://json-schema.org/draft-07/schema#";
|
|
32
|
+
/**
|
|
33
|
+
* Raised when a document's root `$schema` names a dialect Luq does not
|
|
34
|
+
* implement. Thrown at build time, before a validator that would read the
|
|
35
|
+
* document under the wrong dialect's rules can exist.
|
|
36
|
+
*
|
|
37
|
+
* `reason` follows the convention the other two refusals use: a sentence
|
|
38
|
+
* fragment with no terminal punctuation, saying what the dialect does that
|
|
39
|
+
* Draft-07 does not, so that two refusals from two different dialects read as
|
|
40
|
+
* one library rather than as two unrelated messages.
|
|
41
|
+
*/
|
|
42
|
+
class UnsupportedDialectError extends Error {
|
|
43
|
+
constructor(declared, reason) {
|
|
44
|
+
super(`JSON Schema dialect "${declared}" is not supported: ${reason}. Luq ` +
|
|
45
|
+
`implements Draft-07 (${exports.DRAFT07_DIALECT_URI}) and would read this ` +
|
|
46
|
+
"document under Draft-07 rules, which can enforce less than it " +
|
|
47
|
+
"states. Convert the document to Draft-07, or pass " +
|
|
48
|
+
"`{ assumeDraft07: true }` to take the Draft-07 reading deliberately.");
|
|
49
|
+
/** The dialect Luq reads every document under. */
|
|
50
|
+
this.implemented = exports.DRAFT07_DIALECT_URI;
|
|
51
|
+
this.name = "UnsupportedDialectError";
|
|
52
|
+
this.declared = declared;
|
|
53
|
+
this.reason = reason;
|
|
54
|
+
// Without this, `instanceof` fails when the package is compiled to ES5.
|
|
55
|
+
Object.setPrototypeOf(this, UnsupportedDialectError.prototype);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
exports.UnsupportedDialectError = UnsupportedDialectError;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// ===========================================================================
|
|
2
|
+
// L8 src/json-schema/unsupported-dialect-error.ts
|
|
3
|
+
//
|
|
4
|
+
// The third refusal, and the only one with no keyword to hang on.
|
|
5
|
+
// UnsupportedKeywordError refuses a keyword NAME; MalformedSchemaError refuses
|
|
6
|
+
// a keyword VALUE. Both need the document to write something recognisable, and
|
|
7
|
+
// there is one divergence that writes nothing at all:
|
|
8
|
+
//
|
|
9
|
+
// {"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
10
|
+
// "$defs": {"name": {"type": "string"}},
|
|
11
|
+
// "properties": {"nick": {"$ref": "#/$defs/name", "minLength": 5}}}
|
|
12
|
+
//
|
|
13
|
+
// Every keyword there is a Draft-07 keyword spelled the Draft-07 way, so the
|
|
14
|
+
// keyword table sees nothing to refuse. The meaning is not Draft-07: §8.3 has
|
|
15
|
+
// `$ref` REPLACE the object it appears in, and from 2019-09 on `$ref` is an
|
|
16
|
+
// ordinary applicator whose siblings are applied. Read as Draft-07 the document
|
|
17
|
+
// above builds a validator that accepts `{"nick":"ab"}` — the `minLength` is
|
|
18
|
+
// gone, silently, and nothing in the result says a constraint went missing.
|
|
19
|
+
// `$schema` is the only signal the document offers, so it is the one read.
|
|
20
|
+
//
|
|
21
|
+
// It stays a separate class from the other two, neither extending the other,
|
|
22
|
+
// for the reason written in malformed-schema-error.ts: each answers a different
|
|
23
|
+
// question. "Luq cannot honour this keyword", "this document is not valid
|
|
24
|
+
// Draft-07", and now "this document is not Draft-07 AT ALL" are three distinct
|
|
25
|
+
// facts, and a caller catching one must not silently catch another.
|
|
26
|
+
// ===========================================================================
|
|
27
|
+
/** The dialect this library implements, in the spelling the draft publishes. */
|
|
28
|
+
export const DRAFT07_DIALECT_URI = "http://json-schema.org/draft-07/schema#";
|
|
29
|
+
/**
|
|
30
|
+
* Raised when a document's root `$schema` names a dialect Luq does not
|
|
31
|
+
* implement. Thrown at build time, before a validator that would read the
|
|
32
|
+
* document under the wrong dialect's rules can exist.
|
|
33
|
+
*
|
|
34
|
+
* `reason` follows the convention the other two refusals use: a sentence
|
|
35
|
+
* fragment with no terminal punctuation, saying what the dialect does that
|
|
36
|
+
* Draft-07 does not, so that two refusals from two different dialects read as
|
|
37
|
+
* one library rather than as two unrelated messages.
|
|
38
|
+
*/
|
|
39
|
+
export class UnsupportedDialectError extends Error {
|
|
40
|
+
constructor(declared, reason) {
|
|
41
|
+
super(`JSON Schema dialect "${declared}" is not supported: ${reason}. Luq ` +
|
|
42
|
+
`implements Draft-07 (${DRAFT07_DIALECT_URI}) and would read this ` +
|
|
43
|
+
"document under Draft-07 rules, which can enforce less than it " +
|
|
44
|
+
"states. Convert the document to Draft-07, or pass " +
|
|
45
|
+
"`{ assumeDraft07: true }` to take the Draft-07 reading deliberately.");
|
|
46
|
+
/** The dialect Luq reads every document under. */
|
|
47
|
+
this.implemented = DRAFT07_DIALECT_URI;
|
|
48
|
+
this.name = "UnsupportedDialectError";
|
|
49
|
+
this.declared = declared;
|
|
50
|
+
this.reason = reason;
|
|
51
|
+
// Without this, `instanceof` fails when the package is compiled to ES5.
|
|
52
|
+
Object.setPrototypeOf(this, UnsupportedDialectError.prototype);
|
|
53
|
+
}
|
|
54
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maroonedog/luq",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.0",
|
|
4
4
|
"description": "Universal Model & API Definition Platform - TypeScript validation library evolving into cross-language code generation",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -459,18 +459,20 @@
|
|
|
459
459
|
"typecheck:src": "tsc --noEmit -p tsconfig.json",
|
|
460
460
|
"typecheck:scripts": "tsc --noEmit -p scripts/tsconfig.json",
|
|
461
461
|
"typecheck:bench": "tsc --noEmit -p bench/tsconfig.json",
|
|
462
|
-
"generate": "npm run generate:sources && npm run generate:exports && npm run generate:lock",
|
|
462
|
+
"generate": "npm run generate:sources && npm run generate:exports && npm run generate:lock && npm run generate:issue-codes",
|
|
463
463
|
"generate:sources": "npm run generate:manifest && npm run generate:barrel && npm run generate:slot-catalog",
|
|
464
464
|
"generate:slot-catalog": "npx ts-node --project scripts/tsconfig.json scripts/generate-slot-catalog.ts",
|
|
465
465
|
"generate:manifest": "npx ts-node --project scripts/tsconfig.json scripts/generate-plugin-manifest.ts",
|
|
466
466
|
"generate:barrel": "npx ts-node --project scripts/tsconfig.json scripts/generate-plugin-barrel.ts",
|
|
467
467
|
"generate:exports": "npx ts-node --project scripts/tsconfig.json scripts/generate-package-exports.ts",
|
|
468
468
|
"generate:lock": "npx ts-node --project scripts/tsconfig.json scripts/generate-plugin-catalog.ts",
|
|
469
|
+
"generate:issue-codes": "npx ts-node --project scripts/tsconfig.json scripts/generate-issue-code-catalog.ts",
|
|
469
470
|
"check:exports": "npx ts-node --project scripts/tsconfig.json scripts/check-exports.ts --mode=exact",
|
|
470
471
|
"check:catalog-lock": "npx ts-node --project scripts/tsconfig.json scripts/check-catalog-lock.ts",
|
|
472
|
+
"check:issue-code-lock": "npx ts-node --project scripts/tsconfig.json scripts/check-issue-code-lock.ts",
|
|
471
473
|
"check:plugin-isolation": "npx ts-node --project scripts/tsconfig.json scripts/check-plugin-isolation.ts",
|
|
472
474
|
"check:plugin-uniqueness": "npx ts-node --project scripts/tsconfig.json scripts/check-plugin-uniqueness.ts",
|
|
473
|
-
"check:catalog": "npm run check:plugin-uniqueness && npm run check:plugin-isolation && npm run check:exports && npm run check:catalog-lock",
|
|
475
|
+
"check:catalog": "npm run check:plugin-uniqueness && npm run check:plugin-isolation && npm run check:exports && npm run check:catalog-lock && npm run check:issue-code-lock",
|
|
474
476
|
"check:size": "npx ts-node --project scripts/tsconfig.json scripts/measure-bundle-size.ts",
|
|
475
477
|
"check:barrel-equivalence": "npx ts-node --project scripts/tsconfig.json scripts/check-barrel-equivalence.ts",
|
|
476
478
|
"check:no-dynamic-code": "npx ts-node --project scripts/tsconfig.json scripts/check-no-dynamic-code.ts",
|
|
@@ -493,6 +495,7 @@
|
|
|
493
495
|
"check:conformance-figures": "npx ts-node --project scripts/tsconfig.json scripts/check-conformance-figures.ts",
|
|
494
496
|
"bench:competitors": "npx ts-node --project bench/tsconfig.json bench/competitors/report-competitors.ts",
|
|
495
497
|
"bench:competitors:check": "npx ts-node --project bench/tsconfig.json bench/competitors/check-competitors.ts",
|
|
498
|
+
"bench:megamorphism": "npx ts-node --project bench/tsconfig.json bench/megamorphism/report-megamorphism.ts",
|
|
496
499
|
"lint:ox": "oxlint -c .oxlintrc.json src bench",
|
|
497
500
|
"lint:naming": "eslint \"src/**/*.ts\" \"bench/**/*.ts\"",
|
|
498
501
|
"generate:perf-figures": "npx ts-node --project scripts/tsconfig.json scripts/check-perf-figures.ts --write",
|