@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 { stitchWithPlugin } from "./stitch-with.mjs";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { MessageContextExtra } from "../../types";
|
|
2
|
+
import type { BundleOut, NarrowedChain } from "../../plugin-kit/marker.types";
|
|
3
|
+
/** Alias to a path from the root. At run time, a table of strings. */
|
|
4
|
+
export type BundleAliasMap = Readonly<Record<string, string>>;
|
|
5
|
+
export interface StitchWithExtra extends MessageContextExtra {
|
|
6
|
+
readonly aliases: readonly string[];
|
|
7
|
+
}
|
|
8
|
+
export declare const stitchWithPlugin: import("../../plugin-kit/plugin-definition").PluginDefinition<"stitchWith", "stitchWith", readonly ["string", "number", "boolean", "date", "object", "array", "tuple", "union"], {
|
|
9
|
+
args: readonly [fields: BundleAliasMap, define: NarrowedChain];
|
|
10
|
+
out: BundleOut;
|
|
11
|
+
context: StitchWithExtra;
|
|
12
|
+
}>;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.stitchWithPlugin = void 0;
|
|
4
|
+
// ===========================================================================
|
|
5
|
+
// L7 src/plugins/stitch-with/stitch-with.ts — EXPERIMENTAL.
|
|
6
|
+
// The typed successor to `stitch`, for cross-field validation.
|
|
7
|
+
//
|
|
8
|
+
// The point of stitching is to bring SEVERAL FIELDS INTO ONE JUDGEMENT, so
|
|
9
|
+
// the subject is the bundle itself and not each alias. Listing rules per alias
|
|
10
|
+
// is writable but cannot express `total === price * quantity`, at which point
|
|
11
|
+
// it is no longer stitching.
|
|
12
|
+
//
|
|
13
|
+
// One difference from `stitch`. There the bundle reaches the predicate as
|
|
14
|
+
// `Readonly<Record<string, unknown>>`, so the type says nothing about its
|
|
15
|
+
// contents. Here the bundle is assembled from a mapping and IS TYPED:
|
|
16
|
+
//
|
|
17
|
+
// .v("total", (b) => b.number.stitchWith(
|
|
18
|
+
// { cost: "price", count: "quantity" },
|
|
19
|
+
// (f) => f.object.custom((bundle) => bundle.cost * bundle.count === 100)
|
|
20
|
+
// ))
|
|
21
|
+
//
|
|
22
|
+
// `bundle` is `{ cost: number; count: number }`, not a Record. Misspell a
|
|
23
|
+
// member or mistake its type and it fails to compile.
|
|
24
|
+
//
|
|
25
|
+
// Why aliases rather than paths as keys. A path string used as a bundle key is
|
|
26
|
+
// interpreted AS A PATH where it is declared, so `"user.name"` goes looking
|
|
27
|
+
// for `user.name` inside the bundle and never finds it, the bundle being
|
|
28
|
+
// flat. An alias is a bare identifier, so that collision cannot happen and the
|
|
29
|
+
// only things referable are the aliases actually declared.
|
|
30
|
+
//
|
|
31
|
+
// No judgement happens in this file. The sub-chain resolves to
|
|
32
|
+
// `readonly Rule[]` through the same route a narrowed chain takes, and the
|
|
33
|
+
// engine runs it. A bundle-specific collector in the core was built and
|
|
34
|
+
// measured; it added bytes to everyone who never stitches, so this rides the
|
|
35
|
+
// existing route instead and adds none.
|
|
36
|
+
// ===========================================================================
|
|
37
|
+
const types_1 = require("../../types");
|
|
38
|
+
const create_rule_1 = require("../../plugin-kit/create-rule");
|
|
39
|
+
const plugin_definition_1 = require("../../plugin-kit/plugin-definition");
|
|
40
|
+
const index_1 = require("../../path/index");
|
|
41
|
+
const STITCH_WITH_SLOTS = [
|
|
42
|
+
"string",
|
|
43
|
+
"number",
|
|
44
|
+
"boolean",
|
|
45
|
+
"date",
|
|
46
|
+
"object",
|
|
47
|
+
"array",
|
|
48
|
+
"tuple",
|
|
49
|
+
"union",
|
|
50
|
+
];
|
|
51
|
+
const BUNDLE_BRANCH_LABEL = "bundle";
|
|
52
|
+
function readMembers(aliasMap) {
|
|
53
|
+
return Object.freeze(Object.entries(aliasMap).map(([alias, path]) => ({
|
|
54
|
+
alias,
|
|
55
|
+
read: (0, index_1.createValueReader)((0, index_1.parseFieldPath)(path)),
|
|
56
|
+
})));
|
|
57
|
+
}
|
|
58
|
+
/** Assembles the bundle from the root; this object is the branch's subject. */
|
|
59
|
+
function collectBundle(members, root) {
|
|
60
|
+
const bundle = {};
|
|
61
|
+
for (const member of members)
|
|
62
|
+
bundle[member.alias] = member.read(root);
|
|
63
|
+
return bundle;
|
|
64
|
+
}
|
|
65
|
+
exports.stitchWithPlugin = (0, plugin_definition_1.definePlugin)()({
|
|
66
|
+
name: "stitchWith",
|
|
67
|
+
method: "stitchWith",
|
|
68
|
+
slots: STITCH_WITH_SLOTS,
|
|
69
|
+
build: (ctx, aliasMap, rules) => {
|
|
70
|
+
const members = readMembers((0, types_1.isPlainObject)(aliasMap) ? aliasMap : {});
|
|
71
|
+
const aliases = Object.freeze(members.map((member) => member.alias));
|
|
72
|
+
return (0, create_rule_1.composite)({
|
|
73
|
+
code: ctx.code,
|
|
74
|
+
messageFactory: ctx.messageFactory,
|
|
75
|
+
severity: ctx.severity,
|
|
76
|
+
branches: [(0, create_rule_1.branch)(BUNDLE_BRANCH_LABEL, rules)],
|
|
77
|
+
// The field's own value is not read. Only the assembled bundle is.
|
|
78
|
+
combine: (runners) => {
|
|
79
|
+
const runner = runners[0];
|
|
80
|
+
if (runner === undefined)
|
|
81
|
+
return () => types_1.PASS;
|
|
82
|
+
return (_value, runCtx) => {
|
|
83
|
+
const outcome = runner.run(collectBundle(members, runCtx.root), runCtx);
|
|
84
|
+
return outcome.ok ? types_1.PASS : (0, types_1.fail)({ ...outcome.detail });
|
|
85
|
+
};
|
|
86
|
+
},
|
|
87
|
+
describe: (_detail, msgCtx) => `Cross-field validation failed for ${msgCtx.path}`,
|
|
88
|
+
buildMessageContext: () => ({ aliases }),
|
|
89
|
+
});
|
|
90
|
+
},
|
|
91
|
+
subChainArguments: [1],
|
|
92
|
+
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// ===========================================================================
|
|
2
|
+
// L7 src/plugins/stitch-with/stitch-with.ts — EXPERIMENTAL.
|
|
3
|
+
// The typed successor to `stitch`, for cross-field validation.
|
|
4
|
+
//
|
|
5
|
+
// The point of stitching is to bring SEVERAL FIELDS INTO ONE JUDGEMENT, so
|
|
6
|
+
// the subject is the bundle itself and not each alias. Listing rules per alias
|
|
7
|
+
// is writable but cannot express `total === price * quantity`, at which point
|
|
8
|
+
// it is no longer stitching.
|
|
9
|
+
//
|
|
10
|
+
// One difference from `stitch`. There the bundle reaches the predicate as
|
|
11
|
+
// `Readonly<Record<string, unknown>>`, so the type says nothing about its
|
|
12
|
+
// contents. Here the bundle is assembled from a mapping and IS TYPED:
|
|
13
|
+
//
|
|
14
|
+
// .v("total", (b) => b.number.stitchWith(
|
|
15
|
+
// { cost: "price", count: "quantity" },
|
|
16
|
+
// (f) => f.object.custom((bundle) => bundle.cost * bundle.count === 100)
|
|
17
|
+
// ))
|
|
18
|
+
//
|
|
19
|
+
// `bundle` is `{ cost: number; count: number }`, not a Record. Misspell a
|
|
20
|
+
// member or mistake its type and it fails to compile.
|
|
21
|
+
//
|
|
22
|
+
// Why aliases rather than paths as keys. A path string used as a bundle key is
|
|
23
|
+
// interpreted AS A PATH where it is declared, so `"user.name"` goes looking
|
|
24
|
+
// for `user.name` inside the bundle and never finds it, the bundle being
|
|
25
|
+
// flat. An alias is a bare identifier, so that collision cannot happen and the
|
|
26
|
+
// only things referable are the aliases actually declared.
|
|
27
|
+
//
|
|
28
|
+
// No judgement happens in this file. The sub-chain resolves to
|
|
29
|
+
// `readonly Rule[]` through the same route a narrowed chain takes, and the
|
|
30
|
+
// engine runs it. A bundle-specific collector in the core was built and
|
|
31
|
+
// measured; it added bytes to everyone who never stitches, so this rides the
|
|
32
|
+
// existing route instead and adds none.
|
|
33
|
+
// ===========================================================================
|
|
34
|
+
import { PASS, fail, isPlainObject } from "../../types/index.mjs";
|
|
35
|
+
import { branch, composite } from "../../plugin-kit/create-rule.mjs";
|
|
36
|
+
import { definePlugin } from "../../plugin-kit/plugin-definition.mjs";
|
|
37
|
+
import { createValueReader, parseFieldPath } from "../../path/index.mjs";
|
|
38
|
+
const STITCH_WITH_SLOTS = [
|
|
39
|
+
"string",
|
|
40
|
+
"number",
|
|
41
|
+
"boolean",
|
|
42
|
+
"date",
|
|
43
|
+
"object",
|
|
44
|
+
"array",
|
|
45
|
+
"tuple",
|
|
46
|
+
"union",
|
|
47
|
+
];
|
|
48
|
+
const BUNDLE_BRANCH_LABEL = "bundle";
|
|
49
|
+
function readMembers(aliasMap) {
|
|
50
|
+
return Object.freeze(Object.entries(aliasMap).map(([alias, path]) => ({
|
|
51
|
+
alias,
|
|
52
|
+
read: createValueReader(parseFieldPath(path)),
|
|
53
|
+
})));
|
|
54
|
+
}
|
|
55
|
+
/** Assembles the bundle from the root; this object is the branch's subject. */
|
|
56
|
+
function collectBundle(members, root) {
|
|
57
|
+
const bundle = {};
|
|
58
|
+
for (const member of members)
|
|
59
|
+
bundle[member.alias] = member.read(root);
|
|
60
|
+
return bundle;
|
|
61
|
+
}
|
|
62
|
+
export const stitchWithPlugin = /*#__PURE__*/ definePlugin()({
|
|
63
|
+
name: "stitchWith",
|
|
64
|
+
method: "stitchWith",
|
|
65
|
+
slots: STITCH_WITH_SLOTS,
|
|
66
|
+
build: (ctx, aliasMap, rules) => {
|
|
67
|
+
const members = readMembers(isPlainObject(aliasMap) ? aliasMap : {});
|
|
68
|
+
const aliases = Object.freeze(members.map((member) => member.alias));
|
|
69
|
+
return composite({
|
|
70
|
+
code: ctx.code,
|
|
71
|
+
messageFactory: ctx.messageFactory,
|
|
72
|
+
severity: ctx.severity,
|
|
73
|
+
branches: [branch(BUNDLE_BRANCH_LABEL, rules)],
|
|
74
|
+
// The field's own value is not read. Only the assembled bundle is.
|
|
75
|
+
combine: (runners) => {
|
|
76
|
+
const runner = runners[0];
|
|
77
|
+
if (runner === undefined)
|
|
78
|
+
return () => PASS;
|
|
79
|
+
return (_value, runCtx) => {
|
|
80
|
+
const outcome = runner.run(collectBundle(members, runCtx.root), runCtx);
|
|
81
|
+
return outcome.ok ? PASS : fail({ ...outcome.detail });
|
|
82
|
+
};
|
|
83
|
+
},
|
|
84
|
+
describe: (_detail, msgCtx) => `Cross-field validation failed for ${msgCtx.path}`,
|
|
85
|
+
buildMessageContext: () => ({ aliases }),
|
|
86
|
+
});
|
|
87
|
+
},
|
|
88
|
+
subChainArguments: [1],
|
|
89
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./stitch-with/index";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./stitch-with/index.mjs";
|
|
@@ -23,9 +23,25 @@ exports.stringMinPlugin = (0, plugin_definition_1.definePlugin)()({
|
|
|
23
23
|
code: ctx.code,
|
|
24
24
|
messageFactory: ctx.messageFactory,
|
|
25
25
|
severity: ctx.severity,
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
// Stops as soon as min is reached. The full length is only needed to
|
|
27
|
+
// REPORT a failure, and a failing value is shorter than min, so that
|
|
28
|
+
// walk is short too. Counting is still by code point, not UTF-16 unit;
|
|
29
|
+
// the only thing that changed is when it stops.
|
|
30
|
+
run: (value) => {
|
|
31
|
+
if (!(0, types_1.isString)(value))
|
|
32
|
+
return types_1.PASS;
|
|
33
|
+
// min of 0 admits the empty string. The loop body never runs on an
|
|
34
|
+
// empty string, so without this line the empty string fails with 0.
|
|
35
|
+
if (min === 0)
|
|
36
|
+
return types_1.PASS;
|
|
37
|
+
let count = 0;
|
|
38
|
+
for (const _character of value) {
|
|
39
|
+
count += 1;
|
|
40
|
+
if (count >= min)
|
|
41
|
+
return types_1.PASS;
|
|
42
|
+
}
|
|
43
|
+
return (0, types_1.fail)({ expected: min, actual: count });
|
|
44
|
+
},
|
|
29
45
|
describe: (detail) => `String must have at least ${String(min)} characters, but got ` +
|
|
30
46
|
`${String(detail.actual)}`,
|
|
31
47
|
buildMessageContext: (detail) => ({
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// ===========================================================================
|
|
8
8
|
import { check } from "../../plugin-kit/create-rule.mjs";
|
|
9
9
|
import { PluginArgumentError, definePlugin, } from "../../plugin-kit/plugin-definition.mjs";
|
|
10
|
-
import { PASS, fail, isNumber,
|
|
10
|
+
import { PASS, fail, isNumber, isString } from "../../types/index.mjs";
|
|
11
11
|
export const stringMinPlugin = /*#__PURE__*/ definePlugin()({
|
|
12
12
|
name: "stringMin",
|
|
13
13
|
method: "min",
|
|
@@ -20,9 +20,25 @@ export const stringMinPlugin = /*#__PURE__*/ definePlugin()({
|
|
|
20
20
|
code: ctx.code,
|
|
21
21
|
messageFactory: ctx.messageFactory,
|
|
22
22
|
severity: ctx.severity,
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
// Stops as soon as min is reached. The full length is only needed to
|
|
24
|
+
// REPORT a failure, and a failing value is shorter than min, so that
|
|
25
|
+
// walk is short too. Counting is still by code point, not UTF-16 unit;
|
|
26
|
+
// the only thing that changed is when it stops.
|
|
27
|
+
run: (value) => {
|
|
28
|
+
if (!isString(value))
|
|
29
|
+
return PASS;
|
|
30
|
+
// min of 0 admits the empty string. The loop body never runs on an
|
|
31
|
+
// empty string, so without this line the empty string fails with 0.
|
|
32
|
+
if (min === 0)
|
|
33
|
+
return PASS;
|
|
34
|
+
let count = 0;
|
|
35
|
+
for (const _character of value) {
|
|
36
|
+
count += 1;
|
|
37
|
+
if (count >= min)
|
|
38
|
+
return PASS;
|
|
39
|
+
}
|
|
40
|
+
return fail({ expected: min, actual: count });
|
|
41
|
+
},
|
|
26
42
|
describe: (detail) => `String must have at least ${String(min)} characters, but got ` +
|
|
27
43
|
`${String(detail.actual)}`,
|
|
28
44
|
buildMessageContext: (detail) => ({
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { arrays, everydayRules, numbers, presence, strings } from "./presets";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.strings = exports.presence = exports.numbers = exports.everydayRules = exports.arrays = void 0;
|
|
4
|
+
var presets_1 = require("./presets");
|
|
5
|
+
Object.defineProperty(exports, "arrays", { enumerable: true, get: function () { return presets_1.arrays; } });
|
|
6
|
+
Object.defineProperty(exports, "everydayRules", { enumerable: true, get: function () { return presets_1.everydayRules; } });
|
|
7
|
+
Object.defineProperty(exports, "numbers", { enumerable: true, get: function () { return presets_1.numbers; } });
|
|
8
|
+
Object.defineProperty(exports, "presence", { enumerable: true, get: function () { return presets_1.presence; } });
|
|
9
|
+
Object.defineProperty(exports, "strings", { enumerable: true, get: function () { return presets_1.strings; } });
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { arrays, everydayRules, numbers, presence, strings } from "./presets.mjs";
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Present, absent, null. Nearly every declaration uses one of the three.
|
|
3
|
+
*/
|
|
4
|
+
export declare const presence: Readonly<{
|
|
5
|
+
required: import("..").PluginDefinition<"required", "required", readonly import("..").TypeName[], {
|
|
6
|
+
args: readonly [];
|
|
7
|
+
out: import("../plugin-kit").PresenceShift<"excludeMissing">;
|
|
8
|
+
context: import("..").MessageContextExtra;
|
|
9
|
+
}>;
|
|
10
|
+
optional: import("..").PluginDefinition<"optional", "optional", readonly import("..").TypeName[], {
|
|
11
|
+
args: readonly [];
|
|
12
|
+
out: import("../plugin-kit").PresenceShift<"excludeNull">;
|
|
13
|
+
context: import("..").MessageContextExtra;
|
|
14
|
+
}>;
|
|
15
|
+
nullable: import("..").PluginDefinition<"nullable", "nullable", readonly import("..").TypeName[], {
|
|
16
|
+
args: readonly [];
|
|
17
|
+
out: import("../plugin-kit").PresenceShift<"allowNull">;
|
|
18
|
+
context: import("..").MessageContextExtra;
|
|
19
|
+
}>;
|
|
20
|
+
}>;
|
|
21
|
+
/** The everyday string checks. */
|
|
22
|
+
export declare const strings: Readonly<{
|
|
23
|
+
stringMin: import("..").PluginDefinition<"stringMin", "min", readonly ["string"], {
|
|
24
|
+
args: readonly [min: number];
|
|
25
|
+
out: import("../plugin-kit").Unchanged;
|
|
26
|
+
context: import("../plugins/string-min/string-min").StringMinContext;
|
|
27
|
+
}>;
|
|
28
|
+
stringMax: import("..").PluginDefinition<"stringMax", "max", readonly ["string"], {
|
|
29
|
+
args: readonly [max: number];
|
|
30
|
+
out: import("../plugin-kit").Unchanged;
|
|
31
|
+
context: import("../plugins/string-max/string-max").StringMaxContext;
|
|
32
|
+
}>;
|
|
33
|
+
stringPattern: import("..").PluginDefinition<"stringPattern", "pattern", readonly ["string"], {
|
|
34
|
+
args: readonly [pattern: RegExp];
|
|
35
|
+
out: import("../plugin-kit").Unchanged;
|
|
36
|
+
context: {
|
|
37
|
+
readonly pattern: string;
|
|
38
|
+
};
|
|
39
|
+
}>;
|
|
40
|
+
stringEmail: import("..").PluginDefinition<"stringEmail", "email", readonly ["string"], {
|
|
41
|
+
args: readonly [options?: import("../plugins/string-email/string-email").EmailFormatOptions];
|
|
42
|
+
out: import("../plugin-kit").Unchanged;
|
|
43
|
+
context: {
|
|
44
|
+
readonly reason: string;
|
|
45
|
+
};
|
|
46
|
+
}>;
|
|
47
|
+
}>;
|
|
48
|
+
/** The everyday number checks. */
|
|
49
|
+
export declare const numbers: Readonly<{
|
|
50
|
+
numberMin: import("..").PluginDefinition<"numberMin", "min", readonly ["number"], {
|
|
51
|
+
args: readonly [min: number, exclusive?: boolean];
|
|
52
|
+
out: import("../plugin-kit").Unchanged;
|
|
53
|
+
context: import("../plugins/number-min").NumberMinContext;
|
|
54
|
+
}>;
|
|
55
|
+
numberMax: import("..").PluginDefinition<"numberMax", "max", readonly ["number"], {
|
|
56
|
+
args: readonly [max: number, exclusive?: boolean];
|
|
57
|
+
out: import("../plugin-kit").Unchanged;
|
|
58
|
+
context: import("../plugins/number-max").NumberMaxContext;
|
|
59
|
+
}>;
|
|
60
|
+
numberInteger: import("..").PluginDefinition<"numberInteger", "integer", readonly ["number"], {
|
|
61
|
+
args: readonly [];
|
|
62
|
+
out: import("../plugin-kit").Unchanged;
|
|
63
|
+
context: import("..").MessageContextExtra;
|
|
64
|
+
}>;
|
|
65
|
+
}>;
|
|
66
|
+
/** The everyday array checks. Per-element rules ride on arrayEach. */
|
|
67
|
+
export declare const arrays: Readonly<{
|
|
68
|
+
arrayMinLength: import("..").PluginDefinition<"arrayMinLength", "minLength", readonly ["array", "tuple"], {
|
|
69
|
+
args: readonly [min: number];
|
|
70
|
+
out: import("../plugin-kit").Unchanged;
|
|
71
|
+
context: import("../plugins/array-min-length").ArrayMinLengthContext;
|
|
72
|
+
}>;
|
|
73
|
+
arrayMaxLength: import("..").PluginDefinition<"arrayMaxLength", "maxLength", readonly ["array", "tuple"], {
|
|
74
|
+
args: readonly [max: number];
|
|
75
|
+
out: import("../plugin-kit").Unchanged;
|
|
76
|
+
context: import("../plugins/array-max-length").ArrayMaxLengthContext;
|
|
77
|
+
}>;
|
|
78
|
+
arrayEach: import("..").PluginDefinition<"arrayEach", "each", readonly ["array", "tuple"], {
|
|
79
|
+
args: readonly [element: import("../plugin-kit").ElementChain];
|
|
80
|
+
out: import("../plugin-kit").Unchanged;
|
|
81
|
+
context: import("..").MessageContextExtra;
|
|
82
|
+
}>;
|
|
83
|
+
}>;
|
|
84
|
+
/**
|
|
85
|
+
* The four bundles above, together.
|
|
86
|
+
*
|
|
87
|
+
* Not called "common", because that name says nothing — and lint bans it. It
|
|
88
|
+
* holds presence plus the everyday string, number and array checks, and the
|
|
89
|
+
* name is the claim that this is the range people write day to day.
|
|
90
|
+
*
|
|
91
|
+
* Still not everything. For everything there is the `@maroonedog/luq/plugins`
|
|
92
|
+
* barrel, which costs accordingly.
|
|
93
|
+
*/
|
|
94
|
+
export declare const everydayRules: Readonly<{
|
|
95
|
+
arrayMinLength: import("..").PluginDefinition<"arrayMinLength", "minLength", readonly ["array", "tuple"], {
|
|
96
|
+
args: readonly [min: number];
|
|
97
|
+
out: import("../plugin-kit").Unchanged;
|
|
98
|
+
context: import("../plugins/array-min-length").ArrayMinLengthContext;
|
|
99
|
+
}>;
|
|
100
|
+
arrayMaxLength: import("..").PluginDefinition<"arrayMaxLength", "maxLength", readonly ["array", "tuple"], {
|
|
101
|
+
args: readonly [max: number];
|
|
102
|
+
out: import("../plugin-kit").Unchanged;
|
|
103
|
+
context: import("../plugins/array-max-length").ArrayMaxLengthContext;
|
|
104
|
+
}>;
|
|
105
|
+
arrayEach: import("..").PluginDefinition<"arrayEach", "each", readonly ["array", "tuple"], {
|
|
106
|
+
args: readonly [element: import("../plugin-kit").ElementChain];
|
|
107
|
+
out: import("../plugin-kit").Unchanged;
|
|
108
|
+
context: import("..").MessageContextExtra;
|
|
109
|
+
}>;
|
|
110
|
+
numberMin: import("..").PluginDefinition<"numberMin", "min", readonly ["number"], {
|
|
111
|
+
args: readonly [min: number, exclusive?: boolean];
|
|
112
|
+
out: import("../plugin-kit").Unchanged;
|
|
113
|
+
context: import("../plugins/number-min").NumberMinContext;
|
|
114
|
+
}>;
|
|
115
|
+
numberMax: import("..").PluginDefinition<"numberMax", "max", readonly ["number"], {
|
|
116
|
+
args: readonly [max: number, exclusive?: boolean];
|
|
117
|
+
out: import("../plugin-kit").Unchanged;
|
|
118
|
+
context: import("../plugins/number-max").NumberMaxContext;
|
|
119
|
+
}>;
|
|
120
|
+
numberInteger: import("..").PluginDefinition<"numberInteger", "integer", readonly ["number"], {
|
|
121
|
+
args: readonly [];
|
|
122
|
+
out: import("../plugin-kit").Unchanged;
|
|
123
|
+
context: import("..").MessageContextExtra;
|
|
124
|
+
}>;
|
|
125
|
+
stringMin: import("..").PluginDefinition<"stringMin", "min", readonly ["string"], {
|
|
126
|
+
args: readonly [min: number];
|
|
127
|
+
out: import("../plugin-kit").Unchanged;
|
|
128
|
+
context: import("../plugins/string-min/string-min").StringMinContext;
|
|
129
|
+
}>;
|
|
130
|
+
stringMax: import("..").PluginDefinition<"stringMax", "max", readonly ["string"], {
|
|
131
|
+
args: readonly [max: number];
|
|
132
|
+
out: import("../plugin-kit").Unchanged;
|
|
133
|
+
context: import("../plugins/string-max/string-max").StringMaxContext;
|
|
134
|
+
}>;
|
|
135
|
+
stringPattern: import("..").PluginDefinition<"stringPattern", "pattern", readonly ["string"], {
|
|
136
|
+
args: readonly [pattern: RegExp];
|
|
137
|
+
out: import("../plugin-kit").Unchanged;
|
|
138
|
+
context: {
|
|
139
|
+
readonly pattern: string;
|
|
140
|
+
};
|
|
141
|
+
}>;
|
|
142
|
+
stringEmail: import("..").PluginDefinition<"stringEmail", "email", readonly ["string"], {
|
|
143
|
+
args: readonly [options?: import("../plugins/string-email/string-email").EmailFormatOptions];
|
|
144
|
+
out: import("../plugin-kit").Unchanged;
|
|
145
|
+
context: {
|
|
146
|
+
readonly reason: string;
|
|
147
|
+
};
|
|
148
|
+
}>;
|
|
149
|
+
required: import("..").PluginDefinition<"required", "required", readonly import("..").TypeName[], {
|
|
150
|
+
args: readonly [];
|
|
151
|
+
out: import("../plugin-kit").PresenceShift<"excludeMissing">;
|
|
152
|
+
context: import("..").MessageContextExtra;
|
|
153
|
+
}>;
|
|
154
|
+
optional: import("..").PluginDefinition<"optional", "optional", readonly import("..").TypeName[], {
|
|
155
|
+
args: readonly [];
|
|
156
|
+
out: import("../plugin-kit").PresenceShift<"excludeNull">;
|
|
157
|
+
context: import("..").MessageContextExtra;
|
|
158
|
+
}>;
|
|
159
|
+
nullable: import("..").PluginDefinition<"nullable", "nullable", readonly import("..").TypeName[], {
|
|
160
|
+
args: readonly [];
|
|
161
|
+
out: import("../plugin-kit").PresenceShift<"allowNull">;
|
|
162
|
+
context: import("..").MessageContextExtra;
|
|
163
|
+
}>;
|
|
164
|
+
}>;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.everydayRules = exports.arrays = exports.numbers = exports.strings = exports.presence = void 0;
|
|
4
|
+
// ===========================================================================
|
|
5
|
+
// L7 src/presets/presets.ts — predefined plugin bundles.
|
|
6
|
+
//
|
|
7
|
+
// Why they exist. Importing plugins one at a time is the very mechanism behind
|
|
8
|
+
// "you only ship what you used", but making someone write fifteen use() lines
|
|
9
|
+
// for their first validator is imposing that correctness on them.
|
|
10
|
+
//
|
|
11
|
+
// Why several small ones rather than one big one. A single everything-bundle
|
|
12
|
+
// bills the person who needs five plugins for forty. A bundle is just a
|
|
13
|
+
// `PluginBag`, so useAll() takes only the ones wanted, and mixing them is
|
|
14
|
+
// first-wins rather than a collision:
|
|
15
|
+
//
|
|
16
|
+
// Builder().useAll(presence).useAll(strings).for<User>()
|
|
17
|
+
//
|
|
18
|
+
// Membership was chosen from how often each plugin is actually used across
|
|
19
|
+
// this repository, most-used first.
|
|
20
|
+
//
|
|
21
|
+
// What a bundle costs in bytes is measured by the size budget. A preset trades
|
|
22
|
+
// bytes for convenience, so the price should be measured rather than asserted.
|
|
23
|
+
// ===========================================================================
|
|
24
|
+
const required_1 = require("../plugins/required");
|
|
25
|
+
const optional_1 = require("../plugins/optional");
|
|
26
|
+
const nullable_1 = require("../plugins/nullable");
|
|
27
|
+
const string_min_1 = require("../plugins/string-min");
|
|
28
|
+
const string_max_1 = require("../plugins/string-max");
|
|
29
|
+
const string_pattern_1 = require("../plugins/string-pattern");
|
|
30
|
+
const string_email_1 = require("../plugins/string-email");
|
|
31
|
+
const number_min_1 = require("../plugins/number-min");
|
|
32
|
+
const number_max_1 = require("../plugins/number-max");
|
|
33
|
+
const number_integer_1 = require("../plugins/number-integer");
|
|
34
|
+
const array_min_length_1 = require("../plugins/array-min-length");
|
|
35
|
+
const array_max_length_1 = require("../plugins/array-max-length");
|
|
36
|
+
const array_each_1 = require("../plugins/array-each");
|
|
37
|
+
/**
|
|
38
|
+
* Present, absent, null. Nearly every declaration uses one of the three.
|
|
39
|
+
*/
|
|
40
|
+
exports.presence = Object.freeze({
|
|
41
|
+
required: required_1.requiredPlugin,
|
|
42
|
+
optional: optional_1.optionalPlugin,
|
|
43
|
+
nullable: nullable_1.nullablePlugin,
|
|
44
|
+
});
|
|
45
|
+
/** The everyday string checks. */
|
|
46
|
+
exports.strings = Object.freeze({
|
|
47
|
+
stringMin: string_min_1.stringMinPlugin,
|
|
48
|
+
stringMax: string_max_1.stringMaxPlugin,
|
|
49
|
+
stringPattern: string_pattern_1.stringPatternPlugin,
|
|
50
|
+
stringEmail: string_email_1.stringEmailPlugin,
|
|
51
|
+
});
|
|
52
|
+
/** The everyday number checks. */
|
|
53
|
+
exports.numbers = Object.freeze({
|
|
54
|
+
numberMin: number_min_1.numberMinPlugin,
|
|
55
|
+
numberMax: number_max_1.numberMaxPlugin,
|
|
56
|
+
numberInteger: number_integer_1.numberIntegerPlugin,
|
|
57
|
+
});
|
|
58
|
+
/** The everyday array checks. Per-element rules ride on arrayEach. */
|
|
59
|
+
exports.arrays = Object.freeze({
|
|
60
|
+
arrayMinLength: array_min_length_1.arrayMinLengthPlugin,
|
|
61
|
+
arrayMaxLength: array_max_length_1.arrayMaxLengthPlugin,
|
|
62
|
+
arrayEach: array_each_1.arrayEachPlugin,
|
|
63
|
+
});
|
|
64
|
+
/**
|
|
65
|
+
* The four bundles above, together.
|
|
66
|
+
*
|
|
67
|
+
* Not called "common", because that name says nothing — and lint bans it. It
|
|
68
|
+
* holds presence plus the everyday string, number and array checks, and the
|
|
69
|
+
* name is the claim that this is the range people write day to day.
|
|
70
|
+
*
|
|
71
|
+
* Still not everything. For everything there is the `@maroonedog/luq/plugins`
|
|
72
|
+
* barrel, which costs accordingly.
|
|
73
|
+
*/
|
|
74
|
+
exports.everydayRules = Object.freeze({
|
|
75
|
+
...exports.presence,
|
|
76
|
+
...exports.strings,
|
|
77
|
+
...exports.numbers,
|
|
78
|
+
...exports.arrays,
|
|
79
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// ===========================================================================
|
|
2
|
+
// L7 src/presets/presets.ts — predefined plugin bundles.
|
|
3
|
+
//
|
|
4
|
+
// Why they exist. Importing plugins one at a time is the very mechanism behind
|
|
5
|
+
// "you only ship what you used", but making someone write fifteen use() lines
|
|
6
|
+
// for their first validator is imposing that correctness on them.
|
|
7
|
+
//
|
|
8
|
+
// Why several small ones rather than one big one. A single everything-bundle
|
|
9
|
+
// bills the person who needs five plugins for forty. A bundle is just a
|
|
10
|
+
// `PluginBag`, so useAll() takes only the ones wanted, and mixing them is
|
|
11
|
+
// first-wins rather than a collision:
|
|
12
|
+
//
|
|
13
|
+
// Builder().useAll(presence).useAll(strings).for<User>()
|
|
14
|
+
//
|
|
15
|
+
// Membership was chosen from how often each plugin is actually used across
|
|
16
|
+
// this repository, most-used first.
|
|
17
|
+
//
|
|
18
|
+
// What a bundle costs in bytes is measured by the size budget. A preset trades
|
|
19
|
+
// bytes for convenience, so the price should be measured rather than asserted.
|
|
20
|
+
// ===========================================================================
|
|
21
|
+
import { requiredPlugin } from "../plugins/required/index.mjs";
|
|
22
|
+
import { optionalPlugin } from "../plugins/optional/index.mjs";
|
|
23
|
+
import { nullablePlugin } from "../plugins/nullable/index.mjs";
|
|
24
|
+
import { stringMinPlugin } from "../plugins/string-min/index.mjs";
|
|
25
|
+
import { stringMaxPlugin } from "../plugins/string-max/index.mjs";
|
|
26
|
+
import { stringPatternPlugin } from "../plugins/string-pattern/index.mjs";
|
|
27
|
+
import { stringEmailPlugin } from "../plugins/string-email/index.mjs";
|
|
28
|
+
import { numberMinPlugin } from "../plugins/number-min/index.mjs";
|
|
29
|
+
import { numberMaxPlugin } from "../plugins/number-max/index.mjs";
|
|
30
|
+
import { numberIntegerPlugin } from "../plugins/number-integer/index.mjs";
|
|
31
|
+
import { arrayMinLengthPlugin } from "../plugins/array-min-length/index.mjs";
|
|
32
|
+
import { arrayMaxLengthPlugin } from "../plugins/array-max-length/index.mjs";
|
|
33
|
+
import { arrayEachPlugin } from "../plugins/array-each/index.mjs";
|
|
34
|
+
/**
|
|
35
|
+
* Present, absent, null. Nearly every declaration uses one of the three.
|
|
36
|
+
*/
|
|
37
|
+
export const presence = Object.freeze({
|
|
38
|
+
required: requiredPlugin,
|
|
39
|
+
optional: optionalPlugin,
|
|
40
|
+
nullable: nullablePlugin,
|
|
41
|
+
});
|
|
42
|
+
/** The everyday string checks. */
|
|
43
|
+
export const strings = Object.freeze({
|
|
44
|
+
stringMin: stringMinPlugin,
|
|
45
|
+
stringMax: stringMaxPlugin,
|
|
46
|
+
stringPattern: stringPatternPlugin,
|
|
47
|
+
stringEmail: stringEmailPlugin,
|
|
48
|
+
});
|
|
49
|
+
/** The everyday number checks. */
|
|
50
|
+
export const numbers = Object.freeze({
|
|
51
|
+
numberMin: numberMinPlugin,
|
|
52
|
+
numberMax: numberMaxPlugin,
|
|
53
|
+
numberInteger: numberIntegerPlugin,
|
|
54
|
+
});
|
|
55
|
+
/** The everyday array checks. Per-element rules ride on arrayEach. */
|
|
56
|
+
export const arrays = Object.freeze({
|
|
57
|
+
arrayMinLength: arrayMinLengthPlugin,
|
|
58
|
+
arrayMaxLength: arrayMaxLengthPlugin,
|
|
59
|
+
arrayEach: arrayEachPlugin,
|
|
60
|
+
});
|
|
61
|
+
/**
|
|
62
|
+
* The four bundles above, together.
|
|
63
|
+
*
|
|
64
|
+
* Not called "common", because that name says nothing — and lint bans it. It
|
|
65
|
+
* holds presence plus the everyday string, number and array checks, and the
|
|
66
|
+
* name is the claim that this is the range people write day to day.
|
|
67
|
+
*
|
|
68
|
+
* Still not everything. For everything there is the `@maroonedog/luq/plugins`
|
|
69
|
+
* barrel, which costs accordingly.
|
|
70
|
+
*/
|
|
71
|
+
export const everydayRules = Object.freeze({
|
|
72
|
+
...presence,
|
|
73
|
+
...strings,
|
|
74
|
+
...numbers,
|
|
75
|
+
...arrays,
|
|
76
|
+
});
|
|
@@ -56,15 +56,29 @@ function createSubjectComposer(template) {
|
|
|
56
56
|
const write = (0, create_value_writer_1.createValueWriter)(template);
|
|
57
57
|
return (siblings, value) => write(siblings ?? {}, value);
|
|
58
58
|
}
|
|
59
|
-
|
|
59
|
+
const nest = compileNester(template);
|
|
60
|
+
return (siblings, value) => mergeOntoSiblings(siblings, nest(value));
|
|
60
61
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
62
|
+
/**
|
|
63
|
+
* Walks the template **once** and folds it into a wrapping function.
|
|
64
|
+
*
|
|
65
|
+
* Walking it per validation re-read each segment's kind every time, which is
|
|
66
|
+
* exactly the thing validation time must not do: what something IS was settled
|
|
67
|
+
* at compile time. The non-wildcard route was always shaped this way.
|
|
68
|
+
*/
|
|
69
|
+
function compileNester(template) {
|
|
70
|
+
let nest = (value) => value;
|
|
71
|
+
for (let index = template.length - 1; index >= 0; index -= 1) {
|
|
72
|
+
const segment = template[index];
|
|
73
|
+
if (segment === undefined)
|
|
74
|
+
continue;
|
|
75
|
+
const inner = nest;
|
|
76
|
+
nest =
|
|
77
|
+
segment.kind === "each"
|
|
78
|
+
? (value) => [inner(value)]
|
|
79
|
+
: ((key) => (value) => ({ [key]: inner(value) }))(segment.key);
|
|
80
|
+
}
|
|
81
|
+
return nest;
|
|
68
82
|
}
|
|
69
83
|
/** The wrapper wins on the key it names; every other sibling survives. */
|
|
70
84
|
function mergeOntoSiblings(siblings, fragment) {
|