@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
|
@@ -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
|
}
|
|
@@ -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
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { TypeName } from "../types";
|
|
2
|
+
import type { AnyPlugin } from "../plugin-kit/plugin-definition";
|
|
3
|
+
import type { DeclaredCall } from "./declared-call.types";
|
|
4
|
+
/**
|
|
5
|
+
* Only the parent-to-child relation between nodes crosses this line; how the
|
|
6
|
+
* record is held is the implementation's business. Nothing passed here is
|
|
7
|
+
* built for the occasion — the plugin, the slot and the arguments are all
|
|
8
|
+
* already in hand for building the rule.
|
|
9
|
+
*/
|
|
10
|
+
export interface DeclarationRecorder {
|
|
11
|
+
/**
|
|
12
|
+
* `child`'s record is `parent`'s with one call appended. Called once, by
|
|
13
|
+
* whoever made the node, immediately after freezing it.
|
|
14
|
+
*/
|
|
15
|
+
record(parent: object, child: object, plugin: AnyPlugin, slot: TypeName, args: readonly unknown[]): void;
|
|
16
|
+
/** A refine step adds no call. It carries the record across unchanged. */
|
|
17
|
+
inherit(parent: object, child: object): void;
|
|
18
|
+
/** The calls declared up to that node, or undefined when there are none. */
|
|
19
|
+
read(node: object): readonly DeclaredCall[] | undefined;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Null until someone installs. Exposed as the binding rather than behind a
|
|
23
|
+
* getter because that measured smaller: with no installer reachable, what
|
|
24
|
+
* survives minification is one `let` and an optional chain.
|
|
25
|
+
*/
|
|
26
|
+
export declare let declarationRecorder: DeclarationRecorder | null;
|
|
27
|
+
/**
|
|
28
|
+
* Called once, when the implementation is loaded. A second call overwrites,
|
|
29
|
+
* which is harmless while there is one implementation.
|
|
30
|
+
*/
|
|
31
|
+
export declare function installDeclarationRecorder(recorder: DeclarationRecorder | null): void;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.declarationRecorder = void 0;
|
|
4
|
+
exports.installDeclarationRecorder = installDeclarationRecorder;
|
|
5
|
+
/**
|
|
6
|
+
* Null until someone installs. Exposed as the binding rather than behind a
|
|
7
|
+
* getter because that measured smaller: with no installer reachable, what
|
|
8
|
+
* survives minification is one `let` and an optional chain.
|
|
9
|
+
*/
|
|
10
|
+
exports.declarationRecorder = null;
|
|
11
|
+
/**
|
|
12
|
+
* Called once, when the implementation is loaded. A second call overwrites,
|
|
13
|
+
* which is harmless while there is one implementation.
|
|
14
|
+
*/
|
|
15
|
+
function installDeclarationRecorder(recorder) {
|
|
16
|
+
exports.declarationRecorder = recorder;
|
|
17
|
+
}
|