@maroonedog/luq 2.4.3 → 2.5.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 (60) hide show
  1. package/README.md +199 -9
  2. package/dist/chain/field-chain.types.d.ts +11 -1
  3. package/dist/chain/index.d.ts +2 -0
  4. package/dist/chain/plugin-not-imported.types.d.ts +40 -0
  5. package/dist/chain/plugin-not-imported.types.js +2 -0
  6. package/dist/chain/plugin-not-imported.types.mjs +1 -0
  7. package/dist/chain/slot-catalog.generated.d.ts +265 -0
  8. package/dist/chain/slot-catalog.generated.js +4 -0
  9. package/dist/chain/slot-catalog.generated.mjs +3 -0
  10. package/dist/core/type-erasure.d.ts +20 -0
  11. package/dist/core/type-erasure.js +23 -0
  12. package/dist/core/type-erasure.mjs +22 -0
  13. package/dist/field-rule/use-field.d.ts +2 -1
  14. package/dist/field-rule/use-field.js +9 -2
  15. package/dist/field-rule/use-field.mjs +9 -2
  16. package/dist/json-schema/array-keyword-guards.d.ts +31 -0
  17. package/dist/json-schema/array-keyword-guards.js +120 -0
  18. package/dist/json-schema/array-keyword-guards.mjs +114 -0
  19. package/dist/json-schema/assert-object-keyword-values.d.ts +59 -0
  20. package/dist/json-schema/assert-object-keyword-values.js +149 -0
  21. package/dist/json-schema/assert-object-keyword-values.mjs +141 -0
  22. package/dist/json-schema/collect-definitions.d.ts +7 -0
  23. package/dist/json-schema/collect-definitions.js +9 -0
  24. package/dist/json-schema/collect-definitions.mjs +10 -1
  25. package/dist/json-schema/declare-additional-properties.d.ts +11 -1
  26. package/dist/json-schema/declare-additional-properties.js +13 -1
  27. package/dist/json-schema/declare-additional-properties.mjs +13 -1
  28. package/dist/json-schema/declare-object-keywords.d.ts +4 -0
  29. package/dist/json-schema/declare-object-keywords.js +11 -4
  30. package/dist/json-schema/declare-object-keywords.mjs +11 -4
  31. package/dist/json-schema/declare-required-properties.js +6 -0
  32. package/dist/json-schema/declare-required-properties.mjs +6 -0
  33. package/dist/json-schema/declare-value-keywords.d.ts +10 -1
  34. package/dist/json-schema/declare-value-keywords.js +43 -4
  35. package/dist/json-schema/declare-value-keywords.mjs +43 -4
  36. package/dist/json-schema/extensions/json-schema/index.d.ts +1 -1
  37. package/dist/json-schema/extensions/json-schema/index.js +2 -1
  38. package/dist/json-schema/extensions/json-schema/index.mjs +1 -1
  39. package/dist/json-schema/flatten-array-schema.d.ts +11 -1
  40. package/dist/json-schema/flatten-array-schema.js +20 -7
  41. package/dist/json-schema/flatten-array-schema.mjs +20 -7
  42. package/dist/json-schema/index.d.ts +1 -0
  43. package/dist/json-schema/index.js +3 -1
  44. package/dist/json-schema/index.mjs +1 -0
  45. package/dist/json-schema/keyword-map-string.js +30 -1
  46. package/dist/json-schema/keyword-map-string.mjs +30 -1
  47. package/dist/json-schema/keyword-map.js +2 -0
  48. package/dist/json-schema/keyword-map.mjs +2 -0
  49. package/dist/json-schema/malformed-schema-error.d.ts +38 -0
  50. package/dist/json-schema/malformed-schema-error.js +103 -0
  51. package/dist/json-schema/malformed-schema-error.mjs +98 -0
  52. package/dist/plugins/manifest.generated.d.ts +18 -0
  53. package/dist/plugins/manifest.generated.js +77 -77
  54. package/dist/plugins/manifest.generated.mjs +77 -77
  55. package/dist/plugins/one-of/one-of.js +18 -4
  56. package/dist/plugins/one-of/one-of.mjs +18 -4
  57. package/dist/schema-tooling/index.d.ts +11 -0
  58. package/dist/schema-tooling/index.js +12 -1
  59. package/dist/schema-tooling/index.mjs +10 -0
  60. package/package.json +8 -6
@@ -8,4 +8,4 @@
8
8
  // a consumer of this subpath pays only for the plugins they import themselves.
9
9
  // ===========================================================================
10
10
  export { SCHEMA_BRANCH_LABEL, collectDocumentRules, jsonSchemaPlugin, } from "./json-schema.mjs";
11
- export { NotASchemaError, UnsupportedKeywordError, RefResolutionError, buildFieldEntries, buildFromSchema, fromJsonSchema, isDraft07Schema, listBoundPluginNames, listDraft07Keywords, listFormatNames, } from "../../index.mjs";
11
+ export { MalformedSchemaError, NotASchemaError, UnsupportedKeywordError, RefResolutionError, buildFieldEntries, buildFromSchema, fromJsonSchema, isDraft07Schema, listBoundPluginNames, listDraft07Keywords, listFormatNames, } from "../../index.mjs";
@@ -6,6 +6,10 @@ export declare const EACH_STEP = "[*]";
6
6
  /**
7
7
  * The single form only. The tuple form gets no child path: element 0 and
8
8
  * element 1 obey DIFFERENT schemas, and one declared `[*]` cannot say that.
9
+ *
10
+ * Reading through readCheckedItems is what stops a non-schema value here from
11
+ * becoming a child declaration whose sub-schema yields no rules at all; see
12
+ * array-keyword-guards.ts.
9
13
  */
10
14
  export declare function readItemChildren(schema: Draft07SchemaObject): readonly ChildSchema[];
11
15
  export declare function composeTupleItems(schema: Draft07SchemaObject, context: StructuralContext): readonly Rule[];
@@ -13,6 +17,10 @@ export declare function composeTupleItems(schema: Draft07SchemaObject, context:
13
17
  * `contains` is EXISTENTIAL: at least one element matches. arrayContains owns
14
18
  * that reduction, and its default bounds (min 1, no max) are exactly the
15
19
  * draft's, so this is one call and no arithmetic.
20
+ *
21
+ * An element branch holding no rules would satisfy the existence check on the
22
+ * first element and turn the keyword into "the array is non-empty", so the
23
+ * value is read through array-keyword-guards.ts.
16
24
  */
17
25
  export declare function composeContains(schema: Draft07SchemaObject, context: StructuralContext): readonly Rule[];
18
26
  /**
@@ -20,6 +28,8 @@ export declare function composeContains(schema: Draft07SchemaObject, context: St
20
28
  * and `maxItems` to `maxLength`; writing "minItems" as the method name does not
21
29
  * compile (keyword-map-array.ts), which is the C2 regression made impossible.
22
30
  * `uniqueItems: false` never reaches its binding: §6.4.3 gives it no effect and
23
- * the binding's value type is `true`.
31
+ * the binding's value type is `true`. That is why the value is read through
32
+ * readCheckedUniqueItems: with a bare `=== true`, a non-boolean would take the
33
+ * same silent route as the lawful `false` and lose the constraint entirely.
24
34
  */
25
35
  export declare function declareArrayRules(schema: Draft07SchemaObject, context: StructuralContext): readonly Rule[];
@@ -33,6 +33,7 @@ const create_chain_node_1 = require("../chain/create-chain-node");
33
33
  const create_field_slots_1 = require("../chain/create-field-slots");
34
34
  const create_rule_1 = require("../plugin-kit/create-rule");
35
35
  const apply_keyword_binding_1 = require("./apply-keyword-binding");
36
+ const array_keyword_guards_1 = require("./array-keyword-guards");
36
37
  const keyword_map_array_1 = require("./keyword-map-array");
37
38
  const types_1 = require("../types");
38
39
  const collect_definitions_1 = require("./collect-definitions");
@@ -47,9 +48,13 @@ function isTupleItems(items) {
47
48
  /**
48
49
  * The single form only. The tuple form gets no child path: element 0 and
49
50
  * element 1 obey DIFFERENT schemas, and one declared `[*]` cannot say that.
51
+ *
52
+ * Reading through readCheckedItems is what stops a non-schema value here from
53
+ * becoming a child declaration whose sub-schema yields no rules at all; see
54
+ * array-keyword-guards.ts.
50
55
  */
51
56
  function readItemChildren(schema) {
52
- const items = schema.items;
57
+ const items = (0, array_keyword_guards_1.readCheckedItems)(schema);
53
58
  if (items === undefined || isTupleItems(items))
54
59
  return NO_CHILDREN;
55
60
  return Object.freeze([{ step: exports.EACH_STEP, schema: items, isRequired: false }]);
@@ -58,7 +63,9 @@ function readItemChildren(schema) {
58
63
  * Element i against position i, extra elements against `additionalItems`.
59
64
  * A non-array passes, a short array passes, and `additionalItems: false`
60
65
  * becomes the schema that matches nothing (collect-definitions), so the
61
- * "no extra elements" case needs no fourth branch kind.
66
+ * "no extra elements" case needs no fourth branch kind. A rest branch built
67
+ * from a non-schema would carry no rules and check none of those elements,
68
+ * which is why the value is read through array-keyword-guards.ts.
62
69
  */
63
70
  function applyPositionally(runners, positionCount, hasRest) {
64
71
  return (value, ctx) => {
@@ -85,11 +92,11 @@ function describePosition(index) {
85
92
  : `Element ${String(index)} does not match the schema declared for it`;
86
93
  }
87
94
  function composeTupleItems(schema, context) {
88
- const items = schema.items;
95
+ const items = (0, array_keyword_guards_1.readCheckedItems)(schema);
89
96
  if (items === undefined || !isTupleItems(items))
90
97
  return NO_RULES;
91
98
  const branches = items.map((position, index) => context.toBranch(`position:${String(index)}`, position));
92
- const rest = schema.additionalItems;
99
+ const rest = (0, array_keyword_guards_1.readCheckedAdditionalItems)(schema);
93
100
  const hasRest = rest !== undefined;
94
101
  if (rest !== undefined)
95
102
  branches.push(context.toBranch("rest", rest));
@@ -109,9 +116,13 @@ function composeTupleItems(schema, context) {
109
116
  * `contains` is EXISTENTIAL: at least one element matches. arrayContains owns
110
117
  * that reduction, and its default bounds (min 1, no max) are exactly the
111
118
  * draft's, so this is one call and no arithmetic.
119
+ *
120
+ * An element branch holding no rules would satisfy the existence check on the
121
+ * first element and turn the keyword into "the array is non-empty", so the
122
+ * value is read through array-keyword-guards.ts.
112
123
  */
113
124
  function composeContains(schema, context) {
114
- const contains = schema.contains;
125
+ const contains = (0, array_keyword_guards_1.readCheckedContains)(schema);
115
126
  if (contains === undefined)
116
127
  return NO_RULES;
117
128
  const element = context.collectSubSchemaRules((0, collect_definitions_1.toSchemaObject)(contains));
@@ -124,7 +135,9 @@ function composeContains(schema, context) {
124
135
  * and `maxItems` to `maxLength`; writing "minItems" as the method name does not
125
136
  * compile (keyword-map-array.ts), which is the C2 regression made impossible.
126
137
  * `uniqueItems: false` never reaches its binding: §6.4.3 gives it no effect and
127
- * the binding's value type is `true`.
138
+ * the binding's value type is `true`. That is why the value is read through
139
+ * readCheckedUniqueItems: with a bare `=== true`, a non-boolean would take the
140
+ * same silent route as the lawful `false` and lose the constraint entirely.
128
141
  */
129
142
  function declareArrayRules(schema, context) {
130
143
  let chain = (0, create_field_slots_1.createFieldSlots)(context.bag, context.build).array;
@@ -134,7 +147,7 @@ function declareArrayRules(schema, context) {
134
147
  if (schema.maxItems !== undefined) {
135
148
  chain = (0, apply_keyword_binding_1.applyKeywordBinding)(chain, keyword_map_array_1.maxItemsBinding, schema.maxItems);
136
149
  }
137
- if (schema.uniqueItems === true) {
150
+ if ((0, array_keyword_guards_1.readCheckedUniqueItems)(schema) === true) {
138
151
  chain = (0, apply_keyword_binding_1.applyKeywordBinding)(chain, keyword_map_array_1.uniqueItemsBinding, true);
139
152
  }
140
153
  return (0, create_chain_node_1.readChainRules)(chain) ?? NO_RULES;
@@ -26,6 +26,7 @@ import { readChainRules } from "../chain/create-chain-node.mjs";
26
26
  import { createFieldSlots } from "../chain/create-field-slots.mjs";
27
27
  import { composite } from "../plugin-kit/create-rule.mjs";
28
28
  import { applyKeywordBinding } from "./apply-keyword-binding.mjs";
29
+ import { readCheckedAdditionalItems, readCheckedContains, readCheckedItems, readCheckedUniqueItems, } from "./array-keyword-guards.mjs";
29
30
  import { maxItemsBinding, minItemsBinding, uniqueItemsBinding, } from "./keyword-map-array.mjs";
30
31
  import { PASS, fail, isArray, isNumber } from "../types/index.mjs";
31
32
  import { toSchemaObject } from "./collect-definitions.mjs";
@@ -40,9 +41,13 @@ function isTupleItems(items) {
40
41
  /**
41
42
  * The single form only. The tuple form gets no child path: element 0 and
42
43
  * element 1 obey DIFFERENT schemas, and one declared `[*]` cannot say that.
44
+ *
45
+ * Reading through readCheckedItems is what stops a non-schema value here from
46
+ * becoming a child declaration whose sub-schema yields no rules at all; see
47
+ * array-keyword-guards.ts.
43
48
  */
44
49
  export function readItemChildren(schema) {
45
- const items = schema.items;
50
+ const items = readCheckedItems(schema);
46
51
  if (items === undefined || isTupleItems(items))
47
52
  return NO_CHILDREN;
48
53
  return Object.freeze([{ step: EACH_STEP, schema: items, isRequired: false }]);
@@ -51,7 +56,9 @@ export function readItemChildren(schema) {
51
56
  * Element i against position i, extra elements against `additionalItems`.
52
57
  * A non-array passes, a short array passes, and `additionalItems: false`
53
58
  * becomes the schema that matches nothing (collect-definitions), so the
54
- * "no extra elements" case needs no fourth branch kind.
59
+ * "no extra elements" case needs no fourth branch kind. A rest branch built
60
+ * from a non-schema would carry no rules and check none of those elements,
61
+ * which is why the value is read through array-keyword-guards.ts.
55
62
  */
56
63
  function applyPositionally(runners, positionCount, hasRest) {
57
64
  return (value, ctx) => {
@@ -78,11 +85,11 @@ function describePosition(index) {
78
85
  : `Element ${String(index)} does not match the schema declared for it`;
79
86
  }
80
87
  export function composeTupleItems(schema, context) {
81
- const items = schema.items;
88
+ const items = readCheckedItems(schema);
82
89
  if (items === undefined || !isTupleItems(items))
83
90
  return NO_RULES;
84
91
  const branches = items.map((position, index) => context.toBranch(`position:${String(index)}`, position));
85
- const rest = schema.additionalItems;
92
+ const rest = readCheckedAdditionalItems(schema);
86
93
  const hasRest = rest !== undefined;
87
94
  if (rest !== undefined)
88
95
  branches.push(context.toBranch("rest", rest));
@@ -102,9 +109,13 @@ export function composeTupleItems(schema, context) {
102
109
  * `contains` is EXISTENTIAL: at least one element matches. arrayContains owns
103
110
  * that reduction, and its default bounds (min 1, no max) are exactly the
104
111
  * draft's, so this is one call and no arithmetic.
112
+ *
113
+ * An element branch holding no rules would satisfy the existence check on the
114
+ * first element and turn the keyword into "the array is non-empty", so the
115
+ * value is read through array-keyword-guards.ts.
105
116
  */
106
117
  export function composeContains(schema, context) {
107
- const contains = schema.contains;
118
+ const contains = readCheckedContains(schema);
108
119
  if (contains === undefined)
109
120
  return NO_RULES;
110
121
  const element = context.collectSubSchemaRules(toSchemaObject(contains));
@@ -117,7 +128,9 @@ export function composeContains(schema, context) {
117
128
  * and `maxItems` to `maxLength`; writing "minItems" as the method name does not
118
129
  * compile (keyword-map-array.ts), which is the C2 regression made impossible.
119
130
  * `uniqueItems: false` never reaches its binding: §6.4.3 gives it no effect and
120
- * the binding's value type is `true`.
131
+ * the binding's value type is `true`. That is why the value is read through
132
+ * readCheckedUniqueItems: with a bare `=== true`, a non-boolean would take the
133
+ * same silent route as the lawful `false` and lose the constraint entirely.
121
134
  */
122
135
  export function declareArrayRules(schema, context) {
123
136
  let chain = createFieldSlots(context.bag, context.build).array;
@@ -127,7 +140,7 @@ export function declareArrayRules(schema, context) {
127
140
  if (schema.maxItems !== undefined) {
128
141
  chain = applyKeywordBinding(chain, maxItemsBinding, schema.maxItems);
129
142
  }
130
- if (schema.uniqueItems === true) {
143
+ if (readCheckedUniqueItems(schema) === true) {
131
144
  chain = applyKeywordBinding(chain, uniqueItemsBinding, true);
132
145
  }
133
146
  return readChainRules(chain) ?? NO_RULES;
@@ -17,6 +17,7 @@ export { createDocumentScope, createLocalScope } from "./ref-scope";
17
17
  export type { SchemaRegistry } from "./schema-registry";
18
18
  export { createSchemaRegistry } from "./schema-registry";
19
19
  export { UnsupportedKeywordError } from "./unsupported-keyword-error";
20
+ export { MalformedSchemaError } from "./malformed-schema-error";
20
21
  export { isDraft07Schema, isSchemaObject } from "./draft07.types";
21
22
  export type { Draft07Schema, Draft07SchemaObject, Draft07TypeKeyword, } from "./draft07.types";
22
23
  export { assertKeywordSupported, countKeywordHandlings, findKeywordHandling, isDraft07Keyword, listBoundPluginNames, listDraft07Keywords, } from "./keyword-map";
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.listFormatNames = exports.findFormatHandling = exports.listDraft07Keywords = exports.listBoundPluginNames = exports.isDraft07Keyword = exports.findKeywordHandling = exports.countKeywordHandlings = exports.assertKeywordSupported = exports.isSchemaObject = exports.isDraft07Schema = exports.UnsupportedKeywordError = exports.createSchemaRegistry = exports.createLocalScope = exports.createDocumentScope = exports.resolveRefInScope = exports.resolveRef = exports.isResolvableRef = exports.RefResolutionError = exports.declarePresenceRules = exports.declareRequiredProperties = exports.createStructuralContext = exports.toSchemaBranch = exports.collectSubSchemaRules = exports.readChildSchemas = exports.isStructuralKeyword = exports.expandSchemaRules = exports.STRUCTURAL_EXPANSIONS = exports.joinDeclaredPath = exports.flattenSchema = exports.toSchemaObject = exports.resolveSchemaNodeInScope = exports.resolveSchemaNode = exports.readRefPointer = exports.isDefinitionContainer = exports.collectDefinitionNames = exports.DEFINITION_CONTAINERS = exports.fromJsonSchema = exports.buildFromSchema = exports.buildFieldEntries = exports.NotASchemaError = void 0;
3
+ exports.listFormatNames = exports.findFormatHandling = exports.listDraft07Keywords = exports.listBoundPluginNames = exports.isDraft07Keyword = exports.findKeywordHandling = exports.countKeywordHandlings = exports.assertKeywordSupported = exports.isSchemaObject = exports.isDraft07Schema = exports.MalformedSchemaError = exports.UnsupportedKeywordError = exports.createSchemaRegistry = exports.createLocalScope = exports.createDocumentScope = exports.resolveRefInScope = exports.resolveRef = exports.isResolvableRef = exports.RefResolutionError = exports.declarePresenceRules = exports.declareRequiredProperties = exports.createStructuralContext = exports.toSchemaBranch = exports.collectSubSchemaRules = exports.readChildSchemas = exports.isStructuralKeyword = exports.expandSchemaRules = exports.STRUCTURAL_EXPANSIONS = exports.joinDeclaredPath = exports.flattenSchema = exports.toSchemaObject = exports.resolveSchemaNodeInScope = exports.resolveSchemaNode = exports.readRefPointer = exports.isDefinitionContainer = exports.collectDefinitionNames = exports.DEFINITION_CONTAINERS = exports.fromJsonSchema = exports.buildFromSchema = exports.buildFieldEntries = exports.NotASchemaError = void 0;
4
4
  // ===========================================================================
5
5
  // L8 src/json-schema/index.ts — re-exports only. Nothing is defined here.
6
6
  //
@@ -50,6 +50,8 @@ var schema_registry_1 = require("./schema-registry");
50
50
  Object.defineProperty(exports, "createSchemaRegistry", { enumerable: true, get: function () { return schema_registry_1.createSchemaRegistry; } });
51
51
  var unsupported_keyword_error_1 = require("./unsupported-keyword-error");
52
52
  Object.defineProperty(exports, "UnsupportedKeywordError", { enumerable: true, get: function () { return unsupported_keyword_error_1.UnsupportedKeywordError; } });
53
+ var malformed_schema_error_1 = require("./malformed-schema-error");
54
+ Object.defineProperty(exports, "MalformedSchemaError", { enumerable: true, get: function () { return malformed_schema_error_1.MalformedSchemaError; } });
53
55
  var draft07_types_1 = require("./draft07.types");
54
56
  Object.defineProperty(exports, "isDraft07Schema", { enumerable: true, get: function () { return draft07_types_1.isDraft07Schema; } });
55
57
  Object.defineProperty(exports, "isSchemaObject", { enumerable: true, get: function () { return draft07_types_1.isSchemaObject; } });
@@ -17,6 +17,7 @@ export { isResolvableRef, resolveRef, resolveRefInScope } from "./resolve-ref.mj
17
17
  export { createDocumentScope, createLocalScope } from "./ref-scope.mjs";
18
18
  export { createSchemaRegistry } from "./schema-registry.mjs";
19
19
  export { UnsupportedKeywordError } from "./unsupported-keyword-error.mjs";
20
+ export { MalformedSchemaError } from "./malformed-schema-error.mjs";
20
21
  export { isDraft07Schema, isSchemaObject } from "./draft07.types.mjs";
21
22
  export { assertKeywordSupported, countKeywordHandlings, findKeywordHandling, isDraft07Keyword, listBoundPluginNames, listDraft07Keywords, } from "./keyword-map.mjs";
22
23
  export { findFormatHandling, listFormatNames } from "./format-map.mjs";
@@ -10,7 +10,9 @@ exports.stringKeywordMap = exports.contentMediaTypeBinding = exports.contentEnco
10
10
  // table in this repository — 1.x shipped three of them and they disagreed
11
11
  // (docs/legacy-spec/json-schema-mapping.md records the disagreements).
12
12
  // ===========================================================================
13
+ const types_1 = require("../types");
13
14
  const bind_keyword_1 = require("./bind-keyword");
15
+ const malformed_schema_error_1 = require("./malformed-schema-error");
14
16
  const string_min_1 = require("../plugins/string-min");
15
17
  const string_max_1 = require("../plugins/string-max");
16
18
  const string_pattern_1 = require("../plugins/string-pattern");
@@ -18,13 +20,40 @@ const string_content_encoding_1 = require("../plugins/string-content-encoding");
18
20
  const string_content_media_type_1 = require("../plugins/string-content-media-type");
19
21
  exports.minLengthBinding = (0, bind_keyword_1.bindKeyword)("string", "min", string_min_1.stringMinPlugin, (v) => [v]);
20
22
  exports.maxLengthBinding = (0, bind_keyword_1.bindKeyword)("string", "max", string_max_1.stringMaxPlugin, (v) => [v]);
23
+ /**
24
+ * Compiles the document's source, refusing anything the meta-schema forbids.
25
+ *
26
+ * The parameter is `unknown` although the keyword's value type is `string`,
27
+ * and that is the whole point: compiling is a TOTAL operation on JSON values —
28
+ * every one of them has a string form — so a number becomes a literal digit
29
+ * pattern and an object becomes one that matches almost every string. Both
30
+ * then reach stringPattern as a perfectly valid RegExp, leaving its
31
+ * non-RegExp guard nothing to fire on. The refusal has to be here, before the
32
+ * value is turned into something well formed.
33
+ *
34
+ * A string that is not a valid ECMA-262 pattern is the same failure wearing a
35
+ * different coat: the compile step throws a SyntaxError naming neither the
36
+ * keyword nor the document, so it is caught and re-raised as the typed refusal
37
+ * every other malformed value gets.
38
+ */
39
+ function compilePattern(source) {
40
+ if (!(0, types_1.isString)(source)) {
41
+ throw new malformed_schema_error_1.MalformedSchemaError("pattern", "the value must be a string", source);
42
+ }
43
+ try {
44
+ return new RegExp(source);
45
+ }
46
+ catch {
47
+ throw new malformed_schema_error_1.MalformedSchemaError("pattern", "the value must be a valid ECMA-262 regular expression", source);
48
+ }
49
+ }
21
50
  /**
22
51
  * Draft-07 spells `pattern` as an ECMA-262 SOURCE string; the plugin accepts a
23
52
  * RegExp and only a RegExp, because 1.x's `new RegExp(source)` at the call site
24
53
  * dropped flags silently and gave the caller no compile-time check. Compiling
25
54
  * the schema's string therefore happens HERE, once, and nowhere else.
26
55
  */
27
- exports.patternBinding = (0, bind_keyword_1.bindKeyword)("string", "pattern", string_pattern_1.stringPatternPlugin, (v) => [new RegExp(v)]);
56
+ exports.patternBinding = (0, bind_keyword_1.bindKeyword)("string", "pattern", string_pattern_1.stringPatternPlugin, (v) => [compilePattern(v)]);
28
57
  /**
29
58
  * The value type is `ContentEncodingName`, not `string`: an encoding the
30
59
  * plugin does not know must be refused by the converter rather than accepted
@@ -7,7 +7,9 @@
7
7
  // table in this repository — 1.x shipped three of them and they disagreed
8
8
  // (docs/legacy-spec/json-schema-mapping.md records the disagreements).
9
9
  // ===========================================================================
10
+ import { isString } from "../types/index.mjs";
10
11
  import { bindKeyword, structural } from "./bind-keyword.mjs";
12
+ import { MalformedSchemaError } from "./malformed-schema-error.mjs";
11
13
  import { stringMinPlugin } from "../plugins/string-min/index.mjs";
12
14
  import { stringMaxPlugin } from "../plugins/string-max/index.mjs";
13
15
  import { stringPatternPlugin } from "../plugins/string-pattern/index.mjs";
@@ -15,13 +17,40 @@ import { stringContentEncodingPlugin, } from "../plugins/string-content-encoding
15
17
  import { stringContentMediaTypePlugin } from "../plugins/string-content-media-type/index.mjs";
16
18
  export const minLengthBinding = bindKeyword("string", "min", stringMinPlugin, (v) => [v]);
17
19
  export const maxLengthBinding = bindKeyword("string", "max", stringMaxPlugin, (v) => [v]);
20
+ /**
21
+ * Compiles the document's source, refusing anything the meta-schema forbids.
22
+ *
23
+ * The parameter is `unknown` although the keyword's value type is `string`,
24
+ * and that is the whole point: compiling is a TOTAL operation on JSON values —
25
+ * every one of them has a string form — so a number becomes a literal digit
26
+ * pattern and an object becomes one that matches almost every string. Both
27
+ * then reach stringPattern as a perfectly valid RegExp, leaving its
28
+ * non-RegExp guard nothing to fire on. The refusal has to be here, before the
29
+ * value is turned into something well formed.
30
+ *
31
+ * A string that is not a valid ECMA-262 pattern is the same failure wearing a
32
+ * different coat: the compile step throws a SyntaxError naming neither the
33
+ * keyword nor the document, so it is caught and re-raised as the typed refusal
34
+ * every other malformed value gets.
35
+ */
36
+ function compilePattern(source) {
37
+ if (!isString(source)) {
38
+ throw new MalformedSchemaError("pattern", "the value must be a string", source);
39
+ }
40
+ try {
41
+ return new RegExp(source);
42
+ }
43
+ catch {
44
+ throw new MalformedSchemaError("pattern", "the value must be a valid ECMA-262 regular expression", source);
45
+ }
46
+ }
18
47
  /**
19
48
  * Draft-07 spells `pattern` as an ECMA-262 SOURCE string; the plugin accepts a
20
49
  * RegExp and only a RegExp, because 1.x's `new RegExp(source)` at the call site
21
50
  * dropped flags silently and gave the caller no compile-time check. Compiling
22
51
  * the schema's string therefore happens HERE, once, and nowhere else.
23
52
  */
24
- export const patternBinding = bindKeyword("string", "pattern", stringPatternPlugin, (v) => [new RegExp(v)]);
53
+ export const patternBinding = bindKeyword("string", "pattern", stringPatternPlugin, (v) => [compilePattern(v)]);
25
54
  /**
26
55
  * The value type is `ContentEncodingName`, not `string`: an encoding the
27
56
  * plugin does not know must be refused by the converter rather than accepted
@@ -35,6 +35,8 @@ exports.NON_DRAFT07_KEYWORDS = {
35
35
  $recursiveRef: "2019-09 recursive references",
36
36
  dependentRequired: "2019-09 split of `dependencies`; use `dependencies`",
37
37
  dependentSchemas: "2019-09 split of `dependencies`; use `dependencies`",
38
+ minContains: "2019-09 bound on `contains`; Draft-07's is fixed at one",
39
+ maxContains: "2019-09 bound on `contains`; Draft-07 states no maximum",
38
40
  unevaluatedItems: "2019-09; no annotation-collection model in Luq",
39
41
  unevaluatedProperties: "2019-09; no annotation-collection model in Luq",
40
42
  contentSchema: "2019-09 companion to contentMediaType",
@@ -26,6 +26,8 @@ export const NON_DRAFT07_KEYWORDS = {
26
26
  $recursiveRef: "2019-09 recursive references",
27
27
  dependentRequired: "2019-09 split of `dependencies`; use `dependencies`",
28
28
  dependentSchemas: "2019-09 split of `dependencies`; use `dependencies`",
29
+ minContains: "2019-09 bound on `contains`; Draft-07's is fixed at one",
30
+ maxContains: "2019-09 bound on `contains`; Draft-07 states no maximum",
29
31
  unevaluatedItems: "2019-09; no annotation-collection model in Luq",
30
32
  unevaluatedProperties: "2019-09; no annotation-collection model in Luq",
31
33
  contentSchema: "2019-09 companion to contentMediaType",
@@ -0,0 +1,38 @@
1
+ /**
2
+ * The two shapes §4.4 lets a schema take, worded once. Several keywords take a
3
+ * schema as their value — `properties` members, `additionalProperties`, every
4
+ * position of the tuple form of `items` — and each is read by a different
5
+ * module; sharing the phrase is what stops the same refusal being explained
6
+ * three different ways to the same caller.
7
+ *
8
+ * It is a fragment, so it composes: "the value ", `the schema under "a" `.
9
+ */
10
+ export declare const SCHEMA_FORMS = "must be an object or a boolean";
11
+ /**
12
+ * Renders an untrusted schema value for a message, in at most
13
+ * RECEIVED_MAX_LENGTH characters. The value comes from the same document that
14
+ * is already known to be malformed, so nothing about it can be assumed: it may
15
+ * be circular, hold a bigint, or be large enough to drown the message.
16
+ * Rendering must never be the thing that throws.
17
+ */
18
+ export declare function renderReceivedValue(value: unknown): string;
19
+ /**
20
+ * Raised when a keyword's value violates the Draft-07 meta-schema. Thrown at
21
+ * build time, before a validator that would enforce less than the document
22
+ * states can exist.
23
+ */
24
+ export declare class MalformedSchemaError extends Error {
25
+ /** The keyword whose value is malformed, for instance "type". */
26
+ readonly keyword: string;
27
+ /**
28
+ * What the meta-schema REQUIRES, written as a sentence fragment with no
29
+ * terminal punctuation — the message supplies it. Every keyword phrases it
30
+ * the same way, "<the thing> must be <the shape>", so that a caller reading
31
+ * two refusals from two different keywords reads one library rather than
32
+ * three; what was found instead is carried separately, in `received`.
33
+ */
34
+ readonly reason: string;
35
+ /** The offending value, rendered short and safe by renderReceivedValue. */
36
+ readonly received: string;
37
+ constructor(keyword: string, reason: string, value: unknown);
38
+ }
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ // ===========================================================================
3
+ // L8 src/json-schema/malformed-schema-error.ts
4
+ //
5
+ // UnsupportedKeywordError refuses a keyword NAME Luq cannot honour. This
6
+ // refuses a keyword VALUE the Draft-07 meta-schema does not allow, which until
7
+ // it is refused does not fail — it quietly enforces less than the document
8
+ // says. `{"type":"strig"}` drops the unknown name, so the type check vanishes
9
+ // and the field starts accepting null; `{"pattern":{"source":"^SKU-"}}` reaches
10
+ // the regex constructor as a stringified object and compiles to one matching
11
+ // almost anything; `{"additionalProperties":"false"}` loses the closed-object
12
+ // guarantee; `{"required":"name"}` builds and then throws a raw TypeError
13
+ // inside validate() on the first request. A validator that silently drops a
14
+ // constraint is worse than one that refuses the schema, so the refusal is a
15
+ // typed error carrying the keyword, what the meta-schema requires, and a
16
+ // rendering of what was found instead.
17
+ //
18
+ // The two refusals stay separate classes, neither extending the other, because
19
+ // they answer different questions: "Luq cannot honour this keyword" versus
20
+ // "this document is not valid Draft-07". The first is a limit of the library
21
+ // and the second is a bug in the document; a caller catching one must not
22
+ // silently catch the other.
23
+ // ===========================================================================
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ exports.MalformedSchemaError = exports.SCHEMA_FORMS = void 0;
26
+ exports.renderReceivedValue = renderReceivedValue;
27
+ /**
28
+ * The two shapes §4.4 lets a schema take, worded once. Several keywords take a
29
+ * schema as their value — `properties` members, `additionalProperties`, every
30
+ * position of the tuple form of `items` — and each is read by a different
31
+ * module; sharing the phrase is what stops the same refusal being explained
32
+ * three different ways to the same caller.
33
+ *
34
+ * It is a fragment, so it composes: "the value ", `the schema under "a" `.
35
+ */
36
+ exports.SCHEMA_FORMS = "must be an object or a boolean";
37
+ /**
38
+ * How much of the offending value a message may carry. A schema node is
39
+ * arbitrarily large, and an error whose message is a megabyte long is unusable
40
+ * in a log line.
41
+ */
42
+ const RECEIVED_MAX_LENGTH = 80;
43
+ const TRUNCATION_MARKER = "...";
44
+ /**
45
+ * Renders an untrusted schema value for a message, in at most
46
+ * RECEIVED_MAX_LENGTH characters. The value comes from the same document that
47
+ * is already known to be malformed, so nothing about it can be assumed: it may
48
+ * be circular, hold a bigint, or be large enough to drown the message.
49
+ * Rendering must never be the thing that throws.
50
+ */
51
+ function renderReceivedValue(value) {
52
+ if (value === undefined)
53
+ return "undefined";
54
+ if (typeof value === "function")
55
+ return "[function]";
56
+ if (typeof value === "symbol")
57
+ return "[symbol]";
58
+ return truncate(renderJson(value));
59
+ }
60
+ /**
61
+ * JSON.stringify throws on a circular reference and on a bigint anywhere in
62
+ * the value, and returns undefined for a value it has no encoding for at all.
63
+ * Both outcomes become a description of the shape rather than a failure.
64
+ */
65
+ function renderJson(value) {
66
+ try {
67
+ const rendered = JSON.stringify(value);
68
+ return rendered === undefined ? describeUnrenderable(value) : rendered;
69
+ }
70
+ catch {
71
+ return describeUnrenderable(value);
72
+ }
73
+ }
74
+ function describeUnrenderable(value) {
75
+ return Array.isArray(value)
76
+ ? "[unrenderable array]"
77
+ : `[unrenderable ${typeof value}]`;
78
+ }
79
+ function truncate(rendered) {
80
+ if (rendered.length <= RECEIVED_MAX_LENGTH)
81
+ return rendered;
82
+ const kept = RECEIVED_MAX_LENGTH - TRUNCATION_MARKER.length;
83
+ return `${rendered.slice(0, kept)}${TRUNCATION_MARKER}`;
84
+ }
85
+ /**
86
+ * Raised when a keyword's value violates the Draft-07 meta-schema. Thrown at
87
+ * build time, before a validator that would enforce less than the document
88
+ * states can exist.
89
+ */
90
+ class MalformedSchemaError extends Error {
91
+ constructor(keyword, reason, value) {
92
+ const received = renderReceivedValue(value);
93
+ super(`JSON Schema keyword "${keyword}" has a value the Draft-07 meta-schema ` +
94
+ `does not allow: ${reason}. Received: ${received}.`);
95
+ this.name = "MalformedSchemaError";
96
+ this.keyword = keyword;
97
+ this.reason = reason;
98
+ this.received = received;
99
+ // Without this, `instanceof` fails when the package is compiled to ES5.
100
+ Object.setPrototypeOf(this, MalformedSchemaError.prototype);
101
+ }
102
+ }
103
+ exports.MalformedSchemaError = MalformedSchemaError;
@@ -0,0 +1,98 @@
1
+ // ===========================================================================
2
+ // L8 src/json-schema/malformed-schema-error.ts
3
+ //
4
+ // UnsupportedKeywordError refuses a keyword NAME Luq cannot honour. This
5
+ // refuses a keyword VALUE the Draft-07 meta-schema does not allow, which until
6
+ // it is refused does not fail — it quietly enforces less than the document
7
+ // says. `{"type":"strig"}` drops the unknown name, so the type check vanishes
8
+ // and the field starts accepting null; `{"pattern":{"source":"^SKU-"}}` reaches
9
+ // the regex constructor as a stringified object and compiles to one matching
10
+ // almost anything; `{"additionalProperties":"false"}` loses the closed-object
11
+ // guarantee; `{"required":"name"}` builds and then throws a raw TypeError
12
+ // inside validate() on the first request. A validator that silently drops a
13
+ // constraint is worse than one that refuses the schema, so the refusal is a
14
+ // typed error carrying the keyword, what the meta-schema requires, and a
15
+ // rendering of what was found instead.
16
+ //
17
+ // The two refusals stay separate classes, neither extending the other, because
18
+ // they answer different questions: "Luq cannot honour this keyword" versus
19
+ // "this document is not valid Draft-07". The first is a limit of the library
20
+ // and the second is a bug in the document; a caller catching one must not
21
+ // silently catch the other.
22
+ // ===========================================================================
23
+ /**
24
+ * The two shapes §4.4 lets a schema take, worded once. Several keywords take a
25
+ * schema as their value — `properties` members, `additionalProperties`, every
26
+ * position of the tuple form of `items` — and each is read by a different
27
+ * module; sharing the phrase is what stops the same refusal being explained
28
+ * three different ways to the same caller.
29
+ *
30
+ * It is a fragment, so it composes: "the value ", `the schema under "a" `.
31
+ */
32
+ export const SCHEMA_FORMS = "must be an object or a boolean";
33
+ /**
34
+ * How much of the offending value a message may carry. A schema node is
35
+ * arbitrarily large, and an error whose message is a megabyte long is unusable
36
+ * in a log line.
37
+ */
38
+ const RECEIVED_MAX_LENGTH = 80;
39
+ const TRUNCATION_MARKER = "...";
40
+ /**
41
+ * Renders an untrusted schema value for a message, in at most
42
+ * RECEIVED_MAX_LENGTH characters. The value comes from the same document that
43
+ * is already known to be malformed, so nothing about it can be assumed: it may
44
+ * be circular, hold a bigint, or be large enough to drown the message.
45
+ * Rendering must never be the thing that throws.
46
+ */
47
+ export function renderReceivedValue(value) {
48
+ if (value === undefined)
49
+ return "undefined";
50
+ if (typeof value === "function")
51
+ return "[function]";
52
+ if (typeof value === "symbol")
53
+ return "[symbol]";
54
+ return truncate(renderJson(value));
55
+ }
56
+ /**
57
+ * JSON.stringify throws on a circular reference and on a bigint anywhere in
58
+ * the value, and returns undefined for a value it has no encoding for at all.
59
+ * Both outcomes become a description of the shape rather than a failure.
60
+ */
61
+ function renderJson(value) {
62
+ try {
63
+ const rendered = JSON.stringify(value);
64
+ return rendered === undefined ? describeUnrenderable(value) : rendered;
65
+ }
66
+ catch {
67
+ return describeUnrenderable(value);
68
+ }
69
+ }
70
+ function describeUnrenderable(value) {
71
+ return Array.isArray(value)
72
+ ? "[unrenderable array]"
73
+ : `[unrenderable ${typeof value}]`;
74
+ }
75
+ function truncate(rendered) {
76
+ if (rendered.length <= RECEIVED_MAX_LENGTH)
77
+ return rendered;
78
+ const kept = RECEIVED_MAX_LENGTH - TRUNCATION_MARKER.length;
79
+ return `${rendered.slice(0, kept)}${TRUNCATION_MARKER}`;
80
+ }
81
+ /**
82
+ * Raised when a keyword's value violates the Draft-07 meta-schema. Thrown at
83
+ * build time, before a validator that would enforce less than the document
84
+ * states can exist.
85
+ */
86
+ export class MalformedSchemaError extends Error {
87
+ constructor(keyword, reason, value) {
88
+ const received = renderReceivedValue(value);
89
+ super(`JSON Schema keyword "${keyword}" has a value the Draft-07 meta-schema ` +
90
+ `does not allow: ${reason}. Received: ${received}.`);
91
+ this.name = "MalformedSchemaError";
92
+ this.keyword = keyword;
93
+ this.reason = reason;
94
+ this.received = received;
95
+ // Without this, `instanceof` fails when the package is compiled to ES5.
96
+ Object.setPrototypeOf(this, MalformedSchemaError.prototype);
97
+ }
98
+ }
@@ -1,8 +1,26 @@
1
+ /** One plugin symbol: the method it adds, and the slots it adds it to. */
2
+ export interface PluginSurface {
3
+ /** The symbol to pass to `.use()`, e.g. "stringMinPlugin". */
4
+ readonly symbol: string;
5
+ /** The chain method it adds, e.g. "min". */
6
+ readonly method: string;
7
+ /** The slots that method appears on, e.g. ["string"]. */
8
+ readonly slots: readonly string[];
9
+ }
1
10
  export interface PluginManifestEntry {
2
11
  readonly directoryName: string;
3
12
  readonly subpathName: string;
4
13
  readonly tier: "isolated" | "extension";
14
+ /**
15
+ * Where the plugin lives in the REPOSITORY. A source path, so it resolves
16
+ * only in a checkout; an installed package has no src directory. What a
17
+ * consumer writes instead is `entryPoint`.
18
+ */
5
19
  readonly entryFile: string;
20
+ /** The specifier a consumer writes, e.g. "@maroonedog/luq/plugins/stringMin". */
21
+ readonly entryPoint: string;
6
22
  readonly exportedSymbols: readonly string[];
23
+ /** One per exported symbol, in the same order. */
24
+ readonly surfaces: readonly PluginSurface[];
7
25
  }
8
26
  export declare const PLUGIN_MANIFEST: readonly PluginManifestEntry[];