@maroonedog/luq 2.3.0 → 2.3.1
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/dist/chain/create-chain-node.d.ts +10 -0
- package/dist/chain/create-field-slots.js +9 -1
- package/dist/chain/create-field-slots.mjs +9 -1
- package/dist/chain/slot-type-guard.d.ts +10 -0
- package/dist/chain/slot-type-guard.js +37 -0
- package/dist/chain/slot-type-guard.mjs +34 -0
- package/dist/json-schema/create-structural-context.js +3 -0
- package/dist/json-schema/create-structural-context.mjs +3 -0
- package/dist/plugins/object/object.d.ts +6 -0
- package/dist/plugins/object/object.js +9 -11
- package/dist/plugins/object/object.mjs +10 -12
- package/package.json +1 -1
|
@@ -9,6 +9,16 @@ export interface ChainBuildContext {
|
|
|
9
9
|
readonly fieldPath: string;
|
|
10
10
|
readonly declaredSiblingKeys: readonly string[];
|
|
11
11
|
readonly config: ResolvedGlobalConfig;
|
|
12
|
+
/**
|
|
13
|
+
* True when something other than the slot already decides the runtime type.
|
|
14
|
+
*
|
|
15
|
+
* Set by the JSON Schema converter and nowhere else: there the DOCUMENT owns
|
|
16
|
+
* the type, its `type` keyword reports under the code `type`, and its
|
|
17
|
+
* vocabulary is wider than a slot's — `integer`, a list of types, and `null`
|
|
18
|
+
* as a type in its own right. A slot guard on that path would report a
|
|
19
|
+
* second issue for one bad value, under a code no schema asked for.
|
|
20
|
+
*/
|
|
21
|
+
readonly typeDecidedElsewhere?: boolean;
|
|
12
22
|
}
|
|
13
23
|
/**
|
|
14
24
|
* The parts a node cannot make for itself. `resolveArguments` is injected so
|
|
@@ -5,6 +5,7 @@ exports.createFieldSlots = createFieldSlots;
|
|
|
5
5
|
const type_erasure_1 = require("../core/type-erasure");
|
|
6
6
|
const create_chain_node_1 = require("./create-chain-node");
|
|
7
7
|
const collect_branch_rules_1 = require("./collect-branch-rules");
|
|
8
|
+
const slot_type_guard_1 = require("./slot-type-guard");
|
|
8
9
|
const SLOT_NAMES = Object.freeze([
|
|
9
10
|
"string",
|
|
10
11
|
"number",
|
|
@@ -25,9 +26,16 @@ function buildSlotSurface(bag, context) {
|
|
|
25
26
|
resolveArguments: (plugin, declared) => (0, collect_branch_rules_1.resolvePluginArguments)(bag, context, buildSlotSurface, plugin, declared),
|
|
26
27
|
};
|
|
27
28
|
for (const slot of SLOT_NAMES) {
|
|
29
|
+
// The slot's own type check leads the chain, so it runs before any value
|
|
30
|
+
// rule. Those rules pass a wrong-typed value through on purpose; this is
|
|
31
|
+
// what reports it. See slot-type-guard.ts.
|
|
32
|
+
const guard = context.typeDecidedElsewhere === true
|
|
33
|
+
? null
|
|
34
|
+
: (0, slot_type_guard_1.slotTypeGuard)(slot, context.config.defaultSeverity);
|
|
35
|
+
const seed = guard === null ? create_chain_node_1.EMPTY_RULES : Object.freeze([guard]);
|
|
28
36
|
Object.defineProperty(surface, slot, {
|
|
29
37
|
enumerable: true,
|
|
30
|
-
get: () => (0, create_chain_node_1.createChainNode)(wiring, slot,
|
|
38
|
+
get: () => (0, create_chain_node_1.createChainNode)(wiring, slot, seed),
|
|
31
39
|
});
|
|
32
40
|
}
|
|
33
41
|
return surface;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { eraseChainSurface } from "../core/type-erasure.mjs";
|
|
2
2
|
import { createChainNode, EMPTY_RULES, } from "./create-chain-node.mjs";
|
|
3
3
|
import { resolvePluginArguments } from "./collect-branch-rules.mjs";
|
|
4
|
+
import { slotTypeGuard } from "./slot-type-guard.mjs";
|
|
4
5
|
const SLOT_NAMES = Object.freeze([
|
|
5
6
|
"string",
|
|
6
7
|
"number",
|
|
@@ -21,9 +22,16 @@ export function buildSlotSurface(bag, context) {
|
|
|
21
22
|
resolveArguments: (plugin, declared) => resolvePluginArguments(bag, context, buildSlotSurface, plugin, declared),
|
|
22
23
|
};
|
|
23
24
|
for (const slot of SLOT_NAMES) {
|
|
25
|
+
// The slot's own type check leads the chain, so it runs before any value
|
|
26
|
+
// rule. Those rules pass a wrong-typed value through on purpose; this is
|
|
27
|
+
// what reports it. See slot-type-guard.ts.
|
|
28
|
+
const guard = context.typeDecidedElsewhere === true
|
|
29
|
+
? null
|
|
30
|
+
: slotTypeGuard(slot, context.config.defaultSeverity);
|
|
31
|
+
const seed = guard === null ? EMPTY_RULES : Object.freeze([guard]);
|
|
24
32
|
Object.defineProperty(surface, slot, {
|
|
25
33
|
enumerable: true,
|
|
26
|
-
get: () => createChainNode(wiring, slot,
|
|
34
|
+
get: () => createChainNode(wiring, slot, seed),
|
|
27
35
|
});
|
|
28
36
|
}
|
|
29
37
|
return surface;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { IssueSeverity, TypeName } from "../types";
|
|
2
|
+
import type { Rule } from "../plugin-kit/compiled-rule";
|
|
3
|
+
/**
|
|
4
|
+
* The rule `b.<slot>` starts its chain with, or null for a slot that claims
|
|
5
|
+
* nothing about the runtime type.
|
|
6
|
+
*
|
|
7
|
+
* The code is `<slot>Type` — `stringType`, `numberType` — which reads as the
|
|
8
|
+
* plugin name it would have had, matching every other code in the library.
|
|
9
|
+
*/
|
|
10
|
+
export declare function slotTypeGuard(slot: TypeName, severity: IssueSeverity): Rule | null;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.slotTypeGuard = slotTypeGuard;
|
|
4
|
+
const types_1 = require("../types");
|
|
5
|
+
const create_rule_1 = require("../plugin-kit/create-rule");
|
|
6
|
+
const SLOT_TYPES = Object.freeze({
|
|
7
|
+
string: { accepts: types_1.isString, noun: "a string" },
|
|
8
|
+
number: { accepts: types_1.isNumber, noun: "a number" },
|
|
9
|
+
boolean: {
|
|
10
|
+
accepts: (value) => typeof value === "boolean",
|
|
11
|
+
noun: "a boolean",
|
|
12
|
+
},
|
|
13
|
+
date: { accepts: (value) => value instanceof Date, noun: "a Date" },
|
|
14
|
+
array: { accepts: types_1.isArray, noun: "an array" },
|
|
15
|
+
object: { accepts: types_1.isPlainObject, noun: "an object" },
|
|
16
|
+
});
|
|
17
|
+
/**
|
|
18
|
+
* The rule `b.<slot>` starts its chain with, or null for a slot that claims
|
|
19
|
+
* nothing about the runtime type.
|
|
20
|
+
*
|
|
21
|
+
* The code is `<slot>Type` — `stringType`, `numberType` — which reads as the
|
|
22
|
+
* plugin name it would have had, matching every other code in the library.
|
|
23
|
+
*/
|
|
24
|
+
function slotTypeGuard(slot, severity) {
|
|
25
|
+
const slotType = SLOT_TYPES[slot];
|
|
26
|
+
if (slotType === undefined)
|
|
27
|
+
return null;
|
|
28
|
+
return (0, create_rule_1.check)({
|
|
29
|
+
code: `${slot}Type`,
|
|
30
|
+
severity,
|
|
31
|
+
run: (value) => value === undefined || value === null || slotType.accepts(value)
|
|
32
|
+
? types_1.PASS
|
|
33
|
+
: (0, types_1.fail)({ expected: slotType.noun, actual: value }),
|
|
34
|
+
describe: () => `Value must be ${slotType.noun}`,
|
|
35
|
+
buildMessageContext: () => ({}),
|
|
36
|
+
});
|
|
37
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { PASS, fail, isArray, isNumber, isPlainObject, isString, } from "../types/index.mjs";
|
|
2
|
+
import { check } from "../plugin-kit/create-rule.mjs";
|
|
3
|
+
const SLOT_TYPES = Object.freeze({
|
|
4
|
+
string: { accepts: isString, noun: "a string" },
|
|
5
|
+
number: { accepts: isNumber, noun: "a number" },
|
|
6
|
+
boolean: {
|
|
7
|
+
accepts: (value) => typeof value === "boolean",
|
|
8
|
+
noun: "a boolean",
|
|
9
|
+
},
|
|
10
|
+
date: { accepts: (value) => value instanceof Date, noun: "a Date" },
|
|
11
|
+
array: { accepts: isArray, noun: "an array" },
|
|
12
|
+
object: { accepts: isPlainObject, noun: "an object" },
|
|
13
|
+
});
|
|
14
|
+
/**
|
|
15
|
+
* The rule `b.<slot>` starts its chain with, or null for a slot that claims
|
|
16
|
+
* nothing about the runtime type.
|
|
17
|
+
*
|
|
18
|
+
* The code is `<slot>Type` — `stringType`, `numberType` — which reads as the
|
|
19
|
+
* plugin name it would have had, matching every other code in the library.
|
|
20
|
+
*/
|
|
21
|
+
export function slotTypeGuard(slot, severity) {
|
|
22
|
+
const slotType = SLOT_TYPES[slot];
|
|
23
|
+
if (slotType === undefined)
|
|
24
|
+
return null;
|
|
25
|
+
return check({
|
|
26
|
+
code: `${slot}Type`,
|
|
27
|
+
severity,
|
|
28
|
+
run: (value) => value === undefined || value === null || slotType.accepts(value)
|
|
29
|
+
? PASS
|
|
30
|
+
: fail({ expected: slotType.noun, actual: value }),
|
|
31
|
+
describe: () => `Value must be ${slotType.noun}`,
|
|
32
|
+
buildMessageContext: () => ({}),
|
|
33
|
+
});
|
|
34
|
+
}
|
|
@@ -58,6 +58,9 @@ function createStructuralContext(seed, node, visitedRefs, scope = seed.scope) {
|
|
|
58
58
|
fieldPath: seed.chain.fieldPath,
|
|
59
59
|
declaredSiblingKeys: propertyKeysOf(node),
|
|
60
60
|
config: seed.chain.config,
|
|
61
|
+
// The document owns the type here: `type` is a keyword with its own code
|
|
62
|
+
// and a wider vocabulary than a slot has. The slot must not add a second.
|
|
63
|
+
typeDecidedElsewhere: true,
|
|
61
64
|
};
|
|
62
65
|
/**
|
|
63
66
|
* One descent: the node this schema really is, and the context inside it.
|
|
@@ -54,6 +54,9 @@ export function createStructuralContext(seed, node, visitedRefs, scope = seed.sc
|
|
|
54
54
|
fieldPath: seed.chain.fieldPath,
|
|
55
55
|
declaredSiblingKeys: propertyKeysOf(node),
|
|
56
56
|
config: seed.chain.config,
|
|
57
|
+
// The document owns the type here: `type` is a keyword with its own code
|
|
58
|
+
// and a wider vocabulary than a slot has. The slot must not add a second.
|
|
59
|
+
typeDecidedElsewhere: true,
|
|
57
60
|
};
|
|
58
61
|
/**
|
|
59
62
|
* One descent: the node this schema really is, and the context inside it.
|
|
@@ -2,6 +2,12 @@ import type { Unchanged } from "../../plugin-kit/marker.types";
|
|
|
2
2
|
export interface ObjectTypeContext {
|
|
3
3
|
readonly actual: string;
|
|
4
4
|
}
|
|
5
|
+
/**
|
|
6
|
+
* @deprecated Entering `b.object` already checks this, under the code
|
|
7
|
+
* `objectType`. Drop the `.use(objectPlugin)` and the `.object()` call: the
|
|
8
|
+
* field goes on rejecting arrays, `null` and primitives, and only the issue's
|
|
9
|
+
* code and message change.
|
|
10
|
+
*/
|
|
5
11
|
export declare const objectPlugin: import("../../plugin-kit/plugin-definition").PluginDefinition<"object", "object", readonly ["object"], {
|
|
6
12
|
args: readonly [];
|
|
7
13
|
out: Unchanged;
|
|
@@ -4,14 +4,12 @@ exports.objectPlugin = void 0;
|
|
|
4
4
|
const types_1 = require("../../types");
|
|
5
5
|
const create_rule_1 = require("../../plugin-kit/create-rule");
|
|
6
6
|
const plugin_definition_1 = require("../../plugin-kit/plugin-definition");
|
|
7
|
-
/**
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
return typeof value;
|
|
14
|
-
}
|
|
7
|
+
/**
|
|
8
|
+
* @deprecated Entering `b.object` already checks this, under the code
|
|
9
|
+
* `objectType`. Drop the `.use(objectPlugin)` and the `.object()` call: the
|
|
10
|
+
* field goes on rejecting arrays, `null` and primitives, and only the issue's
|
|
11
|
+
* code and message change.
|
|
12
|
+
*/
|
|
15
13
|
exports.objectPlugin = (0, plugin_definition_1.definePlugin)()({
|
|
16
14
|
name: "object",
|
|
17
15
|
method: "object",
|
|
@@ -20,9 +18,9 @@ exports.objectPlugin = (0, plugin_definition_1.definePlugin)()({
|
|
|
20
18
|
code: ctx.code,
|
|
21
19
|
messageFactory: ctx.messageFactory,
|
|
22
20
|
severity: ctx.severity,
|
|
23
|
-
run: (
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
run: () => types_1.PASS,
|
|
22
|
+
// Unreachable while run() never fails, and kept so the shape of the rule
|
|
23
|
+
// stays a rule rather than becoming a special case for the engine.
|
|
26
24
|
describe: (detail) => `Value must be an object, but got ${String(detail.actual)}`,
|
|
27
25
|
buildMessageContext: (detail) => ({ actual: String(detail.actual) }),
|
|
28
26
|
}),
|
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
import { PASS
|
|
1
|
+
import { PASS } from "../../types/index.mjs";
|
|
2
2
|
import { check } from "../../plugin-kit/create-rule.mjs";
|
|
3
3
|
import { definePlugin } from "../../plugin-kit/plugin-definition.mjs";
|
|
4
|
-
/**
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
return typeof value;
|
|
11
|
-
}
|
|
4
|
+
/**
|
|
5
|
+
* @deprecated Entering `b.object` already checks this, under the code
|
|
6
|
+
* `objectType`. Drop the `.use(objectPlugin)` and the `.object()` call: the
|
|
7
|
+
* field goes on rejecting arrays, `null` and primitives, and only the issue's
|
|
8
|
+
* code and message change.
|
|
9
|
+
*/
|
|
12
10
|
export const objectPlugin = /*#__PURE__*/ definePlugin()({
|
|
13
11
|
name: "object",
|
|
14
12
|
method: "object",
|
|
@@ -17,9 +15,9 @@ export const objectPlugin = /*#__PURE__*/ definePlugin()({
|
|
|
17
15
|
code: ctx.code,
|
|
18
16
|
messageFactory: ctx.messageFactory,
|
|
19
17
|
severity: ctx.severity,
|
|
20
|
-
run: (
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
run: () => PASS,
|
|
19
|
+
// Unreachable while run() never fails, and kept so the shape of the rule
|
|
20
|
+
// stays a rule rather than becoming a special case for the engine.
|
|
23
21
|
describe: (detail) => `Value must be an object, but got ${String(detail.actual)}`,
|
|
24
22
|
buildMessageContext: (detail) => ({ actual: String(detail.actual) }),
|
|
25
23
|
}),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maroonedog/luq",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.1",
|
|
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",
|