@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.
Files changed (161) hide show
  1. package/README.md +92 -430
  2. package/dist/builder/builder-surface.types.d.ts +1 -0
  3. package/dist/builder/compile-declarations.d.ts +7 -1
  4. package/dist/builder/compile-declarations.js +18 -8
  5. package/dist/builder/compile-declarations.mjs +18 -8
  6. package/dist/builder/create-builder.js +8 -0
  7. package/dist/builder/create-builder.mjs +8 -0
  8. package/dist/builder/create-field-builder.js +13 -1
  9. package/dist/builder/create-field-builder.mjs +13 -1
  10. package/dist/builder/declared-calls-store.d.ts +9 -0
  11. package/dist/builder/declared-calls-store.js +19 -0
  12. package/dist/builder/declared-calls-store.mjs +15 -0
  13. package/dist/builder/field-builder.types.d.ts +13 -0
  14. package/dist/builder/field-declared-calls.types.d.ts +6 -0
  15. package/dist/builder/field-declared-calls.types.js +2 -0
  16. package/dist/builder/field-declared-calls.types.mjs +1 -0
  17. package/dist/builder/field-entry.types.d.ts +9 -3
  18. package/dist/builder/field-options.types.d.ts +26 -0
  19. package/dist/chain/bundle-paths.types.d.ts +23 -0
  20. package/dist/chain/bundle-paths.types.js +2 -0
  21. package/dist/chain/bundle-paths.types.mjs +1 -0
  22. package/dist/chain/chain-method.types.d.ts +8 -3
  23. package/dist/chain/chain-node-store.d.ts +5 -0
  24. package/dist/chain/chain-node-store.js +15 -0
  25. package/dist/chain/chain-node-store.mjs +11 -0
  26. package/dist/chain/collect-field-rules.d.ts +13 -2
  27. package/dist/chain/collect-field-rules.js +10 -3
  28. package/dist/chain/collect-field-rules.mjs +10 -3
  29. package/dist/chain/create-chain-node.js +19 -9
  30. package/dist/chain/create-chain-node.mjs +19 -9
  31. package/dist/chain/declaration-recorder.port.d.ts +31 -0
  32. package/dist/chain/declaration-recorder.port.js +17 -0
  33. package/dist/chain/declaration-recorder.port.mjs +13 -0
  34. package/dist/chain/declared-call.types.d.ts +15 -0
  35. package/dist/chain/declared-call.types.js +2 -0
  36. package/dist/chain/declared-call.types.mjs +1 -0
  37. package/dist/chain/index.d.ts +3 -1
  38. package/dist/chain/resolve-args.types.d.ts +4 -2
  39. package/dist/compile/compile-array-node.d.ts +0 -7
  40. package/dist/compile/compile-array-node.js +5 -0
  41. package/dist/compile/compile-array-node.mjs +5 -0
  42. package/dist/compile/compile-field.d.ts +1 -0
  43. package/dist/compile/compile-field.js +13 -2
  44. package/dist/compile/compile-field.mjs +13 -2
  45. package/dist/compile/compile-schema.js +4 -0
  46. package/dist/compile/compile-schema.mjs +4 -0
  47. package/dist/compile/group-array-fields.d.ts +1 -0
  48. package/dist/compile/split-rules-by-kind.js +27 -7
  49. package/dist/compile/split-rules-by-kind.mjs +27 -7
  50. package/dist/compile/validation-plan.types.d.ts +36 -0
  51. package/dist/core/type-erasure.d.ts +36 -30
  52. package/dist/core/type-erasure.js +36 -30
  53. package/dist/core/type-erasure.mjs +36 -30
  54. package/dist/json-schema/build-from-schema.js +8 -1
  55. package/dist/json-schema/build-from-schema.mjs +8 -1
  56. package/dist/json-schema/declare-additional-properties.d.ts +7 -7
  57. package/dist/json-schema/declare-additional-properties.js +7 -7
  58. package/dist/json-schema/declare-additional-properties.mjs +7 -7
  59. package/dist/json-schema/declare-object-keywords.js +4 -4
  60. package/dist/json-schema/declare-object-keywords.mjs +4 -4
  61. package/dist/json-schema/flatten-schema.js +1 -1
  62. package/dist/json-schema/flatten-schema.mjs +1 -1
  63. package/dist/json-schema/follow-json-pointer.d.ts +7 -6
  64. package/dist/json-schema/follow-json-pointer.js +24 -24
  65. package/dist/json-schema/follow-json-pointer.mjs +24 -24
  66. package/dist/json-schema/ref-resolution-error.js +3 -3
  67. package/dist/json-schema/ref-resolution-error.mjs +3 -3
  68. package/dist/json-schema/schema-registry.js +12 -11
  69. package/dist/json-schema/schema-registry.mjs +12 -11
  70. package/dist/json-schema/uri-reference.js +12 -12
  71. package/dist/json-schema/uri-reference.mjs +12 -12
  72. package/dist/path/create-value-writer.js +12 -12
  73. package/dist/path/create-value-writer.mjs +12 -12
  74. package/dist/path/reserved-segment.d.ts +16 -16
  75. package/dist/path/reserved-segment.js +17 -21
  76. package/dist/path/reserved-segment.mjs +17 -21
  77. package/dist/plugin-kit/marker.types.d.ts +18 -0
  78. package/dist/plugins/index.generated.d.ts +1 -0
  79. package/dist/plugins/index.generated.js +6 -4
  80. package/dist/plugins/index.generated.mjs +3 -2
  81. package/dist/plugins/manifest.generated.js +3 -2
  82. package/dist/plugins/manifest.generated.mjs +3 -2
  83. package/dist/plugins/object-additional-properties/select-additional-keys.d.ts +8 -8
  84. package/dist/plugins/object-additional-properties/select-additional-keys.js +16 -16
  85. package/dist/plugins/object-additional-properties/select-additional-keys.mjs +16 -16
  86. package/dist/plugins/stitch/stitch.d.ts +24 -8
  87. package/dist/plugins/stitch-with/index.d.ts +2 -0
  88. package/dist/plugins/stitch-with/index.js +5 -0
  89. package/dist/plugins/stitch-with/index.mjs +1 -0
  90. package/dist/plugins/stitch-with/stitch-with.d.ts +12 -0
  91. package/dist/plugins/stitch-with/stitch-with.js +92 -0
  92. package/dist/plugins/stitch-with/stitch-with.mjs +89 -0
  93. package/dist/plugins/stitchWith.d.ts +1 -0
  94. package/dist/plugins/stitchWith.js +2 -0
  95. package/dist/plugins/stitchWith.mjs +1 -0
  96. package/dist/plugins/string-min/string-min.js +19 -3
  97. package/dist/plugins/string-min/string-min.mjs +20 -4
  98. package/dist/presets/index.d.ts +1 -0
  99. package/dist/presets/index.js +9 -0
  100. package/dist/presets/index.mjs +1 -0
  101. package/dist/presets/presets.d.ts +164 -0
  102. package/dist/presets/presets.js +79 -0
  103. package/dist/presets/presets.mjs +76 -0
  104. package/dist/runtime/create-field-validator.js +22 -8
  105. package/dist/runtime/create-field-validator.mjs +22 -8
  106. package/dist/runtime/create-validator.js +38 -8
  107. package/dist/runtime/create-validator.mjs +38 -8
  108. package/dist/runtime/field-rule-context.d.ts +32 -0
  109. package/dist/runtime/field-rule-context.js +48 -0
  110. package/dist/runtime/field-rule-context.mjs +44 -0
  111. package/dist/runtime/index-stack.d.ts +29 -4
  112. package/dist/runtime/index-stack.js +76 -15
  113. package/dist/runtime/index-stack.mjs +76 -15
  114. package/dist/runtime/output-writer.js +5 -1
  115. package/dist/runtime/output-writer.mjs +5 -1
  116. package/dist/runtime/run-array-node.js +22 -10
  117. package/dist/runtime/run-array-node.mjs +22 -10
  118. package/dist/runtime/run-field.js +47 -18
  119. package/dist/runtime/run-field.mjs +47 -18
  120. package/dist/runtime/run-plan.js +5 -1
  121. package/dist/runtime/run-plan.mjs +5 -1
  122. package/dist/standard-schema/assemble-json-schema.d.ts +4 -0
  123. package/dist/standard-schema/assemble-json-schema.js +95 -0
  124. package/dist/standard-schema/assemble-json-schema.mjs +92 -0
  125. package/dist/standard-schema/declaration-recorder.d.ts +6 -0
  126. package/dist/standard-schema/declaration-recorder.js +30 -0
  127. package/dist/standard-schema/declaration-recorder.mjs +27 -0
  128. package/dist/standard-schema/declarations-unavailable-error.d.ts +4 -0
  129. package/dist/standard-schema/declarations-unavailable-error.js +32 -0
  130. package/dist/standard-schema/declarations-unavailable-error.mjs +28 -0
  131. package/dist/standard-schema/emit-field-schema.d.ts +9 -0
  132. package/dist/standard-schema/emit-field-schema.js +68 -0
  133. package/dist/standard-schema/emit-field-schema.mjs +65 -0
  134. package/dist/standard-schema/index.d.ts +5 -0
  135. package/dist/standard-schema/index.js +9 -1
  136. package/dist/standard-schema/index.mjs +4 -0
  137. package/dist/standard-schema/json-schema-target.d.ts +6 -0
  138. package/dist/standard-schema/json-schema-target.js +44 -0
  139. package/dist/standard-schema/json-schema-target.mjs +39 -0
  140. package/dist/standard-schema/plugin-keyword-map.d.ts +3 -0
  141. package/dist/standard-schema/plugin-keyword-map.js +93 -0
  142. package/dist/standard-schema/plugin-keyword-map.mjs +90 -0
  143. package/dist/standard-schema/split-issue-path.d.ts +6 -4
  144. package/dist/standard-schema/split-issue-path.js +15 -13
  145. package/dist/standard-schema/split-issue-path.mjs +15 -13
  146. package/dist/standard-schema/standard-schema.types.d.ts +8 -7
  147. package/dist/standard-schema/standard-schema.types.js +6 -6
  148. package/dist/standard-schema/standard-schema.types.mjs +6 -6
  149. package/dist/standard-schema/to-standard-json-schema.d.ts +19 -0
  150. package/dist/standard-schema/to-standard-json-schema.js +36 -0
  151. package/dist/standard-schema/to-standard-json-schema.mjs +33 -0
  152. package/dist/standard-schema/to-standard-schema.d.ts +16 -15
  153. package/dist/standard-schema/to-standard-schema.js +15 -22
  154. package/dist/standard-schema/to-standard-schema.mjs +15 -22
  155. package/dist/standard-schema/unrepresentable-rule-error.d.ts +15 -0
  156. package/dist/standard-schema/unrepresentable-rule-error.js +43 -0
  157. package/dist/standard-schema/unrepresentable-rule-error.mjs +38 -0
  158. package/dist/types/index.d.ts +19 -7
  159. package/dist/types/index.js +7 -7
  160. package/dist/types/index.mjs +7 -7
  161. package/package.json +35 -19
@@ -19,11 +19,8 @@ exports.runArrayNodes = runArrayNodes;
19
19
  // `grid[0][2].name` and never the declaration pattern.
20
20
  // ===========================================================================
21
21
  const types_1 = require("../types");
22
- const format_issue_path_1 = require("../path/format-issue-path");
23
22
  const run_field_1 = require("./run-field");
24
23
  const output_writer_1 = require("./output-writer");
25
- /** A node template never contains a wildcard: the grouping cut it off. */
26
- const NO_INDICES = Object.freeze([]);
27
24
  /**
28
25
  * `targets` mirrors `nodes` by position and is EMPTY on every run that produces
29
26
  * no output. Zipping by index needs no lookup and no identity check, because
@@ -57,16 +54,27 @@ function runArrayNode(node, subject, context, target) {
57
54
  return target.write(subject, rebuilt);
58
55
  }
59
56
  function runElements(node, array, context, target) {
60
- const nodePath = (0, format_issue_path_1.formatIssuePath)(node.template, NO_INDICES);
57
+ const nodePath = node.renderedPath;
61
58
  const elementSink = context.sink.forArrayElements();
62
59
  const nested = target === undefined ? output_writer_1.NO_WRITE_TARGETS : target.nested;
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
+ const elementContext = {
67
+ root: context.root,
68
+ sink: elementSink,
69
+ indices: context.indices,
70
+ shouldApplyTransforms: context.shouldApplyTransforms,
71
+ runRecursion: context.runRecursion,
72
+ item: undefined,
73
+ external: context.external,
74
+ };
63
75
  let rebuilt = array;
64
76
  for (let index = 0; index < array.length; index += 1) {
65
- const elementContext = {
66
- ...context,
67
- sink: elementSink,
68
- item: describeItem(array, index),
69
- };
77
+ elementContext.item = describeItem(array, index);
70
78
  context.indices.push(nodePath, index);
71
79
  const element = runNested(node, runElementFields(node, array[index], elementContext), elementContext, nested);
72
80
  context.indices.pop();
@@ -84,7 +92,11 @@ function runNested(node, element, elementContext, nested) {
84
92
  }
85
93
  function runElementFields(node, element, elementContext) {
86
94
  let current = element;
87
- for (const field of node.elementFields) {
95
+ const fields = node.elementFields;
96
+ for (let i = 0; i < fields.length; i += 1) {
97
+ const field = fields[i];
98
+ if (field === undefined)
99
+ continue;
88
100
  const outcome = (0, run_field_1.runField)(field, current, elementContext);
89
101
  current = (0, output_writer_1.writeFieldValue)(field, current, outcome, elementContext.shouldApplyTransforms);
90
102
  if (elementContext.sink.shouldStopPlan())
@@ -16,11 +16,8 @@
16
16
  // `grid[0][2].name` and never the declaration pattern.
17
17
  // ===========================================================================
18
18
  import { isArray } from "../types/index.mjs";
19
- import { formatIssuePath } from "../path/format-issue-path.mjs";
20
19
  import { runField } from "./run-field.mjs";
21
20
  import { NO_WRITE_TARGETS, replaceElement, writeFieldValue, } from "./output-writer.mjs";
22
- /** A node template never contains a wildcard: the grouping cut it off. */
23
- const NO_INDICES = Object.freeze([]);
24
21
  /**
25
22
  * `targets` mirrors `nodes` by position and is EMPTY on every run that produces
26
23
  * no output. Zipping by index needs no lookup and no identity check, because
@@ -54,16 +51,27 @@ function runArrayNode(node, subject, context, target) {
54
51
  return target.write(subject, rebuilt);
55
52
  }
56
53
  function runElements(node, array, context, target) {
57
- const nodePath = formatIssuePath(node.template, NO_INDICES);
54
+ const nodePath = node.renderedPath;
58
55
  const elementSink = context.sink.forArrayElements();
59
56
  const nested = target === undefined ? NO_WRITE_TARGETS : target.nested;
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
+ const elementContext = {
64
+ root: context.root,
65
+ sink: elementSink,
66
+ indices: context.indices,
67
+ shouldApplyTransforms: context.shouldApplyTransforms,
68
+ runRecursion: context.runRecursion,
69
+ item: undefined,
70
+ external: context.external,
71
+ };
60
72
  let rebuilt = array;
61
73
  for (let index = 0; index < array.length; index += 1) {
62
- const elementContext = {
63
- ...context,
64
- sink: elementSink,
65
- item: describeItem(array, index),
66
- };
74
+ elementContext.item = describeItem(array, index);
67
75
  context.indices.push(nodePath, index);
68
76
  const element = runNested(node, runElementFields(node, array[index], elementContext), elementContext, nested);
69
77
  context.indices.pop();
@@ -81,7 +89,11 @@ function runNested(node, element, elementContext, nested) {
81
89
  }
82
90
  function runElementFields(node, element, elementContext) {
83
91
  let current = element;
84
- for (const field of node.elementFields) {
92
+ const fields = node.elementFields;
93
+ for (let i = 0; i < fields.length; i += 1) {
94
+ const field = fields[i];
95
+ if (field === undefined)
96
+ continue;
85
97
  const outcome = runField(field, current, elementContext);
86
98
  current = writeFieldValue(field, current, outcome, elementContext.shouldApplyTransforms);
87
99
  if (elementContext.sink.shouldStopPlan())
@@ -3,21 +3,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.FIELD_VALUE_UNCHANGED = void 0;
4
4
  exports.runField = runField;
5
5
  const create_issue_1 = require("./create-issue");
6
+ const field_rule_context_1 = require("./field-rule-context");
6
7
  const decide_presence_1 = require("./decide-presence");
7
8
  /** Shared, frozen, never re-assigned: the common answer allocates nothing. */
8
9
  exports.FIELD_VALUE_UNCHANGED = Object.freeze({
9
10
  hasWriteBack: false,
10
11
  });
11
12
  function runField(field, subject, context) {
12
- const path = context.indices.renderFieldPath(field.template);
13
- const ruleContext = {
14
- root: context.root,
15
- path,
16
- item: context.item,
17
- external: context.external,
18
- };
13
+ const ruleContext = new field_rule_context_1.FieldRuleContext(context.root, context.indices, field.renderedPath, context.item, context.external);
19
14
  const read = field.read(subject);
20
- const value = applyDefault(field, read, context.root);
15
+ const value = applyNormalize(field, applyDefault(field, read, context.root));
21
16
  if (!(0, decide_presence_1.decidePresence)(field, value, ruleContext, context.sink)) {
22
17
  return exports.FIELD_VALUE_UNCHANGED;
23
18
  }
@@ -45,26 +40,56 @@ function applyDefault(field, value, root) {
45
40
  return field.defaultOf(root);
46
41
  return value;
47
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
+ }
48
56
  /** A closed gate ends the field successfully: no check, no transform. */
49
57
  function openGates(field, value, ruleContext) {
50
- for (const gate of field.gates) {
58
+ const gates = field.gates;
59
+ for (let i = 0; i < gates.length; i += 1) {
60
+ const gate = gates[i];
61
+ if (gate === undefined)
62
+ continue;
51
63
  if (!gate.shouldRun(value, ruleContext))
52
64
  return false;
53
65
  }
54
66
  return true;
55
67
  }
68
+ /**
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.
73
+ */
74
+ function reportCheckFailure(check, detail, value, ruleContext, context) {
75
+ context.sink.add((0, create_issue_1.createIssue)({
76
+ path: ruleContext.path,
77
+ code: check.code,
78
+ severity: check.severity,
79
+ value,
80
+ render: (ctx) => check.describe(detail, ctx),
81
+ }));
82
+ }
56
83
  function runChecks(field, value, ruleContext, context, mark) {
57
- for (const check of field.checks) {
84
+ const checks = field.checks;
85
+ for (let i = 0; i < checks.length; i += 1) {
86
+ const check = checks[i];
87
+ if (check === undefined)
88
+ continue;
58
89
  const outcome = check.run(value, ruleContext);
59
90
  if (outcome.ok)
60
91
  continue;
61
- context.sink.add((0, create_issue_1.createIssue)({
62
- path: ruleContext.path,
63
- code: check.code,
64
- severity: check.severity,
65
- value,
66
- render: (ctx) => check.describe(outcome.detail, ctx),
67
- }));
92
+ reportCheckFailure(check, outcome.detail, value, ruleContext, context);
68
93
  if (context.sink.shouldStopField(mark))
69
94
  return;
70
95
  }
@@ -75,7 +100,11 @@ function runTransforms(field, value, ruleContext, context, mark) {
75
100
  return value;
76
101
  }
77
102
  let current = value;
78
- for (const transform of field.transforms) {
103
+ const transforms = field.transforms;
104
+ for (let i = 0; i < transforms.length; i += 1) {
105
+ const transform = transforms[i];
106
+ if (transform === undefined)
107
+ continue;
79
108
  current = transform.apply(current, ruleContext);
80
109
  }
81
110
  return current;
@@ -1,19 +1,14 @@
1
1
  import { createIssue } from "./create-issue.mjs";
2
+ import { FieldRuleContext } from "./field-rule-context.mjs";
2
3
  import { decidePresence } from "./decide-presence.mjs";
3
4
  /** Shared, frozen, never re-assigned: the common answer allocates nothing. */
4
5
  export const FIELD_VALUE_UNCHANGED = Object.freeze({
5
6
  hasWriteBack: false,
6
7
  });
7
8
  export function runField(field, subject, context) {
8
- const path = context.indices.renderFieldPath(field.template);
9
- const ruleContext = {
10
- root: context.root,
11
- path,
12
- item: context.item,
13
- external: context.external,
14
- };
9
+ const ruleContext = new FieldRuleContext(context.root, context.indices, field.renderedPath, context.item, context.external);
15
10
  const read = field.read(subject);
16
- const value = applyDefault(field, read, context.root);
11
+ const value = applyNormalize(field, applyDefault(field, read, context.root));
17
12
  if (!decidePresence(field, value, ruleContext, context.sink)) {
18
13
  return FIELD_VALUE_UNCHANGED;
19
14
  }
@@ -41,26 +36,56 @@ function applyDefault(field, value, root) {
41
36
  return field.defaultOf(root);
42
37
  return value;
43
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
+ }
44
52
  /** A closed gate ends the field successfully: no check, no transform. */
45
53
  function openGates(field, value, ruleContext) {
46
- for (const gate of field.gates) {
54
+ const gates = field.gates;
55
+ for (let i = 0; i < gates.length; i += 1) {
56
+ const gate = gates[i];
57
+ if (gate === undefined)
58
+ continue;
47
59
  if (!gate.shouldRun(value, ruleContext))
48
60
  return false;
49
61
  }
50
62
  return true;
51
63
  }
64
+ /**
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.
69
+ */
70
+ function reportCheckFailure(check, detail, value, ruleContext, context) {
71
+ context.sink.add(createIssue({
72
+ path: ruleContext.path,
73
+ code: check.code,
74
+ severity: check.severity,
75
+ value,
76
+ render: (ctx) => check.describe(detail, ctx),
77
+ }));
78
+ }
52
79
  function runChecks(field, value, ruleContext, context, mark) {
53
- for (const check of field.checks) {
80
+ const checks = field.checks;
81
+ for (let i = 0; i < checks.length; i += 1) {
82
+ const check = checks[i];
83
+ if (check === undefined)
84
+ continue;
54
85
  const outcome = check.run(value, ruleContext);
55
86
  if (outcome.ok)
56
87
  continue;
57
- context.sink.add(createIssue({
58
- path: ruleContext.path,
59
- code: check.code,
60
- severity: check.severity,
61
- value,
62
- render: (ctx) => check.describe(outcome.detail, ctx),
63
- }));
88
+ reportCheckFailure(check, outcome.detail, value, ruleContext, context);
64
89
  if (context.sink.shouldStopField(mark))
65
90
  return;
66
91
  }
@@ -71,7 +96,11 @@ function runTransforms(field, value, ruleContext, context, mark) {
71
96
  return value;
72
97
  }
73
98
  let current = value;
74
- for (const transform of field.transforms) {
99
+ const transforms = field.transforms;
100
+ for (let i = 0; i < transforms.length; i += 1) {
101
+ const transform = transforms[i];
102
+ if (transform === undefined)
103
+ continue;
75
104
  current = transform.apply(current, ruleContext);
76
105
  }
77
106
  return current;
@@ -19,7 +19,11 @@ const output_writer_1 = require("./output-writer");
19
19
  */
20
20
  function runPlan(plan, subject, context, targets = output_writer_1.NO_WRITE_TARGETS) {
21
21
  let current = subject;
22
- for (const field of plan.fields) {
22
+ const fields = plan.fields;
23
+ for (let i = 0; i < fields.length; i += 1) {
24
+ const field = fields[i];
25
+ if (field === undefined)
26
+ continue;
23
27
  const outcome = (0, run_field_1.runField)(field, current, context);
24
28
  current = (0, output_writer_1.writeFieldValue)(field, current, outcome, context.shouldApplyTransforms);
25
29
  if (context.sink.shouldStopPlan())
@@ -15,7 +15,11 @@ import { NO_WRITE_TARGETS, writeFieldValue } from "./output-writer.mjs";
15
15
  */
16
16
  export function runPlan(plan, subject, context, targets = NO_WRITE_TARGETS) {
17
17
  let current = subject;
18
- for (const field of plan.fields) {
18
+ const fields = plan.fields;
19
+ for (let i = 0; i < fields.length; i += 1) {
20
+ const field = fields[i];
21
+ if (field === undefined)
22
+ continue;
19
23
  const outcome = runField(field, current, context);
20
24
  current = writeFieldValue(field, current, outcome, context.shouldApplyTransforms);
21
25
  if (context.sink.shouldStopPlan())
@@ -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,4 @@
1
+ export declare class DeclarationsUnavailableError extends Error {
2
+ readonly fieldPath?: string | undefined;
3
+ constructor(fieldPath?: string | undefined);
4
+ }
@@ -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;