@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
|
@@ -40,23 +40,23 @@ function hasRejectingIssue(issues) {
|
|
|
40
40
|
return issues.some((issue) => issue.severity === "error");
|
|
41
41
|
}
|
|
42
42
|
/**
|
|
43
|
-
*
|
|
43
|
+
* The shared do-nothing runner for a plan that cannot recurse.
|
|
44
44
|
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
45
|
+
* Building a real runner allocates a WeakSet and several closures. Doing that
|
|
46
|
+
* on every validate() of a plan with no recursive rule in it was most of the
|
|
47
|
+
* fixed per-call cost. Whether a plan can recurse is known at build time and
|
|
48
|
+
* is not a question to re-ask at validation time.
|
|
49
49
|
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
50
|
+
* Never actually called: a field only reaches its runner when it declares
|
|
51
|
+
* recursion, and a plan containing one gets the real runner instead.
|
|
52
52
|
*/
|
|
53
53
|
const NO_RECURSION = () => { };
|
|
54
54
|
/**
|
|
55
|
-
*
|
|
55
|
+
* Whether the root needs a real runner.
|
|
56
56
|
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
57
|
+
* Only the fields directly under the root and the elements of array nodes
|
|
58
|
+
* matter. A composite branch makes its own runner for the nested plan, so it
|
|
59
|
+
* never uses the one passed from here.
|
|
60
60
|
*/
|
|
61
61
|
function planCanRecurse(plan) {
|
|
62
62
|
return (plan.fields.some((field) => field.recursion !== null) ||
|
|
@@ -35,23 +35,23 @@ export function hasRejectingIssue(issues) {
|
|
|
35
35
|
return issues.some((issue) => issue.severity === "error");
|
|
36
36
|
}
|
|
37
37
|
/**
|
|
38
|
-
*
|
|
38
|
+
* The shared do-nothing runner for a plan that cannot recurse.
|
|
39
39
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
40
|
+
* Building a real runner allocates a WeakSet and several closures. Doing that
|
|
41
|
+
* on every validate() of a plan with no recursive rule in it was most of the
|
|
42
|
+
* fixed per-call cost. Whether a plan can recurse is known at build time and
|
|
43
|
+
* is not a question to re-ask at validation time.
|
|
44
44
|
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
45
|
+
* Never actually called: a field only reaches its runner when it declares
|
|
46
|
+
* recursion, and a plan containing one gets the real runner instead.
|
|
47
47
|
*/
|
|
48
48
|
const NO_RECURSION = () => { };
|
|
49
49
|
/**
|
|
50
|
-
*
|
|
50
|
+
* Whether the root needs a real runner.
|
|
51
51
|
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
52
|
+
* Only the fields directly under the root and the elements of array nodes
|
|
53
|
+
* matter. A composite branch makes its own runner for the nested plan, so it
|
|
54
|
+
* never uses the one passed from here.
|
|
55
55
|
*/
|
|
56
56
|
function planCanRecurse(plan) {
|
|
57
57
|
return (plan.fields.some((field) => field.recursion !== null) ||
|
|
@@ -24,8 +24,12 @@ function createArrayWriteTargets(nodes) {
|
|
|
24
24
|
* hands back the very object it was given.
|
|
25
25
|
*/
|
|
26
26
|
function createPlanWriteTargets(plan) {
|
|
27
|
-
|
|
27
|
+
// A field declaring only a normalizer is still written back by parse().
|
|
28
|
+
// Leave it out of this condition and no writer is made, so the value read
|
|
29
|
+
// comes back unchanged and in silence.
|
|
30
|
+
if (!plan.hasTransforms && !plan.hasDefaults && !plan.hasNormalizers) {
|
|
28
31
|
return null;
|
|
32
|
+
}
|
|
29
33
|
return createArrayWriteTargets(plan.arrays);
|
|
30
34
|
}
|
|
31
35
|
/**
|
|
@@ -17,8 +17,12 @@ export function createArrayWriteTargets(nodes) {
|
|
|
17
17
|
* hands back the very object it was given.
|
|
18
18
|
*/
|
|
19
19
|
export function createPlanWriteTargets(plan) {
|
|
20
|
-
|
|
20
|
+
// A field declaring only a normalizer is still written back by parse().
|
|
21
|
+
// Leave it out of this condition and no writer is made, so the value read
|
|
22
|
+
// comes back unchanged and in silence.
|
|
23
|
+
if (!plan.hasTransforms && !plan.hasDefaults && !plan.hasNormalizers) {
|
|
21
24
|
return null;
|
|
25
|
+
}
|
|
22
26
|
return createArrayWriteTargets(plan.arrays);
|
|
23
27
|
}
|
|
24
28
|
/**
|
|
@@ -57,12 +57,12 @@ function runElements(node, array, context, target) {
|
|
|
57
57
|
const nodePath = node.renderedPath;
|
|
58
58
|
const elementSink = context.sink.forArrayElements();
|
|
59
59
|
const nested = target === undefined ? output_writer_1.NO_WRITE_TARGETS : target.nested;
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
//
|
|
60
|
+
// One element context per node. `item` is the only member that changes per
|
|
61
|
+
// element; the rest hold still for the whole node, and rebuilding them per
|
|
62
|
+
// element only happened because the fixed parts sat beside the moving one.
|
|
63
|
+
// **It never reaches a plugin** — a plugin sees the RuleContext that running
|
|
64
|
+
// a field assembles, and that reads `item` afresh, so reusing this is not
|
|
65
|
+
// observable from outside.
|
|
66
66
|
const elementContext = {
|
|
67
67
|
root: context.root,
|
|
68
68
|
sink: elementSink,
|
|
@@ -54,12 +54,12 @@ function runElements(node, array, context, target) {
|
|
|
54
54
|
const nodePath = node.renderedPath;
|
|
55
55
|
const elementSink = context.sink.forArrayElements();
|
|
56
56
|
const nested = target === undefined ? NO_WRITE_TARGETS : target.nested;
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
//
|
|
57
|
+
// One element context per node. `item` is the only member that changes per
|
|
58
|
+
// element; the rest hold still for the whole node, and rebuilding them per
|
|
59
|
+
// element only happened because the fixed parts sat beside the moving one.
|
|
60
|
+
// **It never reaches a plugin** — a plugin sees the RuleContext that running
|
|
61
|
+
// a field assembles, and that reads `item` afresh, so reusing this is not
|
|
62
|
+
// observable from outside.
|
|
63
63
|
const elementContext = {
|
|
64
64
|
root: context.root,
|
|
65
65
|
sink: elementSink,
|
|
@@ -12,7 +12,7 @@ exports.FIELD_VALUE_UNCHANGED = Object.freeze({
|
|
|
12
12
|
function runField(field, subject, context) {
|
|
13
13
|
const ruleContext = new field_rule_context_1.FieldRuleContext(context.root, context.indices, field.renderedPath, context.item, context.external);
|
|
14
14
|
const read = field.read(subject);
|
|
15
|
-
const value = applyDefault(field, read, context.root);
|
|
15
|
+
const value = applyNormalize(field, applyDefault(field, read, context.root));
|
|
16
16
|
if (!(0, decide_presence_1.decidePresence)(field, value, ruleContext, context.sink)) {
|
|
17
17
|
return exports.FIELD_VALUE_UNCHANGED;
|
|
18
18
|
}
|
|
@@ -40,15 +40,21 @@ function applyDefault(field, value, root) {
|
|
|
40
40
|
return field.defaultOf(root);
|
|
41
41
|
return value;
|
|
42
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Tidies the value before anything judges it: after default, before presence.
|
|
45
|
+
*
|
|
46
|
+
* Never called for undefined or null. That closes the accident where a
|
|
47
|
+
* caller's `(v) => String(v).trim()` turns a missing field into the string
|
|
48
|
+
* `"undefined"` and walks it past `.required()`. Absence is default's job.
|
|
49
|
+
*/
|
|
50
|
+
function applyNormalize(field, value) {
|
|
51
|
+
if (field.normalize === null || value === undefined || value === null) {
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
54
|
+
return field.normalize(value);
|
|
55
|
+
}
|
|
43
56
|
/** A closed gate ends the field successfully: no check, no transform. */
|
|
44
57
|
function openGates(field, value, ruleContext) {
|
|
45
|
-
// 添字ループである。for-of ではない。ここが回るのはコンパイル済みの
|
|
46
|
-
// 凍結配列で、凍結配列は V8 では PACKED_FROZEN_ELEMENTS になり、配列
|
|
47
|
-
// イテレータの高速化パスから外れる — イテレータと IteratorResult が
|
|
48
|
-
// 消去されず、要素×フィールドの回数だけ確保される。配列シェイプでは
|
|
49
|
-
// それだけで全ゴミの 45〜54% を占めていた (独立に5通りの改変で -45%
|
|
50
|
-
// 〜 -54%)。凍結は落とさない: コンパイル層の不変条件であり、凍結を
|
|
51
|
-
// 外しても添字ループより速くはならない。
|
|
52
58
|
const gates = field.gates;
|
|
53
59
|
for (let i = 0; i < gates.length; i += 1) {
|
|
54
60
|
const gate = gates[i];
|
|
@@ -60,17 +66,10 @@ function openGates(field, value, ruleContext) {
|
|
|
60
66
|
return true;
|
|
61
67
|
}
|
|
62
68
|
/**
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
* --trace-turbo-inlining に名指しされていた。その 302 バイトの大半が、
|
|
68
|
-
* 受理された値では一度も走らない issue の組み立てである。ここへ出すと
|
|
69
|
-
* 残るループ本体が縮み、受理パスで 6.4% 速くなった。
|
|
70
|
-
*
|
|
71
|
-
* 中断の判定はここに含めない。issue を足したあとに shouldStopField を
|
|
72
|
-
* 見るという順序が abortEarlyOnEachField の意味そのものなので、呼び出し側に
|
|
73
|
-
* 並べて置いておく。
|
|
69
|
+
* The side taken only on failure. Lifted out of the loop body because most of
|
|
70
|
+
* that body was issue construction an accepted value never runs, and its size
|
|
71
|
+
* was what pushed the check loop past the inlining budget. Stopping is not
|
|
72
|
+
* decided here: add the issue, then ask — that order is what abortEarly means.
|
|
74
73
|
*/
|
|
75
74
|
function reportCheckFailure(check, detail, value, ruleContext, context) {
|
|
76
75
|
context.sink.add((0, create_issue_1.createIssue)({
|
|
@@ -8,7 +8,7 @@ export const FIELD_VALUE_UNCHANGED = Object.freeze({
|
|
|
8
8
|
export function runField(field, subject, context) {
|
|
9
9
|
const ruleContext = new FieldRuleContext(context.root, context.indices, field.renderedPath, context.item, context.external);
|
|
10
10
|
const read = field.read(subject);
|
|
11
|
-
const value = applyDefault(field, read, context.root);
|
|
11
|
+
const value = applyNormalize(field, applyDefault(field, read, context.root));
|
|
12
12
|
if (!decidePresence(field, value, ruleContext, context.sink)) {
|
|
13
13
|
return FIELD_VALUE_UNCHANGED;
|
|
14
14
|
}
|
|
@@ -36,15 +36,21 @@ function applyDefault(field, value, root) {
|
|
|
36
36
|
return field.defaultOf(root);
|
|
37
37
|
return value;
|
|
38
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Tidies the value before anything judges it: after default, before presence.
|
|
41
|
+
*
|
|
42
|
+
* Never called for undefined or null. That closes the accident where a
|
|
43
|
+
* caller's `(v) => String(v).trim()` turns a missing field into the string
|
|
44
|
+
* `"undefined"` and walks it past `.required()`. Absence is default's job.
|
|
45
|
+
*/
|
|
46
|
+
function applyNormalize(field, value) {
|
|
47
|
+
if (field.normalize === null || value === undefined || value === null) {
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
return field.normalize(value);
|
|
51
|
+
}
|
|
39
52
|
/** A closed gate ends the field successfully: no check, no transform. */
|
|
40
53
|
function openGates(field, value, ruleContext) {
|
|
41
|
-
// 添字ループである。for-of ではない。ここが回るのはコンパイル済みの
|
|
42
|
-
// 凍結配列で、凍結配列は V8 では PACKED_FROZEN_ELEMENTS になり、配列
|
|
43
|
-
// イテレータの高速化パスから外れる — イテレータと IteratorResult が
|
|
44
|
-
// 消去されず、要素×フィールドの回数だけ確保される。配列シェイプでは
|
|
45
|
-
// それだけで全ゴミの 45〜54% を占めていた (独立に5通りの改変で -45%
|
|
46
|
-
// 〜 -54%)。凍結は落とさない: コンパイル層の不変条件であり、凍結を
|
|
47
|
-
// 外しても添字ループより速くはならない。
|
|
48
54
|
const gates = field.gates;
|
|
49
55
|
for (let i = 0; i < gates.length; i += 1) {
|
|
50
56
|
const gate = gates[i];
|
|
@@ -56,17 +62,10 @@ function openGates(field, value, ruleContext) {
|
|
|
56
62
|
return true;
|
|
57
63
|
}
|
|
58
64
|
/**
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
* --trace-turbo-inlining に名指しされていた。その 302 バイトの大半が、
|
|
64
|
-
* 受理された値では一度も走らない issue の組み立てである。ここへ出すと
|
|
65
|
-
* 残るループ本体が縮み、受理パスで 6.4% 速くなった。
|
|
66
|
-
*
|
|
67
|
-
* 中断の判定はここに含めない。issue を足したあとに shouldStopField を
|
|
68
|
-
* 見るという順序が abortEarlyOnEachField の意味そのものなので、呼び出し側に
|
|
69
|
-
* 並べて置いておく。
|
|
65
|
+
* The side taken only on failure. Lifted out of the loop body because most of
|
|
66
|
+
* that body was issue construction an accepted value never runs, and its size
|
|
67
|
+
* was what pushed the check loop past the inlining budget. Stopping is not
|
|
68
|
+
* decided here: add the issue, then ask — that order is what abortEarly means.
|
|
70
69
|
*/
|
|
71
70
|
function reportCheckFailure(check, detail, value, ruleContext, context) {
|
|
72
71
|
context.sink.add(createIssue({
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { FieldDeclaredCalls } from "../builder/field-declared-calls.types";
|
|
2
|
+
import type { UnrepresentablePolicy } from "./unrepresentable-rule-error";
|
|
3
|
+
/** Assembles the root schema from the list of declarations. */
|
|
4
|
+
export declare function assembleJsonSchema(fields: readonly FieldDeclaredCalls[], policy: UnrepresentablePolicy): Record<string, unknown>;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.assembleJsonSchema = assembleJsonSchema;
|
|
4
|
+
// ===========================================================================
|
|
5
|
+
// L10 src/standard-schema/assemble-json-schema.ts
|
|
6
|
+
//
|
|
7
|
+
// Builds the nested object and array schema from a list of paths.
|
|
8
|
+
//
|
|
9
|
+
// `"owner.name"` becomes `properties.owner.properties.name`, and
|
|
10
|
+
// `"employees[*].name"` becomes
|
|
11
|
+
// `properties.employees.items.properties.name`. Paths are read with the
|
|
12
|
+
// existing path parser rather than a second grammar written here.
|
|
13
|
+
//
|
|
14
|
+
// `required` belongs to the PARENT. In JSON Schema it says "this object must
|
|
15
|
+
// carry this key", which is a fact about the object, not about the key.
|
|
16
|
+
// ===========================================================================
|
|
17
|
+
const parse_field_path_1 = require("../path/parse-field-path");
|
|
18
|
+
const emit_field_schema_1 = require("./emit-field-schema");
|
|
19
|
+
const declarations_unavailable_error_1 = require("./declarations-unavailable-error");
|
|
20
|
+
const newNode = () => ({
|
|
21
|
+
properties: new Map(),
|
|
22
|
+
required: new Set(),
|
|
23
|
+
element: null,
|
|
24
|
+
leaf: null,
|
|
25
|
+
});
|
|
26
|
+
/** Descends through `key`, creating it when absent. */
|
|
27
|
+
function descend(node, key) {
|
|
28
|
+
const existing = node.properties.get(key);
|
|
29
|
+
if (existing !== undefined)
|
|
30
|
+
return existing;
|
|
31
|
+
const created = newNode();
|
|
32
|
+
node.properties.set(key, created);
|
|
33
|
+
return created;
|
|
34
|
+
}
|
|
35
|
+
/** Places one path in the tree, setting the schema where the leaf lands. */
|
|
36
|
+
function place(root, segments, schema, isRequired) {
|
|
37
|
+
let node = root;
|
|
38
|
+
let owner = null;
|
|
39
|
+
let ownerKey = "";
|
|
40
|
+
for (const segment of segments) {
|
|
41
|
+
if (segment.kind === "each") {
|
|
42
|
+
node.element ?? (node.element = newNode());
|
|
43
|
+
node = node.element;
|
|
44
|
+
owner = null;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
owner = node;
|
|
48
|
+
ownerKey = segment.key;
|
|
49
|
+
node = descend(node, segment.key);
|
|
50
|
+
}
|
|
51
|
+
node.leaf = schema;
|
|
52
|
+
if (isRequired && owner !== null)
|
|
53
|
+
owner.required.add(ownerKey);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Freezes a node into a JSON Schema.
|
|
57
|
+
*
|
|
58
|
+
* On a node that has children, the leaf schema is the FOUNDATION. When both
|
|
59
|
+
* `.object.required()` and `"user.name"` are declared, the properties from
|
|
60
|
+
* the latter sit on top of the `type: "object"` from the former.
|
|
61
|
+
*/
|
|
62
|
+
function freeze(node) {
|
|
63
|
+
const schema = { ...node.leaf };
|
|
64
|
+
if (node.element !== null) {
|
|
65
|
+
schema["type"] ?? (schema["type"] = "array");
|
|
66
|
+
schema["items"] = freeze(node.element);
|
|
67
|
+
}
|
|
68
|
+
if (node.properties.size > 0) {
|
|
69
|
+
schema["type"] ?? (schema["type"] = "object");
|
|
70
|
+
const properties = {};
|
|
71
|
+
for (const [key, child] of node.properties) {
|
|
72
|
+
properties[key] = freeze(child);
|
|
73
|
+
}
|
|
74
|
+
schema["properties"] = properties;
|
|
75
|
+
}
|
|
76
|
+
if (node.required.size > 0)
|
|
77
|
+
schema["required"] = [...node.required];
|
|
78
|
+
return schema;
|
|
79
|
+
}
|
|
80
|
+
/** Assembles the root schema from the list of declarations. */
|
|
81
|
+
function assembleJsonSchema(fields, policy) {
|
|
82
|
+
const root = newNode();
|
|
83
|
+
for (const field of fields) {
|
|
84
|
+
// One field with no record is enough to refuse, whatever the policy says.
|
|
85
|
+
if (field.calls === null)
|
|
86
|
+
throw new declarations_unavailable_error_1.DeclarationsUnavailableError(field.path);
|
|
87
|
+
const emitted = (0, emit_field_schema_1.emitFieldSchema)(field.path, field.calls, policy);
|
|
88
|
+
place(root, (0, parse_field_path_1.parseFieldPath)(field.path), emitted.schema, emitted.isRequired);
|
|
89
|
+
}
|
|
90
|
+
const schema = freeze(root);
|
|
91
|
+
// Even with nothing declared, or everything dropped, the root is an object:
|
|
92
|
+
// the builder only accepts an object type in the first place.
|
|
93
|
+
schema["type"] ?? (schema["type"] = "object");
|
|
94
|
+
return schema;
|
|
95
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// ===========================================================================
|
|
2
|
+
// L10 src/standard-schema/assemble-json-schema.ts
|
|
3
|
+
//
|
|
4
|
+
// Builds the nested object and array schema from a list of paths.
|
|
5
|
+
//
|
|
6
|
+
// `"owner.name"` becomes `properties.owner.properties.name`, and
|
|
7
|
+
// `"employees[*].name"` becomes
|
|
8
|
+
// `properties.employees.items.properties.name`. Paths are read with the
|
|
9
|
+
// existing path parser rather than a second grammar written here.
|
|
10
|
+
//
|
|
11
|
+
// `required` belongs to the PARENT. In JSON Schema it says "this object must
|
|
12
|
+
// carry this key", which is a fact about the object, not about the key.
|
|
13
|
+
// ===========================================================================
|
|
14
|
+
import { parseFieldPath } from "../path/parse-field-path.mjs";
|
|
15
|
+
import { emitFieldSchema } from "./emit-field-schema.mjs";
|
|
16
|
+
import { DeclarationsUnavailableError } from "./declarations-unavailable-error.mjs";
|
|
17
|
+
const newNode = () => ({
|
|
18
|
+
properties: new Map(),
|
|
19
|
+
required: new Set(),
|
|
20
|
+
element: null,
|
|
21
|
+
leaf: null,
|
|
22
|
+
});
|
|
23
|
+
/** Descends through `key`, creating it when absent. */
|
|
24
|
+
function descend(node, key) {
|
|
25
|
+
const existing = node.properties.get(key);
|
|
26
|
+
if (existing !== undefined)
|
|
27
|
+
return existing;
|
|
28
|
+
const created = newNode();
|
|
29
|
+
node.properties.set(key, created);
|
|
30
|
+
return created;
|
|
31
|
+
}
|
|
32
|
+
/** Places one path in the tree, setting the schema where the leaf lands. */
|
|
33
|
+
function place(root, segments, schema, isRequired) {
|
|
34
|
+
let node = root;
|
|
35
|
+
let owner = null;
|
|
36
|
+
let ownerKey = "";
|
|
37
|
+
for (const segment of segments) {
|
|
38
|
+
if (segment.kind === "each") {
|
|
39
|
+
node.element ?? (node.element = newNode());
|
|
40
|
+
node = node.element;
|
|
41
|
+
owner = null;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
owner = node;
|
|
45
|
+
ownerKey = segment.key;
|
|
46
|
+
node = descend(node, segment.key);
|
|
47
|
+
}
|
|
48
|
+
node.leaf = schema;
|
|
49
|
+
if (isRequired && owner !== null)
|
|
50
|
+
owner.required.add(ownerKey);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Freezes a node into a JSON Schema.
|
|
54
|
+
*
|
|
55
|
+
* On a node that has children, the leaf schema is the FOUNDATION. When both
|
|
56
|
+
* `.object.required()` and `"user.name"` are declared, the properties from
|
|
57
|
+
* the latter sit on top of the `type: "object"` from the former.
|
|
58
|
+
*/
|
|
59
|
+
function freeze(node) {
|
|
60
|
+
const schema = { ...node.leaf };
|
|
61
|
+
if (node.element !== null) {
|
|
62
|
+
schema["type"] ?? (schema["type"] = "array");
|
|
63
|
+
schema["items"] = freeze(node.element);
|
|
64
|
+
}
|
|
65
|
+
if (node.properties.size > 0) {
|
|
66
|
+
schema["type"] ?? (schema["type"] = "object");
|
|
67
|
+
const properties = {};
|
|
68
|
+
for (const [key, child] of node.properties) {
|
|
69
|
+
properties[key] = freeze(child);
|
|
70
|
+
}
|
|
71
|
+
schema["properties"] = properties;
|
|
72
|
+
}
|
|
73
|
+
if (node.required.size > 0)
|
|
74
|
+
schema["required"] = [...node.required];
|
|
75
|
+
return schema;
|
|
76
|
+
}
|
|
77
|
+
/** Assembles the root schema from the list of declarations. */
|
|
78
|
+
export function assembleJsonSchema(fields, policy) {
|
|
79
|
+
const root = newNode();
|
|
80
|
+
for (const field of fields) {
|
|
81
|
+
// One field with no record is enough to refuse, whatever the policy says.
|
|
82
|
+
if (field.calls === null)
|
|
83
|
+
throw new DeclarationsUnavailableError(field.path);
|
|
84
|
+
const emitted = emitFieldSchema(field.path, field.calls, policy);
|
|
85
|
+
place(root, parseFieldPath(field.path), emitted.schema, emitted.isRequired);
|
|
86
|
+
}
|
|
87
|
+
const schema = freeze(root);
|
|
88
|
+
// Even with nothing declared, or everything dropped, the root is an object:
|
|
89
|
+
// the builder only accepts an object type in the first place.
|
|
90
|
+
schema["type"] ?? (schema["type"] = "object");
|
|
91
|
+
return schema;
|
|
92
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Idempotent. Call it at module scope, never from inside a function: the
|
|
3
|
+
* recorder has to be in place before any chain runs, and hiding the call
|
|
4
|
+
* behind a function makes that ordering depend on who calls it first.
|
|
5
|
+
*/
|
|
6
|
+
export declare function installJsonSchemaDeclarationRecorder(): void;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.installJsonSchemaDeclarationRecorder = installJsonSchemaDeclarationRecorder;
|
|
4
|
+
const declaration_recorder_port_1 = require("../chain/declaration-recorder.port");
|
|
5
|
+
const NONE = Object.freeze([]);
|
|
6
|
+
const callsByNode = new WeakMap();
|
|
7
|
+
const recorder = {
|
|
8
|
+
record(parent, child, plugin, slot, args) {
|
|
9
|
+
callsByNode.set(child, [
|
|
10
|
+
...(callsByNode.get(parent) ?? NONE),
|
|
11
|
+
{ pluginName: plugin.name, method: plugin.method, slot, args },
|
|
12
|
+
]);
|
|
13
|
+
},
|
|
14
|
+
inherit(parent, child) {
|
|
15
|
+
const calls = callsByNode.get(parent);
|
|
16
|
+
if (calls !== undefined)
|
|
17
|
+
callsByNode.set(child, calls);
|
|
18
|
+
},
|
|
19
|
+
read(node) {
|
|
20
|
+
return callsByNode.get(node);
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Idempotent. Call it at module scope, never from inside a function: the
|
|
25
|
+
* recorder has to be in place before any chain runs, and hiding the call
|
|
26
|
+
* behind a function makes that ordering depend on who calls it first.
|
|
27
|
+
*/
|
|
28
|
+
function installJsonSchemaDeclarationRecorder() {
|
|
29
|
+
(0, declaration_recorder_port_1.installDeclarationRecorder)(recorder);
|
|
30
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { installDeclarationRecorder, } from "../chain/declaration-recorder.port.mjs";
|
|
2
|
+
const NONE = Object.freeze([]);
|
|
3
|
+
const callsByNode = new WeakMap();
|
|
4
|
+
const recorder = {
|
|
5
|
+
record(parent, child, plugin, slot, args) {
|
|
6
|
+
callsByNode.set(child, [
|
|
7
|
+
...(callsByNode.get(parent) ?? NONE),
|
|
8
|
+
{ pluginName: plugin.name, method: plugin.method, slot, args },
|
|
9
|
+
]);
|
|
10
|
+
},
|
|
11
|
+
inherit(parent, child) {
|
|
12
|
+
const calls = callsByNode.get(parent);
|
|
13
|
+
if (calls !== undefined)
|
|
14
|
+
callsByNode.set(child, calls);
|
|
15
|
+
},
|
|
16
|
+
read(node) {
|
|
17
|
+
return callsByNode.get(node);
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Idempotent. Call it at module scope, never from inside a function: the
|
|
22
|
+
* recorder has to be in place before any chain runs, and hiding the call
|
|
23
|
+
* behind a function makes that ordering depend on who calls it first.
|
|
24
|
+
*/
|
|
25
|
+
export function installJsonSchemaDeclarationRecorder() {
|
|
26
|
+
installDeclarationRecorder(recorder);
|
|
27
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ===========================================================================
|
|
3
|
+
// L10 src/standard-schema/declarations-unavailable-error.ts
|
|
4
|
+
//
|
|
5
|
+
// "Nothing was declared" and "what was declared is not known" are different
|
|
6
|
+
// answers, and only one of them can be written out.
|
|
7
|
+
//
|
|
8
|
+
// Collapsing them into an empty list produces a schema that permits everything
|
|
9
|
+
// and looks entirely confident about it. This refusal exists to stop that.
|
|
10
|
+
//
|
|
11
|
+
// It outranks the unrepresentable policy: `omit` is permission to drop a
|
|
12
|
+
// declaration that cannot be written, not permission to write without knowing
|
|
13
|
+
// what was declared.
|
|
14
|
+
// ===========================================================================
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.DeclarationsUnavailableError = void 0;
|
|
17
|
+
class DeclarationsUnavailableError extends Error {
|
|
18
|
+
constructor(fieldPath) {
|
|
19
|
+
super((fieldPath === undefined
|
|
20
|
+
? "This validator carries no declarations"
|
|
21
|
+
: `"${fieldPath}" carries no declarations`) +
|
|
22
|
+
", so no JSON Schema can be emitted from it. Two things cause " +
|
|
23
|
+
"this. A validator from fromJsonSchema() was assembled from rules " +
|
|
24
|
+
"directly and never went through the builder chain. Otherwise the " +
|
|
25
|
+
'build() ran before "@maroonedog/luq/standard-schema" was loaded — ' +
|
|
26
|
+
"import it from the module that builds the validator, or from one " +
|
|
27
|
+
"evaluated before it.");
|
|
28
|
+
this.fieldPath = fieldPath;
|
|
29
|
+
this.name = "DeclarationsUnavailableError";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
exports.DeclarationsUnavailableError = DeclarationsUnavailableError;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// ===========================================================================
|
|
2
|
+
// L10 src/standard-schema/declarations-unavailable-error.ts
|
|
3
|
+
//
|
|
4
|
+
// "Nothing was declared" and "what was declared is not known" are different
|
|
5
|
+
// answers, and only one of them can be written out.
|
|
6
|
+
//
|
|
7
|
+
// Collapsing them into an empty list produces a schema that permits everything
|
|
8
|
+
// and looks entirely confident about it. This refusal exists to stop that.
|
|
9
|
+
//
|
|
10
|
+
// It outranks the unrepresentable policy: `omit` is permission to drop a
|
|
11
|
+
// declaration that cannot be written, not permission to write without knowing
|
|
12
|
+
// what was declared.
|
|
13
|
+
// ===========================================================================
|
|
14
|
+
export class DeclarationsUnavailableError extends Error {
|
|
15
|
+
constructor(fieldPath) {
|
|
16
|
+
super((fieldPath === undefined
|
|
17
|
+
? "This validator carries no declarations"
|
|
18
|
+
: `"${fieldPath}" carries no declarations`) +
|
|
19
|
+
", so no JSON Schema can be emitted from it. Two things cause " +
|
|
20
|
+
"this. A validator from fromJsonSchema() was assembled from rules " +
|
|
21
|
+
"directly and never went through the builder chain. Otherwise the " +
|
|
22
|
+
'build() ran before "@maroonedog/luq/standard-schema" was loaded — ' +
|
|
23
|
+
"import it from the module that builds the validator, or from one " +
|
|
24
|
+
"evaluated before it.");
|
|
25
|
+
this.fieldPath = fieldPath;
|
|
26
|
+
this.name = "DeclarationsUnavailableError";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { DeclaredCall } from "../chain/declared-call.types";
|
|
2
|
+
import { type UnrepresentablePolicy } from "./unrepresentable-rule-error";
|
|
3
|
+
/** One emitted field. `required` is separate: the parent assembles it. */
|
|
4
|
+
export interface EmittedField {
|
|
5
|
+
readonly schema: Record<string, unknown>;
|
|
6
|
+
readonly isRequired: boolean;
|
|
7
|
+
}
|
|
8
|
+
/** Makes one field's schema from that field's declared calls. */
|
|
9
|
+
export declare function emitFieldSchema(fieldPath: string, calls: readonly DeclaredCall[], policy: UnrepresentablePolicy): EmittedField;
|