@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
|
@@ -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";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ArrayItemContext, Present } from "../types";
|
|
2
|
-
import type { ElementChain, FieldRef, FieldRefs, GuardOut, MarkerRecord, NarrowedChain, PresenceShift, PropertyKeyChain, PropertyValueChain, RootPredicate, RootReader, SelfGuard, SelfReader, SelfValue, TransformOut, Unchanged } from "../plugin-kit/marker.types";
|
|
2
|
+
import type { ElementChain, FieldRef, FieldRefs, GuardOut, MarkerRecord, NarrowedChain, PresenceShift, PropertyKeyChain, PropertyValueChain, RootPredicate, RootReader, SelfGuard, SelfReader, SelfValue, BundleOut, StitchOut, TransformOut, Unchanged } from "../plugin-kit/marker.types";
|
|
3
3
|
import type { ElementOf } from "../path/element-of.types";
|
|
4
4
|
import type { PropertyValueOf } from "../path/property-value-of.types";
|
|
5
5
|
import type { FieldPath } from "../path/field-path.types";
|
|
@@ -24,5 +24,7 @@ export type ResolveArgs<A extends readonly unknown[], B extends PluginBag, TRoot
|
|
|
24
24
|
/** Presence shifts go through the NAMED operators over ChainState. */
|
|
25
25
|
export type ResolveOut<O, TValue, TState extends ChainState> = [O] extends [
|
|
26
26
|
Unchanged
|
|
27
|
-
] ? [TValue, TState] : [O] extends [TransformOut] ? [unknown, TState] : [O] extends [GuardOut] ? [TValue, TState] : [
|
|
27
|
+
] ? [TValue, TState] : [O] extends [TransformOut] ? [unknown, TState] : [O] extends [GuardOut] ? [TValue, TState] : [
|
|
28
|
+
O
|
|
29
|
+
] extends [StitchOut] ? [TValue, TState] : [O] extends [BundleOut] ? [TValue, TState] : [O] extends [PresenceShift<"excludeMissing">] ? [TValue, ExcludeMissing<TState>] : [O] extends [PresenceShift<"excludeUndefined">] ? [TValue, ExcludeUndefined<TState>] : [O] extends [PresenceShift<"excludeNull">] ? [TValue, ExcludeNull<TState>] : [O] extends [PresenceShift<"allowNull">] ? [TValue | null, AllowNull<TState>] : [O, TState];
|
|
28
30
|
export {};
|
|
@@ -9,11 +9,4 @@ export interface NodeCompileContext {
|
|
|
9
9
|
}
|
|
10
10
|
/** The one adapter from a grouped declaration to compileField's request. */
|
|
11
11
|
export declare function compileRelativeDeclaration(declaration: RelativeDeclaration, context: NodeCompileContext): CompiledField;
|
|
12
|
-
/**
|
|
13
|
-
* Nesting is a re-grouping of the members, not a second traversal strategy:
|
|
14
|
-
* `items[*].sub[*].x` arrives here as the group `items` whose one member is
|
|
15
|
-
* still `sub[*].x`, and that member groups again into the nested node `sub`.
|
|
16
|
-
* `matrix[*][*]` reaches the same shape through an EMPTY nested template —
|
|
17
|
-
* the element of the outer array is itself the inner array.
|
|
18
|
-
*/
|
|
19
12
|
export declare function compileArrayNode(group: ArrayFieldGroup, context: NodeCompileContext): ArrayNode;
|
|
@@ -16,6 +16,7 @@ exports.compileArrayNode = compileArrayNode;
|
|
|
16
16
|
// silently unvalidated.
|
|
17
17
|
// ===========================================================================
|
|
18
18
|
const create_array_reader_1 = require("../path/create-array-reader");
|
|
19
|
+
const format_issue_path_1 = require("../path/format-issue-path");
|
|
19
20
|
const compile_field_1 = require("./compile-field");
|
|
20
21
|
const group_array_fields_1 = require("./group-array-fields");
|
|
21
22
|
/** The one adapter from a grouped declaration to compileField's request. */
|
|
@@ -26,6 +27,7 @@ function compileRelativeDeclaration(declaration, context) {
|
|
|
26
27
|
fieldPath: declaration.fieldPath,
|
|
27
28
|
defaultOf: declaration.defaultOf,
|
|
28
29
|
applyDefaultToNull: declaration.applyDefaultToNull,
|
|
30
|
+
normalize: declaration.normalize,
|
|
29
31
|
planRef: context.planRef,
|
|
30
32
|
eraseComposite: context.eraseComposite,
|
|
31
33
|
});
|
|
@@ -37,11 +39,14 @@ function compileRelativeDeclaration(declaration, context) {
|
|
|
37
39
|
* `matrix[*][*]` reaches the same shape through an EMPTY nested template —
|
|
38
40
|
* the element of the outer array is itself the inner array.
|
|
39
41
|
*/
|
|
42
|
+
/** A grouped node template never keeps a wildcard, so rendering needs no index. */
|
|
43
|
+
const NO_INDICES = Object.freeze([]);
|
|
40
44
|
function compileArrayNode(group, context) {
|
|
41
45
|
const grouped = (0, group_array_fields_1.groupArrayFields)(group.members);
|
|
42
46
|
const template = Object.freeze(group.template);
|
|
43
47
|
const node = {
|
|
44
48
|
template,
|
|
49
|
+
renderedPath: (0, format_issue_path_1.formatIssuePath)(template, NO_INDICES),
|
|
45
50
|
read: (0, create_array_reader_1.createArrayReader)(template),
|
|
46
51
|
elementFields: Object.freeze(grouped.direct.map((declaration) => compileRelativeDeclaration(declaration, context))),
|
|
47
52
|
nested: Object.freeze(grouped.arrays.map((nested) => compileArrayNode(nested, context))),
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
// silently unvalidated.
|
|
13
13
|
// ===========================================================================
|
|
14
14
|
import { createArrayReader } from "../path/create-array-reader.mjs";
|
|
15
|
+
import { formatIssuePath } from "../path/format-issue-path.mjs";
|
|
15
16
|
import { compileField } from "./compile-field.mjs";
|
|
16
17
|
import { groupArrayFields, } from "./group-array-fields.mjs";
|
|
17
18
|
/** The one adapter from a grouped declaration to compileField's request. */
|
|
@@ -22,6 +23,7 @@ export function compileRelativeDeclaration(declaration, context) {
|
|
|
22
23
|
fieldPath: declaration.fieldPath,
|
|
23
24
|
defaultOf: declaration.defaultOf,
|
|
24
25
|
applyDefaultToNull: declaration.applyDefaultToNull,
|
|
26
|
+
normalize: declaration.normalize,
|
|
25
27
|
planRef: context.planRef,
|
|
26
28
|
eraseComposite: context.eraseComposite,
|
|
27
29
|
});
|
|
@@ -33,11 +35,14 @@ export function compileRelativeDeclaration(declaration, context) {
|
|
|
33
35
|
* `matrix[*][*]` reaches the same shape through an EMPTY nested template —
|
|
34
36
|
* the element of the outer array is itself the inner array.
|
|
35
37
|
*/
|
|
38
|
+
/** A grouped node template never keeps a wildcard, so rendering needs no index. */
|
|
39
|
+
const NO_INDICES = Object.freeze([]);
|
|
36
40
|
export function compileArrayNode(group, context) {
|
|
37
41
|
const grouped = groupArrayFields(group.members);
|
|
38
42
|
const template = Object.freeze(group.template);
|
|
39
43
|
const node = {
|
|
40
44
|
template,
|
|
45
|
+
renderedPath: formatIssuePath(template, NO_INDICES),
|
|
41
46
|
read: createArrayReader(template),
|
|
42
47
|
elementFields: Object.freeze(grouped.direct.map((declaration) => compileRelativeDeclaration(declaration, context))),
|
|
43
48
|
nested: Object.freeze(grouped.arrays.map((nested) => compileArrayNode(nested, context))),
|
|
@@ -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
|
}
|
|
@@ -4,6 +4,7 @@ exports.APPLIES_DEFAULT_TO_NULL_BY_DEFAULT = void 0;
|
|
|
4
4
|
exports.compileField = compileField;
|
|
5
5
|
exports.compileFieldDeclaration = compileFieldDeclaration;
|
|
6
6
|
const create_value_reader_1 = require("../path/create-value-reader");
|
|
7
|
+
const format_issue_path_1 = require("../path/format-issue-path");
|
|
7
8
|
const create_value_writer_1 = require("../path/create-value-writer");
|
|
8
9
|
const parse_field_path_1 = require("../path/parse-field-path");
|
|
9
10
|
const resolve_conditional_presence_1 = require("./resolve-conditional-presence");
|
|
@@ -12,17 +13,26 @@ const resolve_recursion_1 = require("./resolve-recursion");
|
|
|
12
13
|
const split_rules_by_kind_1 = require("./split-rules-by-kind");
|
|
13
14
|
/** Whether a field with no explicit setting replaces a null with its default. */
|
|
14
15
|
exports.APPLIES_DEFAULT_TO_NULL_BY_DEFAULT = true;
|
|
16
|
+
/** No indices here: an open index is carried by the run-time prefix. */
|
|
17
|
+
const NO_INDICES = Object.freeze([]);
|
|
15
18
|
function compileField(request) {
|
|
16
19
|
const byKind = (0, split_rules_by_kind_1.splitRulesByKind)(request.rules);
|
|
17
20
|
const hasDefault = request.defaultOf !== null;
|
|
18
|
-
|
|
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;
|
|
19
23
|
const template = Object.freeze(request.template);
|
|
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
|
+
const read = (0, create_value_reader_1.createValueReader)(template);
|
|
20
28
|
const field = {
|
|
21
29
|
template,
|
|
22
|
-
|
|
30
|
+
renderedPath: (0, format_issue_path_1.formatIssuePath)(template, NO_INDICES),
|
|
31
|
+
read,
|
|
23
32
|
write: needsWriter ? (0, create_value_writer_1.createValueWriter)(template) : null,
|
|
24
33
|
defaultOf: request.defaultOf,
|
|
25
34
|
applyDefaultToNull: request.applyDefaultToNull,
|
|
35
|
+
normalize: request.normalize,
|
|
26
36
|
presence: (0, resolve_presence_1.resolvePresence)(byKind.presences),
|
|
27
37
|
presenceOverrides: (0, resolve_conditional_presence_1.resolveConditionalPresence)(byKind.conditionalPresences),
|
|
28
38
|
gates: byKind.gates,
|
|
@@ -45,6 +55,7 @@ function compileFieldDeclaration(declaration, planRef, eraseComposite) {
|
|
|
45
55
|
fieldPath: declaration.path,
|
|
46
56
|
defaultOf: declaration.defaultOf ?? null,
|
|
47
57
|
applyDefaultToNull: declaration.applyDefaultToNull ?? exports.APPLIES_DEFAULT_TO_NULL_BY_DEFAULT,
|
|
58
|
+
normalize: declaration.normalize ?? null,
|
|
48
59
|
planRef,
|
|
49
60
|
eraseComposite,
|
|
50
61
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createValueReader } from "../path/create-value-reader.mjs";
|
|
2
|
+
import { formatIssuePath } from "../path/format-issue-path.mjs";
|
|
2
3
|
import { createValueWriter } from "../path/create-value-writer.mjs";
|
|
3
4
|
import { parseFieldPath } from "../path/parse-field-path.mjs";
|
|
4
5
|
import { resolveConditionalPresence } from "./resolve-conditional-presence.mjs";
|
|
@@ -7,17 +8,26 @@ import { resolveRecursion } from "./resolve-recursion.mjs";
|
|
|
7
8
|
import { splitRulesByKind } from "./split-rules-by-kind.mjs";
|
|
8
9
|
/** Whether a field with no explicit setting replaces a null with its default. */
|
|
9
10
|
export const APPLIES_DEFAULT_TO_NULL_BY_DEFAULT = true;
|
|
11
|
+
/** No indices here: an open index is carried by the run-time prefix. */
|
|
12
|
+
const NO_INDICES = Object.freeze([]);
|
|
10
13
|
export function compileField(request) {
|
|
11
14
|
const byKind = splitRulesByKind(request.rules);
|
|
12
15
|
const hasDefault = request.defaultOf !== null;
|
|
13
|
-
|
|
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;
|
|
14
18
|
const template = Object.freeze(request.template);
|
|
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
|
+
const read = createValueReader(template);
|
|
15
23
|
const field = {
|
|
16
24
|
template,
|
|
17
|
-
|
|
25
|
+
renderedPath: formatIssuePath(template, NO_INDICES),
|
|
26
|
+
read,
|
|
18
27
|
write: needsWriter ? createValueWriter(template) : null,
|
|
19
28
|
defaultOf: request.defaultOf,
|
|
20
29
|
applyDefaultToNull: request.applyDefaultToNull,
|
|
30
|
+
normalize: request.normalize,
|
|
21
31
|
presence: resolvePresence(byKind.presences),
|
|
22
32
|
presenceOverrides: resolveConditionalPresence(byKind.conditionalPresences),
|
|
23
33
|
gates: byKind.gates,
|
|
@@ -40,6 +50,7 @@ export function compileFieldDeclaration(declaration, planRef, eraseComposite) {
|
|
|
40
50
|
fieldPath: declaration.path,
|
|
41
51
|
defaultOf: declaration.defaultOf ?? null,
|
|
42
52
|
applyDefaultToNull: declaration.applyDefaultToNull ?? APPLIES_DEFAULT_TO_NULL_BY_DEFAULT,
|
|
53
|
+
normalize: declaration.normalize ?? null,
|
|
43
54
|
planRef,
|
|
44
55
|
eraseComposite,
|
|
45
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 {
|
|
@@ -11,6 +11,26 @@ class UnknownRuleKindError extends Error {
|
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
13
|
exports.UnknownRuleKindError = UnknownRuleKindError;
|
|
14
|
+
/**
|
|
15
|
+
* Every empty bucket is the same object.
|
|
16
|
+
*
|
|
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.
|
|
21
|
+
*/
|
|
22
|
+
const NO_RULES_OF_THIS_KIND = Object.freeze([]);
|
|
23
|
+
/**
|
|
24
|
+
* Returns the shared empty for an empty bucket, and the frozen array
|
|
25
|
+
* otherwise.
|
|
26
|
+
*
|
|
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.
|
|
30
|
+
*/
|
|
31
|
+
function freezeRules(rules) {
|
|
32
|
+
return rules.length === 0 ? NO_RULES_OF_THIS_KIND : Object.freeze(rules);
|
|
33
|
+
}
|
|
14
34
|
function splitRulesByKind(rules) {
|
|
15
35
|
const checks = [];
|
|
16
36
|
const presences = [];
|
|
@@ -38,13 +58,13 @@ function splitRulesByKind(rules) {
|
|
|
38
58
|
rejectUnknownRuleKind(rule);
|
|
39
59
|
}
|
|
40
60
|
return Object.freeze({
|
|
41
|
-
checks:
|
|
42
|
-
presences:
|
|
43
|
-
conditionalPresences:
|
|
44
|
-
gates:
|
|
45
|
-
transforms:
|
|
46
|
-
composites:
|
|
47
|
-
recursions:
|
|
61
|
+
checks: freezeRules(checks),
|
|
62
|
+
presences: freezeRules(presences),
|
|
63
|
+
conditionalPresences: freezeRules(conditionalPresences),
|
|
64
|
+
gates: freezeRules(gates),
|
|
65
|
+
transforms: freezeRules(transforms),
|
|
66
|
+
composites: freezeRules(composites),
|
|
67
|
+
recursions: freezeRules(recursions),
|
|
48
68
|
});
|
|
49
69
|
}
|
|
50
70
|
/** The exhaustiveness proof: the parameter is `never`, so an eighth kind is a
|
|
@@ -6,6 +6,26 @@ export class UnknownRuleKindError extends Error {
|
|
|
6
6
|
this.name = "UnknownRuleKindError";
|
|
7
7
|
}
|
|
8
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* Every empty bucket is the same object.
|
|
11
|
+
*
|
|
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.
|
|
16
|
+
*/
|
|
17
|
+
const NO_RULES_OF_THIS_KIND = Object.freeze([]);
|
|
18
|
+
/**
|
|
19
|
+
* Returns the shared empty for an empty bucket, and the frozen array
|
|
20
|
+
* otherwise.
|
|
21
|
+
*
|
|
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.
|
|
25
|
+
*/
|
|
26
|
+
function freezeRules(rules) {
|
|
27
|
+
return rules.length === 0 ? NO_RULES_OF_THIS_KIND : Object.freeze(rules);
|
|
28
|
+
}
|
|
9
29
|
export function splitRulesByKind(rules) {
|
|
10
30
|
const checks = [];
|
|
11
31
|
const presences = [];
|
|
@@ -33,13 +53,13 @@ export function splitRulesByKind(rules) {
|
|
|
33
53
|
rejectUnknownRuleKind(rule);
|
|
34
54
|
}
|
|
35
55
|
return Object.freeze({
|
|
36
|
-
checks:
|
|
37
|
-
presences:
|
|
38
|
-
conditionalPresences:
|
|
39
|
-
gates:
|
|
40
|
-
transforms:
|
|
41
|
-
composites:
|
|
42
|
-
recursions:
|
|
56
|
+
checks: freezeRules(checks),
|
|
57
|
+
presences: freezeRules(presences),
|
|
58
|
+
conditionalPresences: freezeRules(conditionalPresences),
|
|
59
|
+
gates: freezeRules(gates),
|
|
60
|
+
transforms: freezeRules(transforms),
|
|
61
|
+
composites: freezeRules(composites),
|
|
62
|
+
recursions: freezeRules(recursions),
|
|
43
63
|
});
|
|
44
64
|
}
|
|
45
65
|
/** The exhaustiveness proof: the parameter is `never`, so an eighth kind is a
|
|
@@ -65,6 +65,18 @@ export interface RecursionPolicy {
|
|
|
65
65
|
}
|
|
66
66
|
export interface CompiledField {
|
|
67
67
|
readonly template: readonly PathSegment[];
|
|
68
|
+
/**
|
|
69
|
+
* This field's own path, rendered without array indices.
|
|
70
|
+
*
|
|
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
|
+
*
|
|
76
|
+
* What remains at validation time is one concatenation with the prefix
|
|
77
|
+
* holding the open indices.
|
|
78
|
+
*/
|
|
79
|
+
readonly renderedPath: string;
|
|
68
80
|
readonly read: (subject: unknown) => unknown;
|
|
69
81
|
/**
|
|
70
82
|
* null unless the field declared a transform or a default — the runtime then
|
|
@@ -77,6 +89,11 @@ export interface CompiledField {
|
|
|
77
89
|
readonly write: ValueWriter | null;
|
|
78
90
|
readonly defaultOf: ((root: unknown) => unknown) | null;
|
|
79
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;
|
|
80
97
|
readonly presence: PresencePolicy;
|
|
81
98
|
/**
|
|
82
99
|
* The conditional overrides of `presence`, in declaration order, and the
|
|
@@ -95,6 +112,18 @@ export interface CompiledField {
|
|
|
95
112
|
/** Loop interchange: one array is read once however many element fields exist. */
|
|
96
113
|
export interface ArrayNode {
|
|
97
114
|
readonly template: readonly PathSegment[];
|
|
115
|
+
/**
|
|
116
|
+
* The node's own path, rendered once at build time — `lines`, never
|
|
117
|
+
* `lines[*]`, because the grouping already cut the wildcard off.
|
|
118
|
+
*
|
|
119
|
+
* CompiledField has carried its `renderedPath` since rebuilding it per
|
|
120
|
+
* element was measured at 35% of the per-element price. The array NODE was
|
|
121
|
+
* left behind and went on rendering the same string on every validate().
|
|
122
|
+
* Rendering can throw on a template with an unconsumed wildcard, so moving
|
|
123
|
+
* it here also moves that throw from validate() to build(), which is the
|
|
124
|
+
* direction this layer is supposed to push everything.
|
|
125
|
+
*/
|
|
126
|
+
readonly renderedPath: string;
|
|
98
127
|
readonly read: (subject: unknown) => unknown;
|
|
99
128
|
readonly elementFields: readonly CompiledField[];
|
|
100
129
|
readonly nested: readonly ArrayNode[];
|
|
@@ -104,6 +133,12 @@ export interface ValidationPlan {
|
|
|
104
133
|
readonly arrays: readonly ArrayNode[];
|
|
105
134
|
readonly hasTransforms: boolean;
|
|
106
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;
|
|
107
142
|
}
|
|
108
143
|
/**
|
|
109
144
|
* What the chain collector hands compile: one path, its ordered rules, and the
|
|
@@ -120,4 +155,5 @@ export interface FieldDeclaration {
|
|
|
120
155
|
readonly rules: readonly Rule[];
|
|
121
156
|
readonly defaultOf?: (root: unknown) => unknown;
|
|
122
157
|
readonly applyDefaultToNull?: boolean;
|
|
158
|
+
readonly normalize?: (value: unknown) => unknown;
|
|
123
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;
|
|
@@ -12,51 +12,57 @@ exports.eraseChainSurface = eraseChainSurface;
|
|
|
12
12
|
exports.eraseBuilderSurface = eraseBuilderSurface;
|
|
13
13
|
exports.eraseSchemaValidator = eraseSchemaValidator;
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
15
|
+
* Why: a record built up key by key has all its keys at run time but stays
|
|
16
|
+
* Record<string, unknown> statically. The generic on the calling side —
|
|
17
|
+
* AsyncContextBuilder.set's `C & { [P in K]: V }` — is what holds the
|
|
18
|
+
* key-to-value correspondence as a type.
|
|
19
19
|
*/
|
|
20
20
|
function eraseAssembledRecord(assembled) {
|
|
21
21
|
return assembled;
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
24
|
+
* Why: a chain is a record carrying one method per plugin in the bag. The
|
|
25
|
+
* keys are all there at run time but the value stays Record<string, unknown>
|
|
26
|
+
* statically, while the matching types are mapped types — which keys appear
|
|
27
|
+
* follows from the type arguments alone and cannot be written where the value
|
|
28
|
+
* is assembled.
|
|
29
|
+
*
|
|
30
|
+
* Soundness depends on the assembly following the same rule the mapped type
|
|
31
|
+
* does: attach a method for a plugin exactly when that plugin declares the
|
|
32
|
+
* slot.
|
|
30
33
|
*/
|
|
31
34
|
function eraseChainSurface(assembled) {
|
|
32
35
|
return assembled;
|
|
33
36
|
}
|
|
34
37
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
38
|
+
* Why: a builder chain is one record holding the plugins added by use() and
|
|
39
|
+
* the declarations added by v(), and its shape does not change as the chain
|
|
40
|
+
* advances. Its static type does — each step is expressed with type arguments
|
|
41
|
+
* (the bag intersection, the union of declared paths, the conditional that
|
|
42
|
+
* branches on union guard exhaustiveness) that the assembling code has no way
|
|
43
|
+
* to write. For the same reason the runtime knows only the plan and can only
|
|
44
|
+
* answer ValidationResult<unknown>; this boundary is the one place the
|
|
45
|
+
* declared T can be put back.
|
|
46
|
+
*
|
|
47
|
+
* Soundness depends on the erased surface declaring the same member set as
|
|
48
|
+
* the public type, with the implementation structurally conforming to it.
|
|
49
|
+
* Exactly one call site is allowed.
|
|
45
50
|
*/
|
|
46
51
|
function eraseBuilderSurface(assembled) {
|
|
47
52
|
return assembled;
|
|
48
53
|
}
|
|
49
54
|
/**
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* ValidationResult<unknown
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
55
|
+
* Why: building a validator from a JSON Schema read at run time gives the
|
|
56
|
+
* compiler nothing to check the declared T against — the document is a value,
|
|
57
|
+
* not a type. The plan-backed validator can only answer
|
|
58
|
+
* ValidationResult<unknown>, so this boundary is the one place the caller's
|
|
59
|
+
* explicit T can be put back. Same kind of erasure as the builder surface,
|
|
60
|
+
* hence the same home.
|
|
61
|
+
*
|
|
62
|
+
* Soundness depends on the plan-backed validator declaring the same member
|
|
63
|
+
* set as Validator<T>, with the implementation structurally conforming to it.
|
|
64
|
+
* When T disagrees with the actual document the type lies, but the runtime
|
|
65
|
+
* result is still correct. Exactly one call site is allowed.
|
|
60
66
|
*/
|
|
61
67
|
function eraseSchemaValidator(planBacked) {
|
|
62
68
|
return planBacked;
|