@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,6 +1,7 @@
|
|
|
1
1
|
import { eraseChainSurface } from "../core/type-erasure.mjs";
|
|
2
2
|
import { createChainNode, EMPTY_RULES, } from "./create-chain-node.mjs";
|
|
3
3
|
import { resolvePluginArguments } from "./collect-branch-rules.mjs";
|
|
4
|
+
import { slotTypeGuard } from "./slot-type-guard.mjs";
|
|
4
5
|
const SLOT_NAMES = Object.freeze([
|
|
5
6
|
"string",
|
|
6
7
|
"number",
|
|
@@ -21,9 +22,16 @@ export function buildSlotSurface(bag, context) {
|
|
|
21
22
|
resolveArguments: (plugin, declared) => resolvePluginArguments(bag, context, buildSlotSurface, plugin, declared),
|
|
22
23
|
};
|
|
23
24
|
for (const slot of SLOT_NAMES) {
|
|
25
|
+
// The slot's own type check leads the chain, so it runs before any value
|
|
26
|
+
// rule. Those rules pass a wrong-typed value through on purpose; this is
|
|
27
|
+
// what reports it. See slot-type-guard.ts.
|
|
28
|
+
const guard = context.typeDecidedElsewhere === true
|
|
29
|
+
? null
|
|
30
|
+
: slotTypeGuard(slot, context.config.defaultSeverity);
|
|
31
|
+
const seed = guard === null ? EMPTY_RULES : Object.freeze([guard]);
|
|
24
32
|
Object.defineProperty(surface, slot, {
|
|
25
33
|
enumerable: true,
|
|
26
|
-
get: () => createChainNode(wiring, slot,
|
|
34
|
+
get: () => createChainNode(wiring, slot, seed),
|
|
27
35
|
});
|
|
28
36
|
}
|
|
29
37
|
return surface;
|
|
@@ -0,0 +1,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
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/chain/index.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
export { attachSlotMethods, CHAIN_BUILT_IN_OWNER, PluginMethodCollisionError, } from "./attach-slot-methods";
|
|
2
2
|
export { createChainNode, readChainRules, EMPTY_RULES, type ChainBuildContext, type ChainNodeWiring, } from "./create-chain-node";
|
|
3
|
+
export type { DeclaredCall } from "./declared-call.types";
|
|
4
|
+
export type { DeclarationRecorder } from "./declaration-recorder.port";
|
|
3
5
|
export { collectBranchRules, resolvePluginArguments, SubChainResultError, type SlotSurfaceFactory, type SubChainArgumentDeclaration, type SubChainDefine, } from "./collect-branch-rules";
|
|
4
6
|
export { buildSlotSurface, createFieldSlots } from "./create-field-slots";
|
|
5
|
-
export { collectFieldRules, FieldChainResultError, } from "./collect-field-rules";
|
|
7
|
+
export { collectFieldRules, FieldChainResultError, type FieldChainOutcome, } from "./collect-field-rules";
|
|
6
8
|
export type { PluginBag, BagEntry, SlotPlugins } from "./plugin-bag.types";
|
|
7
9
|
export type { AllowNull, ChainState, CoverWith, ExcludeMissing, ExcludeNull, ExcludeUndefined, OpenState, UncoveredMembers, UnionGuardCoverageError, } from "./chain-state.types";
|
|
8
10
|
export type { ChainMethod } from "./chain-method.types";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { IssueSeverity, TypeName } from "../types";
|
|
2
|
+
import type { Rule } from "../plugin-kit/compiled-rule";
|
|
3
|
+
/**
|
|
4
|
+
* The rule `b.<slot>` starts its chain with, or null for a slot that claims
|
|
5
|
+
* nothing about the runtime type.
|
|
6
|
+
*
|
|
7
|
+
* The code is `<slot>Type` — `stringType`, `numberType` — which reads as the
|
|
8
|
+
* plugin name it would have had, matching every other code in the library.
|
|
9
|
+
*/
|
|
10
|
+
export declare function slotTypeGuard(slot: TypeName, severity: IssueSeverity): Rule | null;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.slotTypeGuard = slotTypeGuard;
|
|
4
|
+
const types_1 = require("../types");
|
|
5
|
+
const create_rule_1 = require("../plugin-kit/create-rule");
|
|
6
|
+
const SLOT_TYPES = Object.freeze({
|
|
7
|
+
string: { accepts: types_1.isString, noun: "a string" },
|
|
8
|
+
number: { accepts: types_1.isNumber, noun: "a number" },
|
|
9
|
+
boolean: {
|
|
10
|
+
accepts: (value) => typeof value === "boolean",
|
|
11
|
+
noun: "a boolean",
|
|
12
|
+
},
|
|
13
|
+
date: { accepts: (value) => value instanceof Date, noun: "a Date" },
|
|
14
|
+
array: { accepts: types_1.isArray, noun: "an array" },
|
|
15
|
+
object: { accepts: types_1.isPlainObject, noun: "an object" },
|
|
16
|
+
});
|
|
17
|
+
/**
|
|
18
|
+
* The rule `b.<slot>` starts its chain with, or null for a slot that claims
|
|
19
|
+
* nothing about the runtime type.
|
|
20
|
+
*
|
|
21
|
+
* The code is `<slot>Type` — `stringType`, `numberType` — which reads as the
|
|
22
|
+
* plugin name it would have had, matching every other code in the library.
|
|
23
|
+
*/
|
|
24
|
+
function slotTypeGuard(slot, severity) {
|
|
25
|
+
const slotType = SLOT_TYPES[slot];
|
|
26
|
+
if (slotType === undefined)
|
|
27
|
+
return null;
|
|
28
|
+
return (0, create_rule_1.check)({
|
|
29
|
+
code: `${slot}Type`,
|
|
30
|
+
severity,
|
|
31
|
+
run: (value) => value === undefined || value === null || slotType.accepts(value)
|
|
32
|
+
? types_1.PASS
|
|
33
|
+
: (0, types_1.fail)({ expected: slotType.noun, actual: value }),
|
|
34
|
+
describe: () => `Value must be ${slotType.noun}`,
|
|
35
|
+
buildMessageContext: () => ({}),
|
|
36
|
+
});
|
|
37
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { PASS, fail, isArray, isNumber, isPlainObject, isString, } from "../types/index.mjs";
|
|
2
|
+
import { check } from "../plugin-kit/create-rule.mjs";
|
|
3
|
+
const SLOT_TYPES = Object.freeze({
|
|
4
|
+
string: { accepts: isString, noun: "a string" },
|
|
5
|
+
number: { accepts: isNumber, noun: "a number" },
|
|
6
|
+
boolean: {
|
|
7
|
+
accepts: (value) => typeof value === "boolean",
|
|
8
|
+
noun: "a boolean",
|
|
9
|
+
},
|
|
10
|
+
date: { accepts: (value) => value instanceof Date, noun: "a Date" },
|
|
11
|
+
array: { accepts: isArray, noun: "an array" },
|
|
12
|
+
object: { accepts: isPlainObject, noun: "an object" },
|
|
13
|
+
});
|
|
14
|
+
/**
|
|
15
|
+
* The rule `b.<slot>` starts its chain with, or null for a slot that claims
|
|
16
|
+
* nothing about the runtime type.
|
|
17
|
+
*
|
|
18
|
+
* The code is `<slot>Type` — `stringType`, `numberType` — which reads as the
|
|
19
|
+
* plugin name it would have had, matching every other code in the library.
|
|
20
|
+
*/
|
|
21
|
+
export function slotTypeGuard(slot, severity) {
|
|
22
|
+
const slotType = SLOT_TYPES[slot];
|
|
23
|
+
if (slotType === undefined)
|
|
24
|
+
return null;
|
|
25
|
+
return check({
|
|
26
|
+
code: `${slot}Type`,
|
|
27
|
+
severity,
|
|
28
|
+
run: (value) => value === undefined || value === null || slotType.accepts(value)
|
|
29
|
+
? PASS
|
|
30
|
+
: fail({ expected: slotType.noun, actual: value }),
|
|
31
|
+
describe: () => `Value must be ${slotType.noun}`,
|
|
32
|
+
buildMessageContext: () => ({}),
|
|
33
|
+
});
|
|
34
|
+
}
|
|
@@ -27,6 +27,7 @@ function compileRelativeDeclaration(declaration, context) {
|
|
|
27
27
|
fieldPath: declaration.fieldPath,
|
|
28
28
|
defaultOf: declaration.defaultOf,
|
|
29
29
|
applyDefaultToNull: declaration.applyDefaultToNull,
|
|
30
|
+
normalize: declaration.normalize,
|
|
30
31
|
planRef: context.planRef,
|
|
31
32
|
eraseComposite: context.eraseComposite,
|
|
32
33
|
});
|
|
@@ -23,6 +23,7 @@ export function compileRelativeDeclaration(declaration, context) {
|
|
|
23
23
|
fieldPath: declaration.fieldPath,
|
|
24
24
|
defaultOf: declaration.defaultOf,
|
|
25
25
|
applyDefaultToNull: declaration.applyDefaultToNull,
|
|
26
|
+
normalize: declaration.normalize,
|
|
26
27
|
planRef: context.planRef,
|
|
27
28
|
eraseComposite: context.eraseComposite,
|
|
28
29
|
});
|
|
@@ -18,6 +18,7 @@ export interface FieldCompileRequest {
|
|
|
18
18
|
readonly fieldPath: string;
|
|
19
19
|
readonly defaultOf: ((root: unknown) => unknown) | null;
|
|
20
20
|
readonly applyDefaultToNull: boolean;
|
|
21
|
+
readonly normalize: ((value: unknown) => unknown) | null;
|
|
21
22
|
readonly planRef: PlanRef;
|
|
22
23
|
readonly eraseComposite: CompositeEraser;
|
|
23
24
|
}
|
|
@@ -13,17 +13,17 @@ const resolve_recursion_1 = require("./resolve-recursion");
|
|
|
13
13
|
const split_rules_by_kind_1 = require("./split-rules-by-kind");
|
|
14
14
|
/** Whether a field with no explicit setting replaces a null with its default. */
|
|
15
15
|
exports.APPLIES_DEFAULT_TO_NULL_BY_DEFAULT = true;
|
|
16
|
-
/**
|
|
16
|
+
/** No indices here: an open index is carried by the run-time prefix. */
|
|
17
17
|
const NO_INDICES = Object.freeze([]);
|
|
18
18
|
function compileField(request) {
|
|
19
19
|
const byKind = (0, split_rules_by_kind_1.splitRulesByKind)(request.rules);
|
|
20
20
|
const hasDefault = request.defaultOf !== null;
|
|
21
|
-
|
|
21
|
+
// Normalizing replaces the value too, so it needs a writer just as default does.
|
|
22
|
+
const needsWriter = byKind.transforms.length > 0 || hasDefault || request.normalize !== null;
|
|
22
23
|
const template = Object.freeze(request.template);
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
// 追い越して出てしまう (テストがそれを捕まえた)。
|
|
24
|
+
// The reader is made first. Refusing a template that still holds a wildcard
|
|
25
|
+
// is the reader's job; formatting the path before that raises a "not enough
|
|
26
|
+
// indices" RangeError which overtakes the PathSyntaxError that should win.
|
|
27
27
|
const read = (0, create_value_reader_1.createValueReader)(template);
|
|
28
28
|
const field = {
|
|
29
29
|
template,
|
|
@@ -32,6 +32,7 @@ function compileField(request) {
|
|
|
32
32
|
write: needsWriter ? (0, create_value_writer_1.createValueWriter)(template) : null,
|
|
33
33
|
defaultOf: request.defaultOf,
|
|
34
34
|
applyDefaultToNull: request.applyDefaultToNull,
|
|
35
|
+
normalize: request.normalize,
|
|
35
36
|
presence: (0, resolve_presence_1.resolvePresence)(byKind.presences),
|
|
36
37
|
presenceOverrides: (0, resolve_conditional_presence_1.resolveConditionalPresence)(byKind.conditionalPresences),
|
|
37
38
|
gates: byKind.gates,
|
|
@@ -54,6 +55,7 @@ function compileFieldDeclaration(declaration, planRef, eraseComposite) {
|
|
|
54
55
|
fieldPath: declaration.path,
|
|
55
56
|
defaultOf: declaration.defaultOf ?? null,
|
|
56
57
|
applyDefaultToNull: declaration.applyDefaultToNull ?? exports.APPLIES_DEFAULT_TO_NULL_BY_DEFAULT,
|
|
58
|
+
normalize: declaration.normalize ?? null,
|
|
57
59
|
planRef,
|
|
58
60
|
eraseComposite,
|
|
59
61
|
});
|
|
@@ -8,17 +8,17 @@ import { resolveRecursion } from "./resolve-recursion.mjs";
|
|
|
8
8
|
import { splitRulesByKind } from "./split-rules-by-kind.mjs";
|
|
9
9
|
/** Whether a field with no explicit setting replaces a null with its default. */
|
|
10
10
|
export const APPLIES_DEFAULT_TO_NULL_BY_DEFAULT = true;
|
|
11
|
-
/**
|
|
11
|
+
/** No indices here: an open index is carried by the run-time prefix. */
|
|
12
12
|
const NO_INDICES = Object.freeze([]);
|
|
13
13
|
export function compileField(request) {
|
|
14
14
|
const byKind = splitRulesByKind(request.rules);
|
|
15
15
|
const hasDefault = request.defaultOf !== null;
|
|
16
|
-
|
|
16
|
+
// Normalizing replaces the value too, so it needs a writer just as default does.
|
|
17
|
+
const needsWriter = byKind.transforms.length > 0 || hasDefault || request.normalize !== null;
|
|
17
18
|
const template = Object.freeze(request.template);
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
// 追い越して出てしまう (テストがそれを捕まえた)。
|
|
19
|
+
// The reader is made first. Refusing a template that still holds a wildcard
|
|
20
|
+
// is the reader's job; formatting the path before that raises a "not enough
|
|
21
|
+
// indices" RangeError which overtakes the PathSyntaxError that should win.
|
|
22
22
|
const read = createValueReader(template);
|
|
23
23
|
const field = {
|
|
24
24
|
template,
|
|
@@ -27,6 +27,7 @@ export function compileField(request) {
|
|
|
27
27
|
write: needsWriter ? createValueWriter(template) : null,
|
|
28
28
|
defaultOf: request.defaultOf,
|
|
29
29
|
applyDefaultToNull: request.applyDefaultToNull,
|
|
30
|
+
normalize: request.normalize,
|
|
30
31
|
presence: resolvePresence(byKind.presences),
|
|
31
32
|
presenceOverrides: resolveConditionalPresence(byKind.conditionalPresences),
|
|
32
33
|
gates: byKind.gates,
|
|
@@ -49,6 +50,7 @@ export function compileFieldDeclaration(declaration, planRef, eraseComposite) {
|
|
|
49
50
|
fieldPath: declaration.path,
|
|
50
51
|
defaultOf: declaration.defaultOf ?? null,
|
|
51
52
|
applyDefaultToNull: declaration.applyDefaultToNull ?? APPLIES_DEFAULT_TO_NULL_BY_DEFAULT,
|
|
53
|
+
normalize: declaration.normalize ?? null,
|
|
52
54
|
planRef,
|
|
53
55
|
eraseComposite,
|
|
54
56
|
});
|
|
@@ -64,6 +64,7 @@ function compilePlan(relatives, executor) {
|
|
|
64
64
|
arrays: Object.freeze(grouped.arrays.map((group) => (0, compile_array_node_1.compileArrayNode)(group, context))),
|
|
65
65
|
hasTransforms: relatives.some((declaration) => declaration.rules.some((rule) => rule.kind === "transform")),
|
|
66
66
|
hasDefaults: relatives.some((declaration) => declaration.defaultOf !== null),
|
|
67
|
+
hasNormalizers: relatives.some((declaration) => declaration.normalize !== null),
|
|
67
68
|
});
|
|
68
69
|
return compiled;
|
|
69
70
|
}
|
|
@@ -94,6 +95,7 @@ function parseRelativeDeclaration(declaration) {
|
|
|
94
95
|
fieldPath: declaration.path,
|
|
95
96
|
defaultOf: declaration.defaultOf ?? null,
|
|
96
97
|
applyDefaultToNull: declaration.applyDefaultToNull ?? compile_field_1.APPLIES_DEFAULT_TO_NULL_BY_DEFAULT,
|
|
98
|
+
normalize: declaration.normalize ?? null,
|
|
97
99
|
};
|
|
98
100
|
}
|
|
99
101
|
/**
|
|
@@ -111,6 +113,7 @@ function collectBranchDeclarations(branch) {
|
|
|
111
113
|
fieldPath: branch.label,
|
|
112
114
|
defaultOf: null,
|
|
113
115
|
applyDefaultToNull: compile_field_1.APPLIES_DEFAULT_TO_NULL_BY_DEFAULT,
|
|
116
|
+
normalize: null,
|
|
114
117
|
});
|
|
115
118
|
}
|
|
116
119
|
for (const field of branch.fields) {
|
|
@@ -120,6 +123,7 @@ function collectBranchDeclarations(branch) {
|
|
|
120
123
|
fieldPath: field.path,
|
|
121
124
|
defaultOf: null,
|
|
122
125
|
applyDefaultToNull: compile_field_1.APPLIES_DEFAULT_TO_NULL_BY_DEFAULT,
|
|
126
|
+
normalize: null,
|
|
123
127
|
});
|
|
124
128
|
}
|
|
125
129
|
return declarations;
|
|
@@ -59,6 +59,7 @@ function compilePlan(relatives, executor) {
|
|
|
59
59
|
arrays: Object.freeze(grouped.arrays.map((group) => compileArrayNode(group, context))),
|
|
60
60
|
hasTransforms: relatives.some((declaration) => declaration.rules.some((rule) => rule.kind === "transform")),
|
|
61
61
|
hasDefaults: relatives.some((declaration) => declaration.defaultOf !== null),
|
|
62
|
+
hasNormalizers: relatives.some((declaration) => declaration.normalize !== null),
|
|
62
63
|
});
|
|
63
64
|
return compiled;
|
|
64
65
|
}
|
|
@@ -89,6 +90,7 @@ function parseRelativeDeclaration(declaration) {
|
|
|
89
90
|
fieldPath: declaration.path,
|
|
90
91
|
defaultOf: declaration.defaultOf ?? null,
|
|
91
92
|
applyDefaultToNull: declaration.applyDefaultToNull ?? APPLIES_DEFAULT_TO_NULL_BY_DEFAULT,
|
|
93
|
+
normalize: declaration.normalize ?? null,
|
|
92
94
|
};
|
|
93
95
|
}
|
|
94
96
|
/**
|
|
@@ -106,6 +108,7 @@ function collectBranchDeclarations(branch) {
|
|
|
106
108
|
fieldPath: branch.label,
|
|
107
109
|
defaultOf: null,
|
|
108
110
|
applyDefaultToNull: APPLIES_DEFAULT_TO_NULL_BY_DEFAULT,
|
|
111
|
+
normalize: null,
|
|
109
112
|
});
|
|
110
113
|
}
|
|
111
114
|
for (const field of branch.fields) {
|
|
@@ -115,6 +118,7 @@ function collectBranchDeclarations(branch) {
|
|
|
115
118
|
fieldPath: field.path,
|
|
116
119
|
defaultOf: null,
|
|
117
120
|
applyDefaultToNull: APPLIES_DEFAULT_TO_NULL_BY_DEFAULT,
|
|
121
|
+
normalize: null,
|
|
118
122
|
});
|
|
119
123
|
}
|
|
120
124
|
return declarations;
|
|
@@ -14,6 +14,7 @@ export interface RelativeDeclaration {
|
|
|
14
14
|
readonly fieldPath: string;
|
|
15
15
|
readonly defaultOf: ((root: unknown) => unknown) | null;
|
|
16
16
|
readonly applyDefaultToNull: boolean;
|
|
17
|
+
readonly normalize: ((value: unknown) => unknown) | null;
|
|
17
18
|
}
|
|
18
19
|
/** Every declaration that reaches through the same array, plus that array. */
|
|
19
20
|
export interface ArrayFieldGroup {
|
|
@@ -12,25 +12,21 @@ class UnknownRuleKindError extends Error {
|
|
|
12
12
|
}
|
|
13
13
|
exports.UnknownRuleKindError = UnknownRuleKindError;
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
15
|
+
* Every empty bucket is the same object.
|
|
16
16
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* またぐたびに別の受け手を見ることになる。
|
|
22
|
-
*
|
|
23
|
-
* この置き換えは src/compile/resolve-conditional-presence.ts の
|
|
24
|
-
* NO_PRESENCE_OVERRIDES と src/runtime/output-writer.ts の NO_WRITE_TARGETS が
|
|
25
|
-
* 既にやっていることを、残りの種にも広げただけである。
|
|
17
|
+
* A field typically uses two or three of the kinds and leaves the rest empty.
|
|
18
|
+
* Freezing a fresh [] per field per kind made every one of those empties a
|
|
19
|
+
* distinct object, so reading the same empty list across fields kept landing
|
|
20
|
+
* on a different receiver.
|
|
26
21
|
*/
|
|
27
22
|
const NO_RULES_OF_THIS_KIND = Object.freeze([]);
|
|
28
23
|
/**
|
|
29
|
-
*
|
|
24
|
+
* Returns the shared empty for an empty bucket, and the frozen array
|
|
25
|
+
* otherwise.
|
|
30
26
|
*
|
|
31
|
-
* `readonly never[]`
|
|
32
|
-
*
|
|
33
|
-
*
|
|
27
|
+
* `readonly never[]` is assignable to every `readonly R[]`, so sharing needs
|
|
28
|
+
* no type assertion — which matters, because this is not a file allowed to
|
|
29
|
+
* write one.
|
|
34
30
|
*/
|
|
35
31
|
function freezeRules(rules) {
|
|
36
32
|
return rules.length === 0 ? NO_RULES_OF_THIS_KIND : Object.freeze(rules);
|
|
@@ -7,25 +7,21 @@ export class UnknownRuleKindError extends Error {
|
|
|
7
7
|
}
|
|
8
8
|
}
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
10
|
+
* Every empty bucket is the same object.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* またぐたびに別の受け手を見ることになる。
|
|
17
|
-
*
|
|
18
|
-
* この置き換えは src/compile/resolve-conditional-presence.ts の
|
|
19
|
-
* NO_PRESENCE_OVERRIDES と src/runtime/output-writer.ts の NO_WRITE_TARGETS が
|
|
20
|
-
* 既にやっていることを、残りの種にも広げただけである。
|
|
12
|
+
* A field typically uses two or three of the kinds and leaves the rest empty.
|
|
13
|
+
* Freezing a fresh [] per field per kind made every one of those empties a
|
|
14
|
+
* distinct object, so reading the same empty list across fields kept landing
|
|
15
|
+
* on a different receiver.
|
|
21
16
|
*/
|
|
22
17
|
const NO_RULES_OF_THIS_KIND = Object.freeze([]);
|
|
23
18
|
/**
|
|
24
|
-
*
|
|
19
|
+
* Returns the shared empty for an empty bucket, and the frozen array
|
|
20
|
+
* otherwise.
|
|
25
21
|
*
|
|
26
|
-
* `readonly never[]`
|
|
27
|
-
*
|
|
28
|
-
*
|
|
22
|
+
* `readonly never[]` is assignable to every `readonly R[]`, so sharing needs
|
|
23
|
+
* no type assertion — which matters, because this is not a file allowed to
|
|
24
|
+
* write one.
|
|
29
25
|
*/
|
|
30
26
|
function freezeRules(rules) {
|
|
31
27
|
return rules.length === 0 ? NO_RULES_OF_THIS_KIND : Object.freeze(rules);
|
|
@@ -66,14 +66,15 @@ export interface RecursionPolicy {
|
|
|
66
66
|
export interface CompiledField {
|
|
67
67
|
readonly template: readonly PathSegment[];
|
|
68
68
|
/**
|
|
69
|
-
*
|
|
69
|
+
* This field's own path, rendered without array indices.
|
|
70
70
|
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
71
|
+
* Rendered once, at compile time. The template is fixed, so rebuilding it
|
|
72
|
+
* per call meant **producing the same string every time** — once per field
|
|
73
|
+
* per element, whether or not a single issue was reported, and it was a
|
|
74
|
+
* large share of the per-element cost.
|
|
75
75
|
*
|
|
76
|
-
*
|
|
76
|
+
* What remains at validation time is one concatenation with the prefix
|
|
77
|
+
* holding the open indices.
|
|
77
78
|
*/
|
|
78
79
|
readonly renderedPath: string;
|
|
79
80
|
readonly read: (subject: unknown) => unknown;
|
|
@@ -88,6 +89,11 @@ export interface CompiledField {
|
|
|
88
89
|
readonly write: ValueWriter | null;
|
|
89
90
|
readonly defaultOf: ((root: unknown) => unknown) | null;
|
|
90
91
|
readonly applyDefaultToNull: boolean;
|
|
92
|
+
/**
|
|
93
|
+
* null unless the field declared one. Runs after the default and BEFORE
|
|
94
|
+
* presence, on a value that is neither undefined nor null.
|
|
95
|
+
*/
|
|
96
|
+
readonly normalize: ((value: unknown) => unknown) | null;
|
|
91
97
|
readonly presence: PresencePolicy;
|
|
92
98
|
/**
|
|
93
99
|
* The conditional overrides of `presence`, in declaration order, and the
|
|
@@ -127,6 +133,12 @@ export interface ValidationPlan {
|
|
|
127
133
|
readonly arrays: readonly ArrayNode[];
|
|
128
134
|
readonly hasTransforms: boolean;
|
|
129
135
|
readonly hasDefaults: boolean;
|
|
136
|
+
/**
|
|
137
|
+
* A normalizer substitutes a value the same way a default does, so the
|
|
138
|
+
* writer must exist for it too. Kept as its own flag rather than folded
|
|
139
|
+
* into hasDefaults, which would make that name say something it does not.
|
|
140
|
+
*/
|
|
141
|
+
readonly hasNormalizers: boolean;
|
|
130
142
|
}
|
|
131
143
|
/**
|
|
132
144
|
* What the chain collector hands compile: one path, its ordered rules, and the
|
|
@@ -143,4 +155,5 @@ export interface FieldDeclaration {
|
|
|
143
155
|
readonly rules: readonly Rule[];
|
|
144
156
|
readonly defaultOf?: (root: unknown) => unknown;
|
|
145
157
|
readonly applyDefaultToNull?: boolean;
|
|
158
|
+
readonly normalize?: (value: unknown) => unknown;
|
|
146
159
|
}
|
|
@@ -1,42 +1,48 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* Why: a record built up key by key has all its keys at run time but stays
|
|
3
|
+
* Record<string, unknown> statically. The generic on the calling side —
|
|
4
|
+
* AsyncContextBuilder.set's `C & { [P in K]: V }` — is what holds the
|
|
5
|
+
* key-to-value correspondence as a type.
|
|
6
6
|
*/
|
|
7
7
|
export declare function eraseAssembledRecord<T extends object>(assembled: Readonly<Record<string, unknown>>): T;
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
9
|
+
* Why: a chain is a record carrying one method per plugin in the bag. The
|
|
10
|
+
* keys are all there at run time but the value stays Record<string, unknown>
|
|
11
|
+
* statically, while the matching types are mapped types — which keys appear
|
|
12
|
+
* follows from the type arguments alone and cannot be written where the value
|
|
13
|
+
* is assembled.
|
|
14
|
+
*
|
|
15
|
+
* Soundness depends on the assembly following the same rule the mapped type
|
|
16
|
+
* does: attach a method for a plugin exactly when that plugin declares the
|
|
17
|
+
* slot.
|
|
15
18
|
*/
|
|
16
19
|
export declare function eraseChainSurface<T extends object>(assembled: Readonly<Record<string, unknown>>): T;
|
|
17
20
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
21
|
+
* Why: a builder chain is one record holding the plugins added by use() and
|
|
22
|
+
* the declarations added by v(), and its shape does not change as the chain
|
|
23
|
+
* advances. Its static type does — each step is expressed with type arguments
|
|
24
|
+
* (the bag intersection, the union of declared paths, the conditional that
|
|
25
|
+
* branches on union guard exhaustiveness) that the assembling code has no way
|
|
26
|
+
* to write. For the same reason the runtime knows only the plan and can only
|
|
27
|
+
* answer ValidationResult<unknown>; this boundary is the one place the
|
|
28
|
+
* declared T can be put back.
|
|
29
|
+
*
|
|
30
|
+
* Soundness depends on the erased surface declaring the same member set as
|
|
31
|
+
* the public type, with the implementation structurally conforming to it.
|
|
32
|
+
* Exactly one call site is allowed.
|
|
28
33
|
*/
|
|
29
34
|
export declare function eraseBuilderSurface<T extends object>(assembled: object): T;
|
|
30
35
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* ValidationResult<unknown
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
36
|
+
* Why: building a validator from a JSON Schema read at run time gives the
|
|
37
|
+
* compiler nothing to check the declared T against — the document is a value,
|
|
38
|
+
* not a type. The plan-backed validator can only answer
|
|
39
|
+
* ValidationResult<unknown>, so this boundary is the one place the caller's
|
|
40
|
+
* explicit T can be put back. Same kind of erasure as the builder surface,
|
|
41
|
+
* hence the same home.
|
|
42
|
+
*
|
|
43
|
+
* Soundness depends on the plan-backed validator declaring the same member
|
|
44
|
+
* set as Validator<T>, with the implementation structurally conforming to it.
|
|
45
|
+
* When T disagrees with the actual document the type lies, but the runtime
|
|
46
|
+
* result is still correct. Exactly one call site is allowed.
|
|
41
47
|
*/
|
|
42
48
|
export declare function eraseSchemaValidator<T>(planBacked: object): T;
|