@maroonedog/luq 2.1.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 +92 -430
- package/dist/builder/builder-surface.types.d.ts +1 -0
- 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 +8 -0
- package/dist/builder/create-builder.mjs +8 -0
- 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-builder.types.d.ts +13 -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 +23 -0
- package/dist/chain/bundle-paths.types.js +2 -0
- package/dist/chain/bundle-paths.types.mjs +1 -0
- package/dist/chain/chain-method.types.d.ts +8 -3
- 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/chain/resolve-args.types.d.ts +4 -2
- package/dist/compile/compile-array-node.d.ts +0 -7
- package/dist/compile/compile-array-node.js +5 -0
- package/dist/compile/compile-array-node.mjs +5 -0
- package/dist/compile/compile-field.d.ts +1 -0
- package/dist/compile/compile-field.js +13 -2
- package/dist/compile/compile-field.mjs +13 -2
- 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 +27 -7
- package/dist/compile/split-rules-by-kind.mjs +27 -7
- package/dist/compile/validation-plan.types.d.ts +36 -0
- 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/flatten-schema.js +1 -1
- package/dist/json-schema/flatten-schema.mjs +1 -1
- 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/plugin-kit/marker.types.d.ts +18 -0
- package/dist/plugins/index.generated.d.ts +1 -0
- package/dist/plugins/index.generated.js +6 -4
- package/dist/plugins/index.generated.mjs +3 -2
- package/dist/plugins/manifest.generated.js +3 -2
- package/dist/plugins/manifest.generated.mjs +3 -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 +24 -8
- package/dist/plugins/stitch-with/index.d.ts +2 -0
- package/dist/plugins/stitch-with/index.js +5 -0
- package/dist/plugins/stitch-with/index.mjs +1 -0
- package/dist/plugins/stitch-with/stitch-with.d.ts +12 -0
- package/dist/plugins/stitch-with/stitch-with.js +92 -0
- package/dist/plugins/stitch-with/stitch-with.mjs +89 -0
- package/dist/plugins/stitchWith.d.ts +1 -0
- package/dist/plugins/stitchWith.js +2 -0
- package/dist/plugins/stitchWith.mjs +1 -0
- package/dist/plugins/string-min/string-min.js +19 -3
- package/dist/plugins/string-min/string-min.mjs +20 -4
- package/dist/presets/index.d.ts +1 -0
- package/dist/presets/index.js +9 -0
- package/dist/presets/index.mjs +1 -0
- package/dist/presets/presets.d.ts +164 -0
- package/dist/presets/presets.js +79 -0
- package/dist/presets/presets.mjs +76 -0
- package/dist/runtime/create-field-validator.js +22 -8
- package/dist/runtime/create-field-validator.mjs +22 -8
- package/dist/runtime/create-validator.js +38 -8
- package/dist/runtime/create-validator.mjs +38 -8
- package/dist/runtime/field-rule-context.d.ts +32 -0
- package/dist/runtime/field-rule-context.js +48 -0
- package/dist/runtime/field-rule-context.mjs +44 -0
- package/dist/runtime/index-stack.d.ts +29 -4
- package/dist/runtime/index-stack.js +76 -15
- package/dist/runtime/index-stack.mjs +76 -15
- package/dist/runtime/output-writer.js +5 -1
- package/dist/runtime/output-writer.mjs +5 -1
- package/dist/runtime/run-array-node.js +22 -10
- package/dist/runtime/run-array-node.mjs +22 -10
- package/dist/runtime/run-field.js +47 -18
- package/dist/runtime/run-field.mjs +47 -18
- package/dist/runtime/run-plan.js +5 -1
- package/dist/runtime/run-plan.mjs +5 -1
- 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 +19 -7
- package/dist/types/index.js +7 -7
- package/dist/types/index.mjs +7 -7
- package/package.json +35 -19
|
@@ -50,6 +50,14 @@ function createBuilderSurface() {
|
|
|
50
50
|
registerPlugin(registration.plugins, plugin);
|
|
51
51
|
return surface;
|
|
52
52
|
},
|
|
53
|
+
useAll(plugins) {
|
|
54
|
+
// In enumeration order, first-wins: a name arriving twice keeps the
|
|
55
|
+
// first one, so a preset never silently replaces what is registered.
|
|
56
|
+
for (const plugin of Object.values(plugins)) {
|
|
57
|
+
registerPlugin(registration.plugins, plugin);
|
|
58
|
+
}
|
|
59
|
+
return surface;
|
|
60
|
+
},
|
|
53
61
|
withConfig(config) {
|
|
54
62
|
registration.config = Object.assign({}, registration.config, config);
|
|
55
63
|
return surface;
|
|
@@ -44,6 +44,14 @@ export function createBuilderSurface() {
|
|
|
44
44
|
registerPlugin(registration.plugins, plugin);
|
|
45
45
|
return surface;
|
|
46
46
|
},
|
|
47
|
+
useAll(plugins) {
|
|
48
|
+
// In enumeration order, first-wins: a name arriving twice keeps the
|
|
49
|
+
// first one, so a preset never silently replaces what is registered.
|
|
50
|
+
for (const plugin of Object.values(plugins)) {
|
|
51
|
+
registerPlugin(registration.plugins, plugin);
|
|
52
|
+
}
|
|
53
|
+
return surface;
|
|
54
|
+
},
|
|
47
55
|
withConfig(config) {
|
|
48
56
|
registration.config = Object.assign({}, registration.config, config);
|
|
49
57
|
return surface;
|
|
@@ -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
|
+
}
|
|
@@ -26,6 +26,19 @@ export interface FieldBuilder<T extends object, B extends PluginBag, TDeclared e
|
|
|
26
26
|
}
|
|
27
27
|
export interface Builder<B extends PluginBag = Record<never, never>> {
|
|
28
28
|
use<P extends AnyPlugin>(plugin: P): Builder<B & BagEntry<P>>;
|
|
29
|
+
/**
|
|
30
|
+
* A whole SET of plugins at once — a preset, or any object of them.
|
|
31
|
+
*
|
|
32
|
+
* A bag is a name -> plugin map, so a preset is that value and nothing more;
|
|
33
|
+
* there is no registry and no preset type to learn. `use()` one at a time
|
|
34
|
+
* still works and still costs only what it names, which is the point of the
|
|
35
|
+
* subpaths — this is for the case where writing fifteen `use()` lines is the
|
|
36
|
+
* thing standing between you and the validator.
|
|
37
|
+
*
|
|
38
|
+
* Duplicates follow the same rule as `use()`: FIRST WINS, so a preset cannot
|
|
39
|
+
* quietly replace a plugin you already registered.
|
|
40
|
+
*/
|
|
41
|
+
useAll<Bag extends PluginBag>(plugins: Bag): Builder<B & Bag>;
|
|
29
42
|
/**
|
|
30
43
|
* The per-builder override of the process-wide GlobalConfig. Merged over
|
|
31
44
|
* getGlobalConfig() once, at build(), and handed to every plugin as
|
|
@@ -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
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { FieldPath } from "../path/field-path.types";
|
|
2
|
+
import type { ValueAtPath } from "../path/value-at-path.types";
|
|
3
|
+
import type { AnyChain } from "./field-chain.types";
|
|
4
|
+
import type { FieldSlots } from "./field-slots.types";
|
|
5
|
+
import type { PluginBag } from "./plugin-bag.types";
|
|
6
|
+
/** Alias to a path from the root; only paths that exist there are writable. */
|
|
7
|
+
export type BundlePaths<TRoot> = Readonly<Record<string, FieldPath<TRoot> & string>>;
|
|
8
|
+
/** The bundle built from the table: aliases as keys, the path's value as value. */
|
|
9
|
+
export type BundleOf<TRoot, M extends BundlePaths<TRoot>> = {
|
|
10
|
+
readonly [A in keyof M & string]: ValueAtPath<TRoot, M[A]>;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* The sub-chain for one bundle.
|
|
14
|
+
*
|
|
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`.
|
|
18
|
+
*
|
|
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.
|
|
22
|
+
*/
|
|
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 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,13 +1,18 @@
|
|
|
1
|
-
import type { GuardOut, TransformOut } from "../plugin-kit/marker.types";
|
|
1
|
+
import type { BundleOut, GuardOut, StitchOut, TransformOut } from "../plugin-kit/marker.types";
|
|
2
|
+
import type { BundleChain, BundlePaths } from "./bundle-paths.types";
|
|
2
3
|
import type { PluginDefinition, PluginSignature } from "../plugin-kit/plugin-definition";
|
|
3
|
-
import type { Present, RuleOptions, TypeName } from "../types";
|
|
4
|
+
import type { CrossFieldOutcome, Present, RuleOptions, TypeName } from "../types";
|
|
5
|
+
import type { FieldPath } from "../path/field-path.types";
|
|
6
|
+
import type { PickPaths } from "../path/value-at-path.types";
|
|
4
7
|
import type { PluginBag } from "./plugin-bag.types";
|
|
5
8
|
import type { ChainState, CoverWith } from "./chain-state.types";
|
|
6
9
|
import type { ResolveArgs, ResolveOut } from "./resolve-args.types";
|
|
7
10
|
import type { AnyChain, FieldChain } from "./field-chain.types";
|
|
8
11
|
import type { FieldSlots } from "./field-slots.types";
|
|
9
12
|
/** One plugin definition -> one call signature. */
|
|
10
|
-
export type ChainMethod<P, B extends PluginBag, S extends TypeName, TRoot, TValue, TState extends ChainState> = P extends PluginDefinition<string, string, readonly TypeName[], infer Sig extends PluginSignature> ? [Sig["out"]] extends [TransformOut] ? <R>(map: (value: Present<TValue, TState>) => R, options?: RuleOptions<Sig["context"]>) => FieldChain<B, S, TRoot, R, TState> : [Sig["out"]] extends [GuardOut] ? <X extends Present<TValue, TState>>(condition: (value: Present<TValue, TState>) => value is X, define: (b: FieldSlots<TRoot, B, X>) => AnyChain, options?: RuleOptions<Sig["context"]>) => FieldChain<B, S, TRoot, TValue, CoverWith<TState, X>> :
|
|
13
|
+
export type ChainMethod<P, B extends PluginBag, S extends TypeName, TRoot, TValue, TState extends ChainState> = P extends PluginDefinition<string, string, readonly TypeName[], infer Sig extends PluginSignature> ? [Sig["out"]] extends [TransformOut] ? <R>(map: (value: Present<TValue, TState>) => R, options?: RuleOptions<Sig["context"]>) => FieldChain<B, S, TRoot, R, TState> : [Sig["out"]] extends [GuardOut] ? <X extends Present<TValue, TState>>(condition: (value: Present<TValue, TState>) => value is X, define: (b: FieldSlots<TRoot, B, X>) => AnyChain, options?: RuleOptions<Sig["context"]>) => FieldChain<B, S, TRoot, TValue, CoverWith<TState, X>> : [
|
|
14
|
+
Sig["out"]
|
|
15
|
+
] extends [StitchOut] ? <const F extends readonly (FieldPath<TRoot> & string)[]>(fields: F, check: (fieldValues: PickPaths<TRoot, F>, value: Present<TValue, TState>, root: TRoot) => CrossFieldOutcome, options?: RuleOptions<Sig["context"]>) => FieldChain<B, S, TRoot, TValue, TState> : [Sig["out"]] extends [BundleOut] ? <const M extends BundlePaths<TRoot>>(fields: M, define: BundleChain<TRoot, M, B>, options?: RuleOptions<Sig["context"]>) => FieldChain<B, S, TRoot, TValue, TState> : (...args: [
|
|
11
16
|
...ResolveArgs<Sig["args"], B, TRoot, TValue, TState>,
|
|
12
17
|
options?: RuleOptions<Sig["context"]>
|
|
13
18
|
]) => ResolveOut<Sig["out"], TValue, TState> extends [
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Null until someone installs. Exposed as the binding rather than behind a
|
|
3
|
+
* getter because that measured smaller: with no installer reachable, what
|
|
4
|
+
* survives minification is one `let` and an optional chain.
|
|
5
|
+
*/
|
|
6
|
+
export let declarationRecorder = null;
|
|
7
|
+
/**
|
|
8
|
+
* Called once, when the implementation is loaded. A second call overwrites,
|
|
9
|
+
* which is harmless while there is one implementation.
|
|
10
|
+
*/
|
|
11
|
+
export function installDeclarationRecorder(recorder) {
|
|
12
|
+
declarationRecorder = recorder;
|
|
13
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { TypeName } from "../types";
|
|
2
|
+
/** One call of one chain method. */
|
|
3
|
+
export interface DeclaredCall {
|
|
4
|
+
readonly pluginName: string;
|
|
5
|
+
readonly method: string;
|
|
6
|
+
readonly slot: TypeName;
|
|
7
|
+
/**
|
|
8
|
+
* The arguments as declared, after argument resolution and nothing else.
|
|
9
|
+
*
|
|
10
|
+
* They are not converted: a RegExp stays a RegExp. How a given output format
|
|
11
|
+
* spells a value is that writer's business, and converting here would make
|
|
12
|
+
* everyone pay for a conversion only some of them want.
|
|
13
|
+
*/
|
|
14
|
+
readonly args: readonly unknown[];
|
|
15
|
+
}
|