@maroonedog/luq 2.2.0 → 2.3.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 +47 -602
- package/dist/builder/compile-declarations.d.ts +7 -1
- package/dist/builder/compile-declarations.js +18 -8
- package/dist/builder/compile-declarations.mjs +18 -8
- package/dist/builder/create-builder.js +2 -3
- package/dist/builder/create-builder.mjs +2 -3
- package/dist/builder/create-field-builder.js +13 -1
- package/dist/builder/create-field-builder.mjs +13 -1
- package/dist/builder/declared-calls-store.d.ts +9 -0
- package/dist/builder/declared-calls-store.js +19 -0
- package/dist/builder/declared-calls-store.mjs +15 -0
- package/dist/builder/field-declared-calls.types.d.ts +6 -0
- package/dist/builder/field-declared-calls.types.js +2 -0
- package/dist/builder/field-declared-calls.types.mjs +1 -0
- package/dist/builder/field-entry.types.d.ts +9 -3
- package/dist/builder/field-options.types.d.ts +26 -0
- package/dist/chain/bundle-paths.types.d.ts +9 -12
- package/dist/chain/chain-node-store.d.ts +5 -0
- package/dist/chain/chain-node-store.js +15 -0
- package/dist/chain/chain-node-store.mjs +11 -0
- package/dist/chain/collect-field-rules.d.ts +13 -2
- package/dist/chain/collect-field-rules.js +10 -3
- package/dist/chain/collect-field-rules.mjs +10 -3
- package/dist/chain/create-chain-node.js +19 -9
- package/dist/chain/create-chain-node.mjs +19 -9
- package/dist/chain/declaration-recorder.port.d.ts +31 -0
- package/dist/chain/declaration-recorder.port.js +17 -0
- package/dist/chain/declaration-recorder.port.mjs +13 -0
- package/dist/chain/declared-call.types.d.ts +15 -0
- package/dist/chain/declared-call.types.js +2 -0
- package/dist/chain/declared-call.types.mjs +1 -0
- package/dist/chain/index.d.ts +3 -1
- package/dist/compile/compile-array-node.js +1 -0
- package/dist/compile/compile-array-node.mjs +1 -0
- package/dist/compile/compile-field.d.ts +1 -0
- package/dist/compile/compile-field.js +8 -6
- package/dist/compile/compile-field.mjs +8 -6
- package/dist/compile/compile-schema.js +4 -0
- package/dist/compile/compile-schema.mjs +4 -0
- package/dist/compile/group-array-fields.d.ts +1 -0
- package/dist/compile/split-rules-by-kind.js +10 -14
- package/dist/compile/split-rules-by-kind.mjs +10 -14
- package/dist/compile/validation-plan.types.d.ts +19 -6
- package/dist/core/type-erasure.d.ts +36 -30
- package/dist/core/type-erasure.js +36 -30
- package/dist/core/type-erasure.mjs +36 -30
- package/dist/json-schema/build-from-schema.js +8 -1
- package/dist/json-schema/build-from-schema.mjs +8 -1
- package/dist/json-schema/declare-additional-properties.d.ts +7 -7
- package/dist/json-schema/declare-additional-properties.js +7 -7
- package/dist/json-schema/declare-additional-properties.mjs +7 -7
- package/dist/json-schema/declare-object-keywords.js +4 -4
- package/dist/json-schema/declare-object-keywords.mjs +4 -4
- package/dist/json-schema/follow-json-pointer.d.ts +7 -6
- package/dist/json-schema/follow-json-pointer.js +24 -24
- package/dist/json-schema/follow-json-pointer.mjs +24 -24
- package/dist/json-schema/ref-resolution-error.js +3 -3
- package/dist/json-schema/ref-resolution-error.mjs +3 -3
- package/dist/json-schema/schema-registry.js +12 -11
- package/dist/json-schema/schema-registry.mjs +12 -11
- package/dist/json-schema/uri-reference.js +12 -12
- package/dist/json-schema/uri-reference.mjs +12 -12
- package/dist/path/create-value-writer.js +12 -12
- package/dist/path/create-value-writer.mjs +12 -12
- package/dist/path/reserved-segment.d.ts +16 -16
- package/dist/path/reserved-segment.js +17 -21
- package/dist/path/reserved-segment.mjs +17 -21
- package/dist/plugins/index.generated.js +2 -2
- package/dist/plugins/index.generated.mjs +2 -2
- package/dist/plugins/manifest.generated.js +2 -2
- package/dist/plugins/manifest.generated.mjs +2 -2
- package/dist/plugins/object-additional-properties/select-additional-keys.d.ts +8 -8
- package/dist/plugins/object-additional-properties/select-additional-keys.js +16 -16
- package/dist/plugins/object-additional-properties/select-additional-keys.mjs +16 -16
- package/dist/plugins/stitch/stitch.d.ts +10 -14
- package/dist/plugins/stitch-with/stitch-with.d.ts +1 -1
- package/dist/plugins/stitch-with/stitch-with.js +22 -24
- package/dist/plugins/stitch-with/stitch-with.mjs +22 -24
- package/dist/plugins/string-min/string-min.js +6 -8
- package/dist/plugins/string-min/string-min.mjs +6 -8
- package/dist/presets/presets.d.ts +10 -11
- package/dist/presets/presets.js +22 -23
- package/dist/presets/presets.mjs +22 -23
- package/dist/runtime/create-field-validator.js +4 -6
- package/dist/runtime/create-field-validator.mjs +4 -6
- package/dist/runtime/create-validator.js +11 -11
- package/dist/runtime/create-validator.mjs +11 -11
- package/dist/runtime/output-writer.js +5 -1
- package/dist/runtime/output-writer.mjs +5 -1
- package/dist/runtime/run-array-node.js +6 -6
- package/dist/runtime/run-array-node.mjs +6 -6
- package/dist/runtime/run-field.js +18 -19
- package/dist/runtime/run-field.mjs +18 -19
- package/dist/standard-schema/assemble-json-schema.d.ts +4 -0
- package/dist/standard-schema/assemble-json-schema.js +95 -0
- package/dist/standard-schema/assemble-json-schema.mjs +92 -0
- package/dist/standard-schema/declaration-recorder.d.ts +6 -0
- package/dist/standard-schema/declaration-recorder.js +30 -0
- package/dist/standard-schema/declaration-recorder.mjs +27 -0
- package/dist/standard-schema/declarations-unavailable-error.d.ts +4 -0
- package/dist/standard-schema/declarations-unavailable-error.js +32 -0
- package/dist/standard-schema/declarations-unavailable-error.mjs +28 -0
- package/dist/standard-schema/emit-field-schema.d.ts +9 -0
- package/dist/standard-schema/emit-field-schema.js +68 -0
- package/dist/standard-schema/emit-field-schema.mjs +65 -0
- package/dist/standard-schema/index.d.ts +5 -0
- package/dist/standard-schema/index.js +9 -1
- package/dist/standard-schema/index.mjs +4 -0
- package/dist/standard-schema/json-schema-target.d.ts +6 -0
- package/dist/standard-schema/json-schema-target.js +44 -0
- package/dist/standard-schema/json-schema-target.mjs +39 -0
- package/dist/standard-schema/plugin-keyword-map.d.ts +3 -0
- package/dist/standard-schema/plugin-keyword-map.js +93 -0
- package/dist/standard-schema/plugin-keyword-map.mjs +90 -0
- package/dist/standard-schema/split-issue-path.d.ts +6 -4
- package/dist/standard-schema/split-issue-path.js +15 -13
- package/dist/standard-schema/split-issue-path.mjs +15 -13
- package/dist/standard-schema/standard-schema.types.d.ts +8 -7
- package/dist/standard-schema/standard-schema.types.js +6 -6
- package/dist/standard-schema/standard-schema.types.mjs +6 -6
- package/dist/standard-schema/to-standard-json-schema.d.ts +19 -0
- package/dist/standard-schema/to-standard-json-schema.js +36 -0
- package/dist/standard-schema/to-standard-json-schema.mjs +33 -0
- package/dist/standard-schema/to-standard-schema.d.ts +16 -15
- package/dist/standard-schema/to-standard-schema.js +15 -22
- package/dist/standard-schema/to-standard-schema.mjs +15 -22
- package/dist/standard-schema/unrepresentable-rule-error.d.ts +15 -0
- package/dist/standard-schema/unrepresentable-rule-error.js +43 -0
- package/dist/standard-schema/unrepresentable-rule-error.mjs +38 -0
- package/dist/types/index.d.ts +12 -12
- package/dist/types/index.js +7 -7
- package/dist/types/index.mjs +7 -7
- package/package.json +1 -1
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.assembleJsonSchema = assembleJsonSchema;
|
|
4
|
+
// ===========================================================================
|
|
5
|
+
// L10 src/standard-schema/assemble-json-schema.ts
|
|
6
|
+
//
|
|
7
|
+
// Builds the nested object and array schema from a list of paths.
|
|
8
|
+
//
|
|
9
|
+
// `"owner.name"` becomes `properties.owner.properties.name`, and
|
|
10
|
+
// `"employees[*].name"` becomes
|
|
11
|
+
// `properties.employees.items.properties.name`. Paths are read with the
|
|
12
|
+
// existing path parser rather than a second grammar written here.
|
|
13
|
+
//
|
|
14
|
+
// `required` belongs to the PARENT. In JSON Schema it says "this object must
|
|
15
|
+
// carry this key", which is a fact about the object, not about the key.
|
|
16
|
+
// ===========================================================================
|
|
17
|
+
const parse_field_path_1 = require("../path/parse-field-path");
|
|
18
|
+
const emit_field_schema_1 = require("./emit-field-schema");
|
|
19
|
+
const declarations_unavailable_error_1 = require("./declarations-unavailable-error");
|
|
20
|
+
const newNode = () => ({
|
|
21
|
+
properties: new Map(),
|
|
22
|
+
required: new Set(),
|
|
23
|
+
element: null,
|
|
24
|
+
leaf: null,
|
|
25
|
+
});
|
|
26
|
+
/** Descends through `key`, creating it when absent. */
|
|
27
|
+
function descend(node, key) {
|
|
28
|
+
const existing = node.properties.get(key);
|
|
29
|
+
if (existing !== undefined)
|
|
30
|
+
return existing;
|
|
31
|
+
const created = newNode();
|
|
32
|
+
node.properties.set(key, created);
|
|
33
|
+
return created;
|
|
34
|
+
}
|
|
35
|
+
/** Places one path in the tree, setting the schema where the leaf lands. */
|
|
36
|
+
function place(root, segments, schema, isRequired) {
|
|
37
|
+
let node = root;
|
|
38
|
+
let owner = null;
|
|
39
|
+
let ownerKey = "";
|
|
40
|
+
for (const segment of segments) {
|
|
41
|
+
if (segment.kind === "each") {
|
|
42
|
+
node.element ?? (node.element = newNode());
|
|
43
|
+
node = node.element;
|
|
44
|
+
owner = null;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
owner = node;
|
|
48
|
+
ownerKey = segment.key;
|
|
49
|
+
node = descend(node, segment.key);
|
|
50
|
+
}
|
|
51
|
+
node.leaf = schema;
|
|
52
|
+
if (isRequired && owner !== null)
|
|
53
|
+
owner.required.add(ownerKey);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Freezes a node into a JSON Schema.
|
|
57
|
+
*
|
|
58
|
+
* On a node that has children, the leaf schema is the FOUNDATION. When both
|
|
59
|
+
* `.object.required()` and `"user.name"` are declared, the properties from
|
|
60
|
+
* the latter sit on top of the `type: "object"` from the former.
|
|
61
|
+
*/
|
|
62
|
+
function freeze(node) {
|
|
63
|
+
const schema = { ...node.leaf };
|
|
64
|
+
if (node.element !== null) {
|
|
65
|
+
schema["type"] ?? (schema["type"] = "array");
|
|
66
|
+
schema["items"] = freeze(node.element);
|
|
67
|
+
}
|
|
68
|
+
if (node.properties.size > 0) {
|
|
69
|
+
schema["type"] ?? (schema["type"] = "object");
|
|
70
|
+
const properties = {};
|
|
71
|
+
for (const [key, child] of node.properties) {
|
|
72
|
+
properties[key] = freeze(child);
|
|
73
|
+
}
|
|
74
|
+
schema["properties"] = properties;
|
|
75
|
+
}
|
|
76
|
+
if (node.required.size > 0)
|
|
77
|
+
schema["required"] = [...node.required];
|
|
78
|
+
return schema;
|
|
79
|
+
}
|
|
80
|
+
/** Assembles the root schema from the list of declarations. */
|
|
81
|
+
function assembleJsonSchema(fields, policy) {
|
|
82
|
+
const root = newNode();
|
|
83
|
+
for (const field of fields) {
|
|
84
|
+
// One field with no record is enough to refuse, whatever the policy says.
|
|
85
|
+
if (field.calls === null)
|
|
86
|
+
throw new declarations_unavailable_error_1.DeclarationsUnavailableError(field.path);
|
|
87
|
+
const emitted = (0, emit_field_schema_1.emitFieldSchema)(field.path, field.calls, policy);
|
|
88
|
+
place(root, (0, parse_field_path_1.parseFieldPath)(field.path), emitted.schema, emitted.isRequired);
|
|
89
|
+
}
|
|
90
|
+
const schema = freeze(root);
|
|
91
|
+
// Even with nothing declared, or everything dropped, the root is an object:
|
|
92
|
+
// the builder only accepts an object type in the first place.
|
|
93
|
+
schema["type"] ?? (schema["type"] = "object");
|
|
94
|
+
return schema;
|
|
95
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// ===========================================================================
|
|
2
|
+
// L10 src/standard-schema/assemble-json-schema.ts
|
|
3
|
+
//
|
|
4
|
+
// Builds the nested object and array schema from a list of paths.
|
|
5
|
+
//
|
|
6
|
+
// `"owner.name"` becomes `properties.owner.properties.name`, and
|
|
7
|
+
// `"employees[*].name"` becomes
|
|
8
|
+
// `properties.employees.items.properties.name`. Paths are read with the
|
|
9
|
+
// existing path parser rather than a second grammar written here.
|
|
10
|
+
//
|
|
11
|
+
// `required` belongs to the PARENT. In JSON Schema it says "this object must
|
|
12
|
+
// carry this key", which is a fact about the object, not about the key.
|
|
13
|
+
// ===========================================================================
|
|
14
|
+
import { parseFieldPath } from "../path/parse-field-path.mjs";
|
|
15
|
+
import { emitFieldSchema } from "./emit-field-schema.mjs";
|
|
16
|
+
import { DeclarationsUnavailableError } from "./declarations-unavailable-error.mjs";
|
|
17
|
+
const newNode = () => ({
|
|
18
|
+
properties: new Map(),
|
|
19
|
+
required: new Set(),
|
|
20
|
+
element: null,
|
|
21
|
+
leaf: null,
|
|
22
|
+
});
|
|
23
|
+
/** Descends through `key`, creating it when absent. */
|
|
24
|
+
function descend(node, key) {
|
|
25
|
+
const existing = node.properties.get(key);
|
|
26
|
+
if (existing !== undefined)
|
|
27
|
+
return existing;
|
|
28
|
+
const created = newNode();
|
|
29
|
+
node.properties.set(key, created);
|
|
30
|
+
return created;
|
|
31
|
+
}
|
|
32
|
+
/** Places one path in the tree, setting the schema where the leaf lands. */
|
|
33
|
+
function place(root, segments, schema, isRequired) {
|
|
34
|
+
let node = root;
|
|
35
|
+
let owner = null;
|
|
36
|
+
let ownerKey = "";
|
|
37
|
+
for (const segment of segments) {
|
|
38
|
+
if (segment.kind === "each") {
|
|
39
|
+
node.element ?? (node.element = newNode());
|
|
40
|
+
node = node.element;
|
|
41
|
+
owner = null;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
owner = node;
|
|
45
|
+
ownerKey = segment.key;
|
|
46
|
+
node = descend(node, segment.key);
|
|
47
|
+
}
|
|
48
|
+
node.leaf = schema;
|
|
49
|
+
if (isRequired && owner !== null)
|
|
50
|
+
owner.required.add(ownerKey);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Freezes a node into a JSON Schema.
|
|
54
|
+
*
|
|
55
|
+
* On a node that has children, the leaf schema is the FOUNDATION. When both
|
|
56
|
+
* `.object.required()` and `"user.name"` are declared, the properties from
|
|
57
|
+
* the latter sit on top of the `type: "object"` from the former.
|
|
58
|
+
*/
|
|
59
|
+
function freeze(node) {
|
|
60
|
+
const schema = { ...node.leaf };
|
|
61
|
+
if (node.element !== null) {
|
|
62
|
+
schema["type"] ?? (schema["type"] = "array");
|
|
63
|
+
schema["items"] = freeze(node.element);
|
|
64
|
+
}
|
|
65
|
+
if (node.properties.size > 0) {
|
|
66
|
+
schema["type"] ?? (schema["type"] = "object");
|
|
67
|
+
const properties = {};
|
|
68
|
+
for (const [key, child] of node.properties) {
|
|
69
|
+
properties[key] = freeze(child);
|
|
70
|
+
}
|
|
71
|
+
schema["properties"] = properties;
|
|
72
|
+
}
|
|
73
|
+
if (node.required.size > 0)
|
|
74
|
+
schema["required"] = [...node.required];
|
|
75
|
+
return schema;
|
|
76
|
+
}
|
|
77
|
+
/** Assembles the root schema from the list of declarations. */
|
|
78
|
+
export function assembleJsonSchema(fields, policy) {
|
|
79
|
+
const root = newNode();
|
|
80
|
+
for (const field of fields) {
|
|
81
|
+
// One field with no record is enough to refuse, whatever the policy says.
|
|
82
|
+
if (field.calls === null)
|
|
83
|
+
throw new DeclarationsUnavailableError(field.path);
|
|
84
|
+
const emitted = emitFieldSchema(field.path, field.calls, policy);
|
|
85
|
+
place(root, parseFieldPath(field.path), emitted.schema, emitted.isRequired);
|
|
86
|
+
}
|
|
87
|
+
const schema = freeze(root);
|
|
88
|
+
// Even with nothing declared, or everything dropped, the root is an object:
|
|
89
|
+
// the builder only accepts an object type in the first place.
|
|
90
|
+
schema["type"] ?? (schema["type"] = "object");
|
|
91
|
+
return schema;
|
|
92
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Idempotent. Call it at module scope, never from inside a function: the
|
|
3
|
+
* recorder has to be in place before any chain runs, and hiding the call
|
|
4
|
+
* behind a function makes that ordering depend on who calls it first.
|
|
5
|
+
*/
|
|
6
|
+
export declare function installJsonSchemaDeclarationRecorder(): void;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.installJsonSchemaDeclarationRecorder = installJsonSchemaDeclarationRecorder;
|
|
4
|
+
const declaration_recorder_port_1 = require("../chain/declaration-recorder.port");
|
|
5
|
+
const NONE = Object.freeze([]);
|
|
6
|
+
const callsByNode = new WeakMap();
|
|
7
|
+
const recorder = {
|
|
8
|
+
record(parent, child, plugin, slot, args) {
|
|
9
|
+
callsByNode.set(child, [
|
|
10
|
+
...(callsByNode.get(parent) ?? NONE),
|
|
11
|
+
{ pluginName: plugin.name, method: plugin.method, slot, args },
|
|
12
|
+
]);
|
|
13
|
+
},
|
|
14
|
+
inherit(parent, child) {
|
|
15
|
+
const calls = callsByNode.get(parent);
|
|
16
|
+
if (calls !== undefined)
|
|
17
|
+
callsByNode.set(child, calls);
|
|
18
|
+
},
|
|
19
|
+
read(node) {
|
|
20
|
+
return callsByNode.get(node);
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Idempotent. Call it at module scope, never from inside a function: the
|
|
25
|
+
* recorder has to be in place before any chain runs, and hiding the call
|
|
26
|
+
* behind a function makes that ordering depend on who calls it first.
|
|
27
|
+
*/
|
|
28
|
+
function installJsonSchemaDeclarationRecorder() {
|
|
29
|
+
(0, declaration_recorder_port_1.installDeclarationRecorder)(recorder);
|
|
30
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { installDeclarationRecorder, } from "../chain/declaration-recorder.port.mjs";
|
|
2
|
+
const NONE = Object.freeze([]);
|
|
3
|
+
const callsByNode = new WeakMap();
|
|
4
|
+
const recorder = {
|
|
5
|
+
record(parent, child, plugin, slot, args) {
|
|
6
|
+
callsByNode.set(child, [
|
|
7
|
+
...(callsByNode.get(parent) ?? NONE),
|
|
8
|
+
{ pluginName: plugin.name, method: plugin.method, slot, args },
|
|
9
|
+
]);
|
|
10
|
+
},
|
|
11
|
+
inherit(parent, child) {
|
|
12
|
+
const calls = callsByNode.get(parent);
|
|
13
|
+
if (calls !== undefined)
|
|
14
|
+
callsByNode.set(child, calls);
|
|
15
|
+
},
|
|
16
|
+
read(node) {
|
|
17
|
+
return callsByNode.get(node);
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Idempotent. Call it at module scope, never from inside a function: the
|
|
22
|
+
* recorder has to be in place before any chain runs, and hiding the call
|
|
23
|
+
* behind a function makes that ordering depend on who calls it first.
|
|
24
|
+
*/
|
|
25
|
+
export function installJsonSchemaDeclarationRecorder() {
|
|
26
|
+
installDeclarationRecorder(recorder);
|
|
27
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ===========================================================================
|
|
3
|
+
// L10 src/standard-schema/declarations-unavailable-error.ts
|
|
4
|
+
//
|
|
5
|
+
// "Nothing was declared" and "what was declared is not known" are different
|
|
6
|
+
// answers, and only one of them can be written out.
|
|
7
|
+
//
|
|
8
|
+
// Collapsing them into an empty list produces a schema that permits everything
|
|
9
|
+
// and looks entirely confident about it. This refusal exists to stop that.
|
|
10
|
+
//
|
|
11
|
+
// It outranks the unrepresentable policy: `omit` is permission to drop a
|
|
12
|
+
// declaration that cannot be written, not permission to write without knowing
|
|
13
|
+
// what was declared.
|
|
14
|
+
// ===========================================================================
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.DeclarationsUnavailableError = void 0;
|
|
17
|
+
class DeclarationsUnavailableError extends Error {
|
|
18
|
+
constructor(fieldPath) {
|
|
19
|
+
super((fieldPath === undefined
|
|
20
|
+
? "This validator carries no declarations"
|
|
21
|
+
: `"${fieldPath}" carries no declarations`) +
|
|
22
|
+
", so no JSON Schema can be emitted from it. Two things cause " +
|
|
23
|
+
"this. A validator from fromJsonSchema() was assembled from rules " +
|
|
24
|
+
"directly and never went through the builder chain. Otherwise the " +
|
|
25
|
+
'build() ran before "@maroonedog/luq/standard-schema" was loaded — ' +
|
|
26
|
+
"import it from the module that builds the validator, or from one " +
|
|
27
|
+
"evaluated before it.");
|
|
28
|
+
this.fieldPath = fieldPath;
|
|
29
|
+
this.name = "DeclarationsUnavailableError";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
exports.DeclarationsUnavailableError = DeclarationsUnavailableError;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// ===========================================================================
|
|
2
|
+
// L10 src/standard-schema/declarations-unavailable-error.ts
|
|
3
|
+
//
|
|
4
|
+
// "Nothing was declared" and "what was declared is not known" are different
|
|
5
|
+
// answers, and only one of them can be written out.
|
|
6
|
+
//
|
|
7
|
+
// Collapsing them into an empty list produces a schema that permits everything
|
|
8
|
+
// and looks entirely confident about it. This refusal exists to stop that.
|
|
9
|
+
//
|
|
10
|
+
// It outranks the unrepresentable policy: `omit` is permission to drop a
|
|
11
|
+
// declaration that cannot be written, not permission to write without knowing
|
|
12
|
+
// what was declared.
|
|
13
|
+
// ===========================================================================
|
|
14
|
+
export class DeclarationsUnavailableError extends Error {
|
|
15
|
+
constructor(fieldPath) {
|
|
16
|
+
super((fieldPath === undefined
|
|
17
|
+
? "This validator carries no declarations"
|
|
18
|
+
: `"${fieldPath}" carries no declarations`) +
|
|
19
|
+
", so no JSON Schema can be emitted from it. Two things cause " +
|
|
20
|
+
"this. A validator from fromJsonSchema() was assembled from rules " +
|
|
21
|
+
"directly and never went through the builder chain. Otherwise the " +
|
|
22
|
+
'build() ran before "@maroonedog/luq/standard-schema" was loaded — ' +
|
|
23
|
+
"import it from the module that builds the validator, or from one " +
|
|
24
|
+
"evaluated before it.");
|
|
25
|
+
this.fieldPath = fieldPath;
|
|
26
|
+
this.name = "DeclarationsUnavailableError";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { DeclaredCall } from "../chain/declared-call.types";
|
|
2
|
+
import { type UnrepresentablePolicy } from "./unrepresentable-rule-error";
|
|
3
|
+
/** One emitted field. `required` is separate: the parent assembles it. */
|
|
4
|
+
export interface EmittedField {
|
|
5
|
+
readonly schema: Record<string, unknown>;
|
|
6
|
+
readonly isRequired: boolean;
|
|
7
|
+
}
|
|
8
|
+
/** Makes one field's schema from that field's declared calls. */
|
|
9
|
+
export declare function emitFieldSchema(fieldPath: string, calls: readonly DeclaredCall[], policy: UnrepresentablePolicy): EmittedField;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.emitFieldSchema = emitFieldSchema;
|
|
4
|
+
const plugin_keyword_map_1 = require("./plugin-keyword-map");
|
|
5
|
+
const unrepresentable_rule_error_1 = require("./unrepresentable-rule-error");
|
|
6
|
+
/** Slot to JSON Schema type name; undefined when it cannot be decided. */
|
|
7
|
+
const TYPE_OF_SLOT = Object.freeze({
|
|
8
|
+
string: "string",
|
|
9
|
+
number: "number",
|
|
10
|
+
boolean: "boolean",
|
|
11
|
+
array: "array",
|
|
12
|
+
object: "object",
|
|
13
|
+
// date is not a JSON type. Draft-07 convention writes it as a string with
|
|
14
|
+
// `format: "date-time"`, but the date slot judges Date instances, which
|
|
15
|
+
// are not JSON values. Counted as unwritable rather than conflated.
|
|
16
|
+
});
|
|
17
|
+
function typeOf(calls) {
|
|
18
|
+
const slots = new Set(calls.map((call) => call.slot));
|
|
19
|
+
slots.delete("any");
|
|
20
|
+
if (slots.size !== 1)
|
|
21
|
+
return undefined;
|
|
22
|
+
const [slot] = [...slots];
|
|
23
|
+
return slot === undefined ? undefined : TYPE_OF_SLOT[slot];
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* How `type` is spelled. Draft-07 expresses `.nullable()` as a list of
|
|
27
|
+
* types (`{"type": ["string", "null"]}`). `{"nullable": true}` is the
|
|
28
|
+
* OpenAPI 3.0 spelling and is not in the JSON Schema vocabulary.
|
|
29
|
+
*/
|
|
30
|
+
function typeKeyword(calls, fieldPath, policy) {
|
|
31
|
+
const isInteger = calls.some((call) => call.pluginName === "numberInteger");
|
|
32
|
+
const isNullable = calls.some((call) => call.pluginName === "nullable");
|
|
33
|
+
const base = isInteger ? "integer" : typeOf(calls);
|
|
34
|
+
if (base === undefined) {
|
|
35
|
+
if (policy === "throw") {
|
|
36
|
+
throw new unrepresentable_rule_error_1.UnrepresentableRuleError(fieldPath, "the chain", "its declarations do not settle on one JSON type");
|
|
37
|
+
}
|
|
38
|
+
return {};
|
|
39
|
+
}
|
|
40
|
+
return { type: isNullable ? [base, "null"] : base };
|
|
41
|
+
}
|
|
42
|
+
/** Makes one field's schema from that field's declared calls. */
|
|
43
|
+
function emitFieldSchema(fieldPath, calls, policy) {
|
|
44
|
+
if (calls.length === 0 && policy === "throw") {
|
|
45
|
+
throw new unrepresentable_rule_error_1.UnrepresentableRuleError(fieldPath, "this field", "no declaration was recorded for it (it was not built through the " +
|
|
46
|
+
"builder chain)");
|
|
47
|
+
}
|
|
48
|
+
const schema = typeKeyword(calls, fieldPath, policy);
|
|
49
|
+
for (const call of calls) {
|
|
50
|
+
const toKeywords = plugin_keyword_map_1.PLUGIN_KEYWORDS[call.pluginName];
|
|
51
|
+
if (toKeywords === undefined) {
|
|
52
|
+
if (policy === "throw") {
|
|
53
|
+
throw new unrepresentable_rule_error_1.UnrepresentableRuleError(fieldPath, call.pluginName, "no JSON Schema keyword expresses it");
|
|
54
|
+
}
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
const keywords = toKeywords(call.args);
|
|
58
|
+
if (keywords === null)
|
|
59
|
+
continue;
|
|
60
|
+
Object.assign(schema, keywords);
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
schema,
|
|
64
|
+
// `.optional()` counts wherever it appears in the chain, not only first.
|
|
65
|
+
isRequired: calls.some((call) => call.pluginName === "required") &&
|
|
66
|
+
!calls.some((call) => call.pluginName === "optional"),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { PLUGIN_KEYWORDS } from "./plugin-keyword-map.mjs";
|
|
2
|
+
import { UnrepresentableRuleError, } from "./unrepresentable-rule-error.mjs";
|
|
3
|
+
/** Slot to JSON Schema type name; undefined when it cannot be decided. */
|
|
4
|
+
const TYPE_OF_SLOT = Object.freeze({
|
|
5
|
+
string: "string",
|
|
6
|
+
number: "number",
|
|
7
|
+
boolean: "boolean",
|
|
8
|
+
array: "array",
|
|
9
|
+
object: "object",
|
|
10
|
+
// date is not a JSON type. Draft-07 convention writes it as a string with
|
|
11
|
+
// `format: "date-time"`, but the date slot judges Date instances, which
|
|
12
|
+
// are not JSON values. Counted as unwritable rather than conflated.
|
|
13
|
+
});
|
|
14
|
+
function typeOf(calls) {
|
|
15
|
+
const slots = new Set(calls.map((call) => call.slot));
|
|
16
|
+
slots.delete("any");
|
|
17
|
+
if (slots.size !== 1)
|
|
18
|
+
return undefined;
|
|
19
|
+
const [slot] = [...slots];
|
|
20
|
+
return slot === undefined ? undefined : TYPE_OF_SLOT[slot];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* How `type` is spelled. Draft-07 expresses `.nullable()` as a list of
|
|
24
|
+
* types (`{"type": ["string", "null"]}`). `{"nullable": true}` is the
|
|
25
|
+
* OpenAPI 3.0 spelling and is not in the JSON Schema vocabulary.
|
|
26
|
+
*/
|
|
27
|
+
function typeKeyword(calls, fieldPath, policy) {
|
|
28
|
+
const isInteger = calls.some((call) => call.pluginName === "numberInteger");
|
|
29
|
+
const isNullable = calls.some((call) => call.pluginName === "nullable");
|
|
30
|
+
const base = isInteger ? "integer" : typeOf(calls);
|
|
31
|
+
if (base === undefined) {
|
|
32
|
+
if (policy === "throw") {
|
|
33
|
+
throw new UnrepresentableRuleError(fieldPath, "the chain", "its declarations do not settle on one JSON type");
|
|
34
|
+
}
|
|
35
|
+
return {};
|
|
36
|
+
}
|
|
37
|
+
return { type: isNullable ? [base, "null"] : base };
|
|
38
|
+
}
|
|
39
|
+
/** Makes one field's schema from that field's declared calls. */
|
|
40
|
+
export function emitFieldSchema(fieldPath, calls, policy) {
|
|
41
|
+
if (calls.length === 0 && policy === "throw") {
|
|
42
|
+
throw new UnrepresentableRuleError(fieldPath, "this field", "no declaration was recorded for it (it was not built through the " +
|
|
43
|
+
"builder chain)");
|
|
44
|
+
}
|
|
45
|
+
const schema = typeKeyword(calls, fieldPath, policy);
|
|
46
|
+
for (const call of calls) {
|
|
47
|
+
const toKeywords = PLUGIN_KEYWORDS[call.pluginName];
|
|
48
|
+
if (toKeywords === undefined) {
|
|
49
|
+
if (policy === "throw") {
|
|
50
|
+
throw new UnrepresentableRuleError(fieldPath, call.pluginName, "no JSON Schema keyword expresses it");
|
|
51
|
+
}
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
const keywords = toKeywords(call.args);
|
|
55
|
+
if (keywords === null)
|
|
56
|
+
continue;
|
|
57
|
+
Object.assign(schema, keywords);
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
schema,
|
|
61
|
+
// `.optional()` counts wherever it appears in the chain, not only first.
|
|
62
|
+
isRequired: calls.some((call) => call.pluginName === "required") &&
|
|
63
|
+
!calls.some((call) => call.pluginName === "optional"),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -4,3 +4,8 @@ export type { IssuePathSegments } from "./split-issue-path";
|
|
|
4
4
|
export { splitIssuePath } from "./split-issue-path";
|
|
5
5
|
export type { StandardLuqSchema } from "./to-standard-schema";
|
|
6
6
|
export { toStandardSchema } from "./to-standard-schema";
|
|
7
|
+
export type { JsonSchemaOptions, StandardJsonSchemaLuqSchema, } from "./to-standard-json-schema";
|
|
8
|
+
export { toStandardJsonSchema } from "./to-standard-json-schema";
|
|
9
|
+
export { DeclarationsUnavailableError } from "./declarations-unavailable-error";
|
|
10
|
+
export { UnsupportedJsonSchemaTargetError } from "./json-schema-target";
|
|
11
|
+
export { UnrepresentableRuleError, type UnrepresentablePolicy, } from "./unrepresentable-rule-error";
|
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.toStandardSchema = exports.splitIssuePath = void 0;
|
|
3
|
+
exports.UnrepresentableRuleError = exports.UnsupportedJsonSchemaTargetError = exports.DeclarationsUnavailableError = exports.toStandardJsonSchema = exports.toStandardSchema = exports.splitIssuePath = void 0;
|
|
4
4
|
var split_issue_path_1 = require("./split-issue-path");
|
|
5
5
|
Object.defineProperty(exports, "splitIssuePath", { enumerable: true, get: function () { return split_issue_path_1.splitIssuePath; } });
|
|
6
6
|
var to_standard_schema_1 = require("./to-standard-schema");
|
|
7
7
|
Object.defineProperty(exports, "toStandardSchema", { enumerable: true, get: function () { return to_standard_schema_1.toStandardSchema; } });
|
|
8
|
+
var to_standard_json_schema_1 = require("./to-standard-json-schema");
|
|
9
|
+
Object.defineProperty(exports, "toStandardJsonSchema", { enumerable: true, get: function () { return to_standard_json_schema_1.toStandardJsonSchema; } });
|
|
10
|
+
var declarations_unavailable_error_1 = require("./declarations-unavailable-error");
|
|
11
|
+
Object.defineProperty(exports, "DeclarationsUnavailableError", { enumerable: true, get: function () { return declarations_unavailable_error_1.DeclarationsUnavailableError; } });
|
|
12
|
+
var json_schema_target_1 = require("./json-schema-target");
|
|
13
|
+
Object.defineProperty(exports, "UnsupportedJsonSchemaTargetError", { enumerable: true, get: function () { return json_schema_target_1.UnsupportedJsonSchemaTargetError; } });
|
|
14
|
+
var unrepresentable_rule_error_1 = require("./unrepresentable-rule-error");
|
|
15
|
+
Object.defineProperty(exports, "UnrepresentableRuleError", { enumerable: true, get: function () { return unrepresentable_rule_error_1.UnrepresentableRuleError; } });
|
|
@@ -1,2 +1,6 @@
|
|
|
1
1
|
export { splitIssuePath } from "./split-issue-path.mjs";
|
|
2
2
|
export { toStandardSchema } from "./to-standard-schema.mjs";
|
|
3
|
+
export { toStandardJsonSchema } from "./to-standard-json-schema.mjs";
|
|
4
|
+
export { DeclarationsUnavailableError } from "./declarations-unavailable-error.mjs";
|
|
5
|
+
export { UnsupportedJsonSchemaTargetError } from "./json-schema-target.mjs";
|
|
6
|
+
export { UnrepresentableRuleError, } from "./unrepresentable-rule-error.mjs";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare class UnsupportedJsonSchemaTargetError extends Error {
|
|
2
|
+
readonly target: string;
|
|
3
|
+
constructor(target: string);
|
|
4
|
+
}
|
|
5
|
+
/** Returns the `$schema` for a supported target; throws for anything else. */
|
|
6
|
+
export declare function resolveJsonSchemaTarget(target: string): string;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ===========================================================================
|
|
3
|
+
// L10 src/standard-schema/json-schema-target.ts
|
|
4
|
+
//
|
|
5
|
+
// Takes the spec's `target` and admits only what is actually supported.
|
|
6
|
+
//
|
|
7
|
+
// The spec says to throw on an unsupported target, and it is right to: writing
|
|
8
|
+
// draft-07 silently when 2020-12 was asked for hands the caller a document
|
|
9
|
+
// they will read under the wrong rules.
|
|
10
|
+
//
|
|
11
|
+
// Two are supported, draft-2020-12 and draft-07 — the two the spec names as
|
|
12
|
+
// widely used and strongly recommends implementing. openapi-3.0 descends from
|
|
13
|
+
// draft-04 and is a different lineage, so it is not admitted on a guess.
|
|
14
|
+
//
|
|
15
|
+
// Across the vocabulary currently emitted, the two differ only in `$schema`:
|
|
16
|
+
// every keyword written today is spelled and means the same in both. Tuples
|
|
17
|
+
// would differ, and are not emitted. This is where the branch goes if that
|
|
18
|
+
// changes.
|
|
19
|
+
// ===========================================================================
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.UnsupportedJsonSchemaTargetError = void 0;
|
|
22
|
+
exports.resolveJsonSchemaTarget = resolveJsonSchemaTarget;
|
|
23
|
+
/** The supported targets, and the `$schema` each one announces. */
|
|
24
|
+
const SCHEMA_URI = Object.freeze({
|
|
25
|
+
"draft-2020-12": "https://json-schema.org/draft/2020-12/schema",
|
|
26
|
+
"draft-07": "http://json-schema.org/draft-07/schema#",
|
|
27
|
+
});
|
|
28
|
+
class UnsupportedJsonSchemaTargetError extends Error {
|
|
29
|
+
constructor(target) {
|
|
30
|
+
super(`Luq does not emit JSON Schema for the target "${target}". ` +
|
|
31
|
+
`Supported targets: ${Object.keys(SCHEMA_URI).join(", ")}.`);
|
|
32
|
+
this.target = target;
|
|
33
|
+
this.name = "UnsupportedJsonSchemaTargetError";
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
exports.UnsupportedJsonSchemaTargetError = UnsupportedJsonSchemaTargetError;
|
|
37
|
+
/** Returns the `$schema` for a supported target; throws for anything else. */
|
|
38
|
+
function resolveJsonSchemaTarget(target) {
|
|
39
|
+
const schemaUri = SCHEMA_URI[target];
|
|
40
|
+
if (schemaUri === undefined) {
|
|
41
|
+
throw new UnsupportedJsonSchemaTargetError(target);
|
|
42
|
+
}
|
|
43
|
+
return schemaUri;
|
|
44
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// ===========================================================================
|
|
2
|
+
// L10 src/standard-schema/json-schema-target.ts
|
|
3
|
+
//
|
|
4
|
+
// Takes the spec's `target` and admits only what is actually supported.
|
|
5
|
+
//
|
|
6
|
+
// The spec says to throw on an unsupported target, and it is right to: writing
|
|
7
|
+
// draft-07 silently when 2020-12 was asked for hands the caller a document
|
|
8
|
+
// they will read under the wrong rules.
|
|
9
|
+
//
|
|
10
|
+
// Two are supported, draft-2020-12 and draft-07 — the two the spec names as
|
|
11
|
+
// widely used and strongly recommends implementing. openapi-3.0 descends from
|
|
12
|
+
// draft-04 and is a different lineage, so it is not admitted on a guess.
|
|
13
|
+
//
|
|
14
|
+
// Across the vocabulary currently emitted, the two differ only in `$schema`:
|
|
15
|
+
// every keyword written today is spelled and means the same in both. Tuples
|
|
16
|
+
// would differ, and are not emitted. This is where the branch goes if that
|
|
17
|
+
// changes.
|
|
18
|
+
// ===========================================================================
|
|
19
|
+
/** The supported targets, and the `$schema` each one announces. */
|
|
20
|
+
const SCHEMA_URI = Object.freeze({
|
|
21
|
+
"draft-2020-12": "https://json-schema.org/draft/2020-12/schema",
|
|
22
|
+
"draft-07": "http://json-schema.org/draft-07/schema#",
|
|
23
|
+
});
|
|
24
|
+
export class UnsupportedJsonSchemaTargetError extends Error {
|
|
25
|
+
constructor(target) {
|
|
26
|
+
super(`Luq does not emit JSON Schema for the target "${target}". ` +
|
|
27
|
+
`Supported targets: ${Object.keys(SCHEMA_URI).join(", ")}.`);
|
|
28
|
+
this.target = target;
|
|
29
|
+
this.name = "UnsupportedJsonSchemaTargetError";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/** Returns the `$schema` for a supported target; throws for anything else. */
|
|
33
|
+
export function resolveJsonSchemaTarget(target) {
|
|
34
|
+
const schemaUri = SCHEMA_URI[target];
|
|
35
|
+
if (schemaUri === undefined) {
|
|
36
|
+
throw new UnsupportedJsonSchemaTargetError(target);
|
|
37
|
+
}
|
|
38
|
+
return schemaUri;
|
|
39
|
+
}
|