@maroonedog/luq 2.2.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/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.d.ts +10 -0
- package/dist/chain/create-chain-node.js +19 -9
- package/dist/chain/create-chain-node.mjs +19 -9
- package/dist/chain/create-field-slots.js +9 -1
- package/dist/chain/create-field-slots.mjs +9 -1
- 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/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/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/create-structural-context.js +3 -0
- package/dist/json-schema/create-structural-context.mjs +3 -0
- 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/object.d.ts +6 -0
- package/dist/plugins/object/object.js +9 -11
- package/dist/plugins/object/object.mjs +10 -12
- 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
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import type { ValidationPlan } from "../compile/validation-plan.types";
|
|
2
2
|
import type { GlobalConfig } from "../types/global-config";
|
|
3
3
|
import type { FieldEntry } from "./field-entry.types";
|
|
4
|
-
|
|
4
|
+
import type { FieldDeclaredCalls } from "./field-declared-calls.types";
|
|
5
|
+
/** What build() makes in one pass: the plan to run, and what was declared. */
|
|
6
|
+
export interface CompiledDeclarations {
|
|
7
|
+
readonly plan: ValidationPlan;
|
|
8
|
+
readonly declaredCalls: readonly FieldDeclaredCalls[];
|
|
9
|
+
}
|
|
10
|
+
export declare function compileDeclarations(entries: readonly FieldEntry[], configOverride: GlobalConfig | undefined): CompiledDeclarations;
|
|
@@ -26,15 +26,25 @@ const global_config_store_1 = require("./global-config-store");
|
|
|
26
26
|
function compileDeclarations(entries, configOverride) {
|
|
27
27
|
const config = (0, global_config_1.resolveGlobalConfig)(configOverride, (0, global_config_store_1.getGlobalConfig)());
|
|
28
28
|
const childKeysOf = (0, declared_child_keys_1.indexDeclaredChildKeys)(entries.map((entry) => entry.path));
|
|
29
|
-
const declarations =
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
const declarations = [];
|
|
30
|
+
const declaredCalls = [];
|
|
31
|
+
for (const entry of entries) {
|
|
32
|
+
const outcome = entry.collectRules({
|
|
32
33
|
fieldPath: entry.path,
|
|
33
34
|
declaredSiblingKeys: childKeysOf(entry.path),
|
|
34
35
|
config,
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
});
|
|
37
|
+
declarations.push({
|
|
38
|
+
path: entry.path,
|
|
39
|
+
rules: outcome.rules,
|
|
40
|
+
defaultOf: entry.defaultOf ?? undefined,
|
|
41
|
+
applyDefaultToNull: entry.applyDefaultToNull,
|
|
42
|
+
normalize: entry.normalize ?? undefined,
|
|
43
|
+
});
|
|
44
|
+
declaredCalls.push({ path: entry.path, calls: outcome.calls });
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
plan: (0, compile_schema_1.compileSchema)(declarations, (0, run_branch_1.createBranchExecutor)()),
|
|
48
|
+
declaredCalls,
|
|
49
|
+
};
|
|
40
50
|
}
|
|
@@ -23,15 +23,25 @@ import { getGlobalConfig } from "./global-config-store.mjs";
|
|
|
23
23
|
export function compileDeclarations(entries, configOverride) {
|
|
24
24
|
const config = resolveGlobalConfig(configOverride, getGlobalConfig());
|
|
25
25
|
const childKeysOf = indexDeclaredChildKeys(entries.map((entry) => entry.path));
|
|
26
|
-
const declarations =
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
const declarations = [];
|
|
27
|
+
const declaredCalls = [];
|
|
28
|
+
for (const entry of entries) {
|
|
29
|
+
const outcome = entry.collectRules({
|
|
29
30
|
fieldPath: entry.path,
|
|
30
31
|
declaredSiblingKeys: childKeysOf(entry.path),
|
|
31
32
|
config,
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
});
|
|
34
|
+
declarations.push({
|
|
35
|
+
path: entry.path,
|
|
36
|
+
rules: outcome.rules,
|
|
37
|
+
defaultOf: entry.defaultOf ?? undefined,
|
|
38
|
+
applyDefaultToNull: entry.applyDefaultToNull,
|
|
39
|
+
normalize: entry.normalize ?? undefined,
|
|
40
|
+
});
|
|
41
|
+
declaredCalls.push({ path: entry.path, calls: outcome.calls });
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
plan: compileSchema(declarations, createBranchExecutor()),
|
|
45
|
+
declaredCalls,
|
|
46
|
+
};
|
|
37
47
|
}
|
|
@@ -51,9 +51,8 @@ function createBuilderSurface() {
|
|
|
51
51
|
return surface;
|
|
52
52
|
},
|
|
53
53
|
useAll(plugins) {
|
|
54
|
-
//
|
|
55
|
-
//
|
|
56
|
-
// 置き換えることはない。
|
|
54
|
+
// In enumeration order, first-wins: a name arriving twice keeps the
|
|
55
|
+
// first one, so a preset never silently replaces what is registered.
|
|
57
56
|
for (const plugin of Object.values(plugins)) {
|
|
58
57
|
registerPlugin(registration.plugins, plugin);
|
|
59
58
|
}
|
|
@@ -45,9 +45,8 @@ export function createBuilderSurface() {
|
|
|
45
45
|
return surface;
|
|
46
46
|
},
|
|
47
47
|
useAll(plugins) {
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
// 置き換えることはない。
|
|
48
|
+
// In enumeration order, first-wins: a name arriving twice keeps the
|
|
49
|
+
// first one, so a preset never silently replaces what is registered.
|
|
51
50
|
for (const plugin of Object.values(plugins)) {
|
|
52
51
|
registerPlugin(registration.plugins, plugin);
|
|
53
52
|
}
|
|
@@ -17,9 +17,20 @@ exports.createFieldBuilderSurface = createFieldBuilderSurface;
|
|
|
17
17
|
const collect_field_rules_1 = require("../chain/collect-field-rules");
|
|
18
18
|
const compile_declarations_1 = require("./compile-declarations");
|
|
19
19
|
const create_plan_validator_1 = require("./create-plan-validator");
|
|
20
|
+
const declared_calls_store_1 = require("./declared-calls-store");
|
|
20
21
|
const resolve_field_default_1 = require("./resolve-field-default");
|
|
21
22
|
/** No declaration yet: shared and frozen, so `.for<T>()` allocates nothing. */
|
|
22
23
|
const NO_ENTRIES = Object.freeze([]);
|
|
24
|
+
/**
|
|
25
|
+
* Builds the plan and attaches whatever was recorded during that same single
|
|
26
|
+
* pass. The attachment is external, so the validator gains no member.
|
|
27
|
+
*/
|
|
28
|
+
function buildValidator(entries, configOverride) {
|
|
29
|
+
const compiled = (0, compile_declarations_1.compileDeclarations)(entries, configOverride);
|
|
30
|
+
const validator = (0, create_plan_validator_1.createPlanBackedValidator)(compiled.plan);
|
|
31
|
+
(0, declared_calls_store_1.rememberDeclaredCalls)(validator, compiled.declaredCalls);
|
|
32
|
+
return validator;
|
|
33
|
+
}
|
|
23
34
|
function createFieldBuilderSurface(bag, configOverride, entries = NO_ENTRIES) {
|
|
24
35
|
const surface = {
|
|
25
36
|
v: (path, define, options) => createFieldBuilderSurface(bag, configOverride, [
|
|
@@ -27,7 +38,7 @@ function createFieldBuilderSurface(bag, configOverride, entries = NO_ENTRIES) {
|
|
|
27
38
|
toFieldEntry(bag, path, define, options),
|
|
28
39
|
]),
|
|
29
40
|
strict: () => surface,
|
|
30
|
-
build: () => (
|
|
41
|
+
build: () => buildValidator(entries, configOverride),
|
|
31
42
|
};
|
|
32
43
|
return Object.freeze(surface);
|
|
33
44
|
}
|
|
@@ -42,6 +53,7 @@ function toFieldEntry(bag, path, define, options) {
|
|
|
42
53
|
path,
|
|
43
54
|
defaultOf: policy.defaultOf,
|
|
44
55
|
applyDefaultToNull: policy.applyDefaultToNull,
|
|
56
|
+
normalize: options?.normalize ?? null,
|
|
45
57
|
collectRules: (context) => (0, collect_field_rules_1.collectFieldRules)(bag, context, define),
|
|
46
58
|
};
|
|
47
59
|
}
|
|
@@ -14,9 +14,20 @@
|
|
|
14
14
|
import { collectFieldRules } from "../chain/collect-field-rules.mjs";
|
|
15
15
|
import { compileDeclarations } from "./compile-declarations.mjs";
|
|
16
16
|
import { createPlanBackedValidator } from "./create-plan-validator.mjs";
|
|
17
|
+
import { rememberDeclaredCalls } from "./declared-calls-store.mjs";
|
|
17
18
|
import { resolveFieldDefault } from "./resolve-field-default.mjs";
|
|
18
19
|
/** No declaration yet: shared and frozen, so `.for<T>()` allocates nothing. */
|
|
19
20
|
const NO_ENTRIES = Object.freeze([]);
|
|
21
|
+
/**
|
|
22
|
+
* Builds the plan and attaches whatever was recorded during that same single
|
|
23
|
+
* pass. The attachment is external, so the validator gains no member.
|
|
24
|
+
*/
|
|
25
|
+
function buildValidator(entries, configOverride) {
|
|
26
|
+
const compiled = compileDeclarations(entries, configOverride);
|
|
27
|
+
const validator = createPlanBackedValidator(compiled.plan);
|
|
28
|
+
rememberDeclaredCalls(validator, compiled.declaredCalls);
|
|
29
|
+
return validator;
|
|
30
|
+
}
|
|
20
31
|
export function createFieldBuilderSurface(bag, configOverride, entries = NO_ENTRIES) {
|
|
21
32
|
const surface = {
|
|
22
33
|
v: (path, define, options) => createFieldBuilderSurface(bag, configOverride, [
|
|
@@ -24,7 +35,7 @@ export function createFieldBuilderSurface(bag, configOverride, entries = NO_ENTR
|
|
|
24
35
|
toFieldEntry(bag, path, define, options),
|
|
25
36
|
]),
|
|
26
37
|
strict: () => surface,
|
|
27
|
-
build: () =>
|
|
38
|
+
build: () => buildValidator(entries, configOverride),
|
|
28
39
|
};
|
|
29
40
|
return Object.freeze(surface);
|
|
30
41
|
}
|
|
@@ -39,6 +50,7 @@ function toFieldEntry(bag, path, define, options) {
|
|
|
39
50
|
path,
|
|
40
51
|
defaultOf: policy.defaultOf,
|
|
41
52
|
applyDefaultToNull: policy.applyDefaultToNull,
|
|
53
|
+
normalize: options?.normalize ?? null,
|
|
42
54
|
collectRules: (context) => collectFieldRules(bag, context, define),
|
|
43
55
|
};
|
|
44
56
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { FieldDeclaredCalls } from "./field-declared-calls.types";
|
|
2
|
+
/** Called only by build(), attaching to an already frozen validator. */
|
|
3
|
+
export declare function rememberDeclaredCalls(validator: object, calls: readonly FieldDeclaredCalls[]): void;
|
|
4
|
+
/**
|
|
5
|
+
* undefined means this validator did not come from build(). Keeping it
|
|
6
|
+
* distinct from the empty list is the point: it lets a writer tell "no
|
|
7
|
+
* constraints were declared" from "what was declared is not known".
|
|
8
|
+
*/
|
|
9
|
+
export declare function readDeclaredCalls(validator: unknown): readonly FieldDeclaredCalls[] | undefined;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.rememberDeclaredCalls = rememberDeclaredCalls;
|
|
4
|
+
exports.readDeclaredCalls = readDeclaredCalls;
|
|
5
|
+
const declaredCallsByValidator = new WeakMap();
|
|
6
|
+
/** Called only by build(), attaching to an already frozen validator. */
|
|
7
|
+
function rememberDeclaredCalls(validator, calls) {
|
|
8
|
+
declaredCallsByValidator.set(validator, calls);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* undefined means this validator did not come from build(). Keeping it
|
|
12
|
+
* distinct from the empty list is the point: it lets a writer tell "no
|
|
13
|
+
* constraints were declared" from "what was declared is not known".
|
|
14
|
+
*/
|
|
15
|
+
function readDeclaredCalls(validator) {
|
|
16
|
+
if (typeof validator !== "object" || validator === null)
|
|
17
|
+
return undefined;
|
|
18
|
+
return declaredCallsByValidator.get(validator);
|
|
19
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const declaredCallsByValidator = new WeakMap();
|
|
2
|
+
/** Called only by build(), attaching to an already frozen validator. */
|
|
3
|
+
export function rememberDeclaredCalls(validator, calls) {
|
|
4
|
+
declaredCallsByValidator.set(validator, calls);
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* undefined means this validator did not come from build(). Keeping it
|
|
8
|
+
* distinct from the empty list is the point: it lets a writer tell "no
|
|
9
|
+
* constraints were declared" from "what was declared is not known".
|
|
10
|
+
*/
|
|
11
|
+
export function readDeclaredCalls(validator) {
|
|
12
|
+
if (typeof validator !== "object" || validator === null)
|
|
13
|
+
return undefined;
|
|
14
|
+
return declaredCallsByValidator.get(validator);
|
|
15
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import type { ChainBuildContext } from "../chain/create-chain-node";
|
|
2
|
-
import type {
|
|
2
|
+
import type { FieldChainOutcome } from "../chain/collect-field-rules";
|
|
3
|
+
import type { FieldNormalizer } from "./field-options.types";
|
|
3
4
|
export interface FieldEntry {
|
|
4
5
|
readonly path: string;
|
|
5
6
|
/** null unless `.v()`'s third argument declared a default. */
|
|
6
7
|
readonly defaultOf: ((root: unknown) => unknown) | null;
|
|
7
8
|
readonly applyDefaultToNull: boolean;
|
|
8
|
-
/**
|
|
9
|
-
|
|
9
|
+
/** null unless `.v()`'s third argument declared a normalizer. */
|
|
10
|
+
readonly normalize: FieldNormalizer | null;
|
|
11
|
+
/**
|
|
12
|
+
* Runs the user's chain callback ONCE and returns its ordered rules,
|
|
13
|
+
* together with what was declared to produce them.
|
|
14
|
+
*/
|
|
15
|
+
collectRules(context: ChainBuildContext): FieldChainOutcome;
|
|
10
16
|
}
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
/** The lazy form of a default. A zero-argument function is assignable to it. */
|
|
2
2
|
export type DefaultFactory<TValue> = (root: unknown) => TValue;
|
|
3
|
+
/**
|
|
4
|
+
* Tidies a value before anything judges it. See `normalize` below.
|
|
5
|
+
*
|
|
6
|
+
* Both sides are `unknown` on purpose: the input has not been validated yet,
|
|
7
|
+
* and a form puts a string in a numeric field, so `"42"` → `42` is the main
|
|
8
|
+
* use of this layer. Typing it `(value: TValue) => TValue` would be a lie.
|
|
9
|
+
* What comes back out is judged by the rules, not by the type.
|
|
10
|
+
*/
|
|
11
|
+
export type FieldNormalizer = (value: unknown) => unknown;
|
|
3
12
|
export interface FieldOptions<TValue> {
|
|
4
13
|
/**
|
|
5
14
|
* Substituted before ANY rule looks at the value, so validate() and parse()
|
|
@@ -8,4 +17,21 @@ export interface FieldOptions<TValue> {
|
|
|
8
17
|
readonly default?: TValue | DefaultFactory<TValue>;
|
|
9
18
|
/** Defaults to true — a declared null is replaced, matching 1.x. */
|
|
10
19
|
readonly applyDefaultToNull?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Tidies the value before anything judges it. Runs straight after
|
|
22
|
+
* `default` and before presence is decided.
|
|
23
|
+
*
|
|
24
|
+
* Same promise as `default`: validate() and parse() judge the same value,
|
|
25
|
+
* and only parse() writes it back, so the two can never disagree.
|
|
26
|
+
*
|
|
27
|
+
* **Never called for undefined or null.** Otherwise
|
|
28
|
+
* `(v) => String(v).trim()` would turn a missing field into the string
|
|
29
|
+
* `"undefined"` and let it past `.required()`. Absence is `default`'s
|
|
30
|
+
* business; this one only ever sees a value that is there, which is why a
|
|
31
|
+
* normalizer needs no null check of its own.
|
|
32
|
+
*
|
|
33
|
+
* That ordering is what makes the common case work: `" "` → trim → `""`
|
|
34
|
+
* → presence reads the empty string as missing → required reports it.
|
|
35
|
+
*/
|
|
36
|
+
readonly normalize?: FieldNormalizer;
|
|
11
37
|
}
|
|
@@ -3,24 +3,21 @@ import type { ValueAtPath } from "../path/value-at-path.types";
|
|
|
3
3
|
import type { AnyChain } from "./field-chain.types";
|
|
4
4
|
import type { FieldSlots } from "./field-slots.types";
|
|
5
5
|
import type { PluginBag } from "./plugin-bag.types";
|
|
6
|
-
/**
|
|
6
|
+
/** Alias to a path from the root; only paths that exist there are writable. */
|
|
7
7
|
export type BundlePaths<TRoot> = Readonly<Record<string, FieldPath<TRoot> & string>>;
|
|
8
|
-
/**
|
|
8
|
+
/** The bundle built from the table: aliases as keys, the path's value as value. */
|
|
9
9
|
export type BundleOf<TRoot, M extends BundlePaths<TRoot>> = {
|
|
10
10
|
readonly [A in keyof M & string]: ValueAtPath<TRoot, M[A]>;
|
|
11
11
|
};
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
13
|
+
* The sub-chain for one bundle.
|
|
14
14
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* stitch ではなくなる。
|
|
15
|
+
* The subject is the bundle itself, not each alias: bringing several fields
|
|
16
|
+
* into ONE judgement is the point, and a per-alias form cannot express
|
|
17
|
+
* `total === price * quantity`.
|
|
19
18
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* `Readonly<Record<string, unknown>>` を渡していたのに対する、ここの一点だけ
|
|
24
|
-
* が違いである。
|
|
19
|
+
* Because the subject is the bundle, the slots (`b.object` and friends) open
|
|
20
|
+
* on it directly rather than descending into it. The bundle is typed, so its
|
|
21
|
+
* members complete and mistaking one fails to compile.
|
|
25
22
|
*/
|
|
26
23
|
export type BundleChain<TRoot, M extends BundlePaths<TRoot>, B extends PluginBag> = (b: FieldSlots<BundleOf<TRoot, M>, B, BundleOf<TRoot, M>>) => AnyChain;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Rule } from "../plugin-kit/compiled-rule";
|
|
2
|
+
/** Called only by whoever made the node, once, just before freezing it. */
|
|
3
|
+
export declare function rememberChainNode(node: object, rules: readonly Rule[]): void;
|
|
4
|
+
/** The one way back out of a chain: undefined for anything that is not a node. */
|
|
5
|
+
export declare function readChainNode(candidate: unknown): readonly Rule[] | undefined;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.rememberChainNode = rememberChainNode;
|
|
4
|
+
exports.readChainNode = readChainNode;
|
|
5
|
+
const rulesByNode = new WeakMap();
|
|
6
|
+
/** Called only by whoever made the node, once, just before freezing it. */
|
|
7
|
+
function rememberChainNode(node, rules) {
|
|
8
|
+
rulesByNode.set(node, rules);
|
|
9
|
+
}
|
|
10
|
+
/** The one way back out of a chain: undefined for anything that is not a node. */
|
|
11
|
+
function readChainNode(candidate) {
|
|
12
|
+
if (typeof candidate !== "object" || candidate === null)
|
|
13
|
+
return undefined;
|
|
14
|
+
return rulesByNode.get(candidate);
|
|
15
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const rulesByNode = new WeakMap();
|
|
2
|
+
/** Called only by whoever made the node, once, just before freezing it. */
|
|
3
|
+
export function rememberChainNode(node, rules) {
|
|
4
|
+
rulesByNode.set(node, rules);
|
|
5
|
+
}
|
|
6
|
+
/** The one way back out of a chain: undefined for anything that is not a node. */
|
|
7
|
+
export function readChainNode(candidate) {
|
|
8
|
+
if (typeof candidate !== "object" || candidate === null)
|
|
9
|
+
return undefined;
|
|
10
|
+
return rulesByNode.get(candidate);
|
|
11
|
+
}
|
|
@@ -2,9 +2,20 @@ import type { Rule } from "../plugin-kit/compiled-rule";
|
|
|
2
2
|
import type { PluginBag } from "./plugin-bag.types";
|
|
3
3
|
import type { AnyChain } from "./field-chain.types";
|
|
4
4
|
import type { FieldSlots } from "./field-slots.types";
|
|
5
|
-
import {
|
|
5
|
+
import type { ChainBuildContext } from "./create-chain-node";
|
|
6
|
+
import type { DeclaredCall } from "./declared-call.types";
|
|
7
|
+
/** What the single run produced: rules for the runtime, calls for a writer. */
|
|
8
|
+
export interface FieldChainOutcome {
|
|
9
|
+
readonly rules: readonly Rule[];
|
|
10
|
+
/**
|
|
11
|
+
* null means no record was kept, which is not the empty list's "nothing was
|
|
12
|
+
* declared". Collapsing the two lets a writer return a schema with no
|
|
13
|
+
* constraints and no idea that it is missing them.
|
|
14
|
+
*/
|
|
15
|
+
readonly calls: readonly DeclaredCall[] | null;
|
|
16
|
+
}
|
|
6
17
|
export declare class FieldChainResultError extends Error {
|
|
7
18
|
readonly fieldPath: string;
|
|
8
19
|
constructor(fieldPath: string);
|
|
9
20
|
}
|
|
10
|
-
export declare function collectFieldRules<TRoot, B extends PluginBag, TField>(bag: B, context: ChainBuildContext, define: (b: FieldSlots<TRoot, B, TField>) => AnyChain):
|
|
21
|
+
export declare function collectFieldRules<TRoot, B extends PluginBag, TField>(bag: B, context: ChainBuildContext, define: (b: FieldSlots<TRoot, B, TField>) => AnyChain): FieldChainOutcome;
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.FieldChainResultError = void 0;
|
|
4
4
|
exports.collectFieldRules = collectFieldRules;
|
|
5
|
-
const
|
|
5
|
+
const chain_node_store_1 = require("./chain-node-store");
|
|
6
|
+
const declaration_recorder_port_1 = require("./declaration-recorder.port");
|
|
6
7
|
const create_field_slots_1 = require("./create-field-slots");
|
|
7
8
|
class FieldChainResultError extends Error {
|
|
8
9
|
constructor(fieldPath) {
|
|
@@ -15,8 +16,14 @@ class FieldChainResultError extends Error {
|
|
|
15
16
|
exports.FieldChainResultError = FieldChainResultError;
|
|
16
17
|
function collectFieldRules(bag, context, define) {
|
|
17
18
|
const chain = define((0, create_field_slots_1.createFieldSlots)(bag, context));
|
|
18
|
-
const rules = (0,
|
|
19
|
+
const rules = (0, chain_node_store_1.readChainNode)(chain);
|
|
19
20
|
if (rules === undefined)
|
|
20
21
|
throw new FieldChainResultError(context.fieldPath);
|
|
21
|
-
|
|
22
|
+
const recorder = declaration_recorder_port_1.declarationRecorder;
|
|
23
|
+
return Object.freeze({
|
|
24
|
+
rules: Object.freeze(rules.slice()),
|
|
25
|
+
calls: recorder === null
|
|
26
|
+
? null
|
|
27
|
+
: Object.freeze((recorder.read(chain) ?? []).slice()),
|
|
28
|
+
});
|
|
22
29
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readChainNode } from "./chain-node-store.mjs";
|
|
2
|
+
import { declarationRecorder } from "./declaration-recorder.port.mjs";
|
|
2
3
|
import { createFieldSlots } from "./create-field-slots.mjs";
|
|
3
4
|
export class FieldChainResultError extends Error {
|
|
4
5
|
constructor(fieldPath) {
|
|
@@ -10,8 +11,14 @@ export class FieldChainResultError extends Error {
|
|
|
10
11
|
}
|
|
11
12
|
export function collectFieldRules(bag, context, define) {
|
|
12
13
|
const chain = define(createFieldSlots(bag, context));
|
|
13
|
-
const rules =
|
|
14
|
+
const rules = readChainNode(chain);
|
|
14
15
|
if (rules === undefined)
|
|
15
16
|
throw new FieldChainResultError(context.fieldPath);
|
|
16
|
-
|
|
17
|
+
const recorder = declarationRecorder;
|
|
18
|
+
return Object.freeze({
|
|
19
|
+
rules: Object.freeze(rules.slice()),
|
|
20
|
+
calls: recorder === null
|
|
21
|
+
? null
|
|
22
|
+
: Object.freeze((recorder.read(chain) ?? []).slice()),
|
|
23
|
+
});
|
|
17
24
|
}
|
|
@@ -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
|
|
@@ -20,12 +20,18 @@ exports.readChainRules = readChainRules;
|
|
|
20
20
|
// The rule list is held in a WeakMap rather than on the node, so the node
|
|
21
21
|
// carries exactly the members its type declares and reading the rules back
|
|
22
22
|
// needs no assertion and no runtime shape check.
|
|
23
|
+
//
|
|
24
|
+
// A node does NOT carry what was called with what. This file builds no record
|
|
25
|
+
// of the call; it only notifies whoever asked to be told, and with nobody
|
|
26
|
+
// asking that is one null check per chain step. See
|
|
27
|
+
// declaration-recorder.port.ts.
|
|
23
28
|
// ===========================================================================
|
|
24
29
|
const types_1 = require("../types");
|
|
25
30
|
const create_rule_1 = require("../plugin-kit/create-rule");
|
|
26
31
|
const refine_methods_types_1 = require("./refine-methods.types");
|
|
27
32
|
const attach_slot_methods_1 = require("./attach-slot-methods");
|
|
28
|
-
const
|
|
33
|
+
const declaration_recorder_port_1 = require("./declaration-recorder.port");
|
|
34
|
+
const chain_node_store_1 = require("./chain-node-store");
|
|
29
35
|
const EMPTY_RECORD = Object.freeze({});
|
|
30
36
|
exports.EMPTY_RULES = Object.freeze([]);
|
|
31
37
|
function isIssueSeverity(value) {
|
|
@@ -70,7 +76,7 @@ function nullIsAValue(severity) {
|
|
|
70
76
|
buildMessageContext: () => ({}),
|
|
71
77
|
});
|
|
72
78
|
}
|
|
73
|
-
function createSlotMethod(wiring, slot, rules, plugin) {
|
|
79
|
+
function createSlotMethod(wiring, parent, slot, rules, plugin) {
|
|
74
80
|
const arity = Math.max(plugin.build.length - 1, 0);
|
|
75
81
|
return (...args) => {
|
|
76
82
|
const rawOptions = args.length > arity ? args[arity] : undefined;
|
|
@@ -79,25 +85,29 @@ function createSlotMethod(wiring, slot, rules, plugin) {
|
|
|
79
85
|
const added = plugin.judgesNull === true
|
|
80
86
|
? [nullIsAValue(wiring.context.config.defaultSeverity), rule]
|
|
81
87
|
: [rule];
|
|
82
|
-
|
|
88
|
+
const child = createChainNode(wiring, slot, [...rules, ...added]);
|
|
89
|
+
declaration_recorder_port_1.declarationRecorder?.record(parent, child, plugin, slot, resolved);
|
|
90
|
+
return child;
|
|
83
91
|
};
|
|
84
92
|
}
|
|
85
93
|
function attachRefineMethods(target, wiring, rules) {
|
|
86
94
|
for (const [methodName, slot] of Object.entries(refine_methods_types_1.REFINE_METHOD_SLOTS)) {
|
|
87
|
-
target[methodName] = () =>
|
|
95
|
+
target[methodName] = () => {
|
|
96
|
+
const child = createChainNode(wiring, slot, rules);
|
|
97
|
+
declaration_recorder_port_1.declarationRecorder?.inherit(target, child);
|
|
98
|
+
return child;
|
|
99
|
+
};
|
|
88
100
|
}
|
|
89
101
|
}
|
|
90
102
|
/** The runtime value behind FieldChain. Erased to its type by its callers. */
|
|
91
103
|
function createChainNode(wiring, slot, rules) {
|
|
92
104
|
const node = {};
|
|
93
105
|
attachRefineMethods(node, wiring, rules);
|
|
94
|
-
(0, attach_slot_methods_1.attachSlotMethods)(node, wiring.bag, slot, (plugin) => createSlotMethod(wiring, slot, rules, plugin));
|
|
95
|
-
|
|
106
|
+
(0, attach_slot_methods_1.attachSlotMethods)(node, wiring.bag, slot, (plugin) => createSlotMethod(wiring, node, slot, rules, plugin));
|
|
107
|
+
(0, chain_node_store_1.rememberChainNode)(node, rules);
|
|
96
108
|
return Object.freeze(node);
|
|
97
109
|
}
|
|
98
110
|
/** The one way back out of a chain: undefined when the value is not a node. */
|
|
99
111
|
function readChainRules(candidate) {
|
|
100
|
-
|
|
101
|
-
return undefined;
|
|
102
|
-
return chainRulesByNode.get(candidate);
|
|
112
|
+
return (0, chain_node_store_1.readChainNode)(candidate);
|
|
103
113
|
}
|
|
@@ -15,12 +15,18 @@
|
|
|
15
15
|
// The rule list is held in a WeakMap rather than on the node, so the node
|
|
16
16
|
// carries exactly the members its type declares and reading the rules back
|
|
17
17
|
// needs no assertion and no runtime shape check.
|
|
18
|
+
//
|
|
19
|
+
// A node does NOT carry what was called with what. This file builds no record
|
|
20
|
+
// of the call; it only notifies whoever asked to be told, and with nobody
|
|
21
|
+
// asking that is one null check per chain step. See
|
|
22
|
+
// declaration-recorder.port.ts.
|
|
18
23
|
// ===========================================================================
|
|
19
24
|
import { isPlainObject, isString } from "../types/index.mjs";
|
|
20
25
|
import { presence } from "../plugin-kit/create-rule.mjs";
|
|
21
26
|
import { REFINE_METHOD_SLOTS } from "./refine-methods.types.mjs";
|
|
22
27
|
import { attachSlotMethods } from "./attach-slot-methods.mjs";
|
|
23
|
-
|
|
28
|
+
import { declarationRecorder } from "./declaration-recorder.port.mjs";
|
|
29
|
+
import { readChainNode, rememberChainNode } from "./chain-node-store.mjs";
|
|
24
30
|
const EMPTY_RECORD = Object.freeze({});
|
|
25
31
|
export const EMPTY_RULES = Object.freeze([]);
|
|
26
32
|
function isIssueSeverity(value) {
|
|
@@ -65,7 +71,7 @@ function nullIsAValue(severity) {
|
|
|
65
71
|
buildMessageContext: () => ({}),
|
|
66
72
|
});
|
|
67
73
|
}
|
|
68
|
-
function createSlotMethod(wiring, slot, rules, plugin) {
|
|
74
|
+
function createSlotMethod(wiring, parent, slot, rules, plugin) {
|
|
69
75
|
const arity = Math.max(plugin.build.length - 1, 0);
|
|
70
76
|
return (...args) => {
|
|
71
77
|
const rawOptions = args.length > arity ? args[arity] : undefined;
|
|
@@ -74,25 +80,29 @@ function createSlotMethod(wiring, slot, rules, plugin) {
|
|
|
74
80
|
const added = plugin.judgesNull === true
|
|
75
81
|
? [nullIsAValue(wiring.context.config.defaultSeverity), rule]
|
|
76
82
|
: [rule];
|
|
77
|
-
|
|
83
|
+
const child = createChainNode(wiring, slot, [...rules, ...added]);
|
|
84
|
+
declarationRecorder?.record(parent, child, plugin, slot, resolved);
|
|
85
|
+
return child;
|
|
78
86
|
};
|
|
79
87
|
}
|
|
80
88
|
function attachRefineMethods(target, wiring, rules) {
|
|
81
89
|
for (const [methodName, slot] of Object.entries(REFINE_METHOD_SLOTS)) {
|
|
82
|
-
target[methodName] = () =>
|
|
90
|
+
target[methodName] = () => {
|
|
91
|
+
const child = createChainNode(wiring, slot, rules);
|
|
92
|
+
declarationRecorder?.inherit(target, child);
|
|
93
|
+
return child;
|
|
94
|
+
};
|
|
83
95
|
}
|
|
84
96
|
}
|
|
85
97
|
/** The runtime value behind FieldChain. Erased to its type by its callers. */
|
|
86
98
|
export function createChainNode(wiring, slot, rules) {
|
|
87
99
|
const node = {};
|
|
88
100
|
attachRefineMethods(node, wiring, rules);
|
|
89
|
-
attachSlotMethods(node, wiring.bag, slot, (plugin) => createSlotMethod(wiring, slot, rules, plugin));
|
|
90
|
-
|
|
101
|
+
attachSlotMethods(node, wiring.bag, slot, (plugin) => createSlotMethod(wiring, node, slot, rules, plugin));
|
|
102
|
+
rememberChainNode(node, rules);
|
|
91
103
|
return Object.freeze(node);
|
|
92
104
|
}
|
|
93
105
|
/** The one way back out of a chain: undefined when the value is not a node. */
|
|
94
106
|
export function readChainRules(candidate) {
|
|
95
|
-
|
|
96
|
-
return undefined;
|
|
97
|
-
return chainRulesByNode.get(candidate);
|
|
107
|
+
return readChainNode(candidate);
|
|
98
108
|
}
|
|
@@ -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;
|