@jarenjs/validate 0.34.2 → 0.43.3

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/ARCHITECTURE.md CHANGED
@@ -955,7 +955,7 @@ Map<string, ValidationObject> {
955
955
 
956
956
  When schemas have `$id` that changes the base URI:
957
957
 
958
- ```json
958
+ ```jsonc
959
959
  {
960
960
  "$id": "http://example.com/schema",
961
961
  "$defs": {
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @jarenjs/validate
2
2
 
3
- The JSON Schema validating compiler at the heart of [Jaren](https://github.com/jklarenbeek/jarenjs). It compiles JSON Schemas into optimized validation functions and fully supports `draft-06`, `draft-07`, `draft 2019-09` and `draft 2020-12` — passing 100% of the official [JSON-Schema-Test-Suite](https://github.com/json-schema-org/JSON-Schema-Test-Suite) for draft-07, 2019-09 and 2020-12.
3
+ The JSON Schema validating compiler at the heart of [Jaren](https://github.com/jklarenbeek/jarenjs). It compiles JSON Schemas into optimized validation functions and fully supports `draft-06`, `draft-07`, `draft 2019-09` and `draft 2020-12` — passing the official [JSON-Schema-Test-Suite](https://github.com/json-schema-org/JSON-Schema-Test-Suite) for draft-07 and 2019-09 in full, and all of 2020-12 except the `$dynamicRef` cases the repository's roadmap names.
4
4
 
5
5
  ## Usage
6
6
 
@@ -330,6 +330,7 @@ export type JSONSchemaKeywords = {
330
330
  data?: DataKeywordSchema;
331
331
  };
332
332
  export type JSONSchema = JSONSchemaKeywords & Record<string, unknown>;
333
+ export type JSONSchemaLike = JSONSchema | Record<string, unknown>;
333
334
  export type FormatCompiler = (schemaObj: ValidationObject, jsonSchema: JSONSchema & {
334
335
  format?: string;
335
336
  }) => ((data: unknown, dataPath?: string) => boolean) | undefined;
@@ -447,6 +448,18 @@ export type FormatCompiler = (schemaObj: ValidationObject, jsonSchema: JSONSchem
447
448
  * forms accept everything (`true`) or nothing (`false`).
448
449
  * @typedef {JSONSchemaKeywords & Record<string, unknown>} JSONSchema
449
450
  */
451
+ /**
452
+ * A schema as a validator boundary ACCEPTS it. {@link JSONSchema}
453
+ * documents what a schema IS — typed keywords, open for extensions —
454
+ * but a schema held as a plain `Record<string, unknown>` map (the
455
+ * natural type for a document that crossed a wire or a package
456
+ * boundary) does not assign to that keyword intersection under strict
457
+ * TypeScript, and forcing the consumer to cast at every `compile` and
458
+ * `addSchema` call site teaches them to cast, which is worse than the
459
+ * looser parameter. The boundary methods accept this union; both arms
460
+ * are treated identically at runtime, which was always true.
461
+ * @typedef {JSONSchema | Record<string, unknown>} JSONSchemaLike
462
+ */
450
463
  /**
451
464
  * A format compiler function.
452
465
  * Called once per schema location at compile time with the compiling
@@ -893,7 +906,7 @@ export declare class JarenValidator<TCollect extends boolean = false> {
893
906
  * Adds schema(s) to the validator instance.
894
907
  * This method does not compile schemas - it only registers them for reference.
895
908
  * Dependencies can be added in any order, and circular dependencies are supported.
896
- * @param {JSONSchema | boolean | (JSONSchema | boolean)[]} schema - The schema(s) to add
909
+ * @param {JSONSchemaLike | boolean | (JSONSchemaLike | boolean)[]} schema - The schema(s) to add
897
910
  * @param {string} [key] - Optional key/URI to register the schema under
898
911
  * @returns {this} This validator instance for chaining (the polymorphic `this` keeps the collectErrors type parameter across a chain)
899
912
  * @example
@@ -906,18 +919,18 @@ export declare class JarenValidator<TCollect extends boolean = false> {
906
919
  * // Add with explicit key
907
920
  * validator.addSchema({ type: 'string' }, 'http://example.com/name');
908
921
  */
909
- addSchema(schema: JSONSchema | boolean | (JSONSchema | boolean)[], key?: string): this;
922
+ addSchema(schema: JSONSchemaLike | boolean | (JSONSchemaLike | boolean)[], key?: string): this;
910
923
  static normalizeUriKey(key: any): any;
911
924
  /**
912
925
  * Adds meta-schema(s) that can be used to validate schemas.
913
926
  * Meta-schemas are schemas that describe the structure of valid JSON schemas.
914
- * @param {JSONSchema | boolean | (JSONSchema | boolean)[]} schema - The meta-schema(s) to add
927
+ * @param {JSONSchemaLike | boolean | (JSONSchemaLike | boolean)[]} schema - The meta-schema(s) to add
915
928
  * @param {string} [key] - Optional key/URI for the meta-schema
916
929
  * @returns {this} This validator instance for chaining (the polymorphic `this` keeps the collectErrors type parameter across a chain)
917
930
  * @example
918
931
  * validator.addMetaSchema(draft7MetaSchema, 'http://json-schema.org/draft-07/schema');
919
932
  */
920
- addMetaSchema(schema: JSONSchema | boolean | (JSONSchema | boolean)[], key?: string): this;
933
+ addMetaSchema(schema: JSONSchemaLike | boolean | (JSONSchemaLike | boolean)[], key?: string): this;
921
934
  /**
922
935
  * Retrieves a registered schema by its key/URI.
923
936
  * @param {string} key - The schema URI/key
@@ -927,13 +940,13 @@ export declare class JarenValidator<TCollect extends boolean = false> {
927
940
  /**
928
941
  * Validates a schema against a registered meta-schema.
929
942
  * This is used to ensure schemas are valid according to the JSON Schema specification.
930
- * @param {JSONSchema | boolean} schema - The schema to validate
943
+ * @param {JSONSchemaLike | boolean} schema - The schema to validate
931
944
  * @returns {boolean} True if the schema is valid
932
945
  * @example
933
946
  * validator.addMetaSchema(draft7MetaSchema);
934
947
  * const isValid = validator.validateSchema({ type: 'string' }); // true
935
948
  */
936
- validateSchema(schema: JSONSchema | boolean): boolean;
949
+ validateSchema(schema: JSONSchemaLike | boolean): boolean;
937
950
  /**
938
951
  * Compiles a schema into a validation function.
939
952
  * This is the main method for creating validators. It resolves all $ref references,
@@ -945,8 +958,8 @@ export declare class JarenValidator<TCollect extends boolean = false> {
945
958
  * what a checked contract wrapper wants; pair it with a schema-to-type
946
959
  * generator if you need the shape derived mechanically.
947
960
  * @template [T=unknown]
948
- * @param {JSONSchema | boolean} schema - The schema to compile
949
- * @param {(JSONSchema | boolean)[]} [schemas] - Additional schemas to reference during compilation
961
+ * @param {JSONSchemaLike | boolean} schema - The schema to compile
962
+ * @param {(JSONSchemaLike | boolean)[]} [schemas] - Additional schemas to reference during compilation
950
963
  * @returns {TCollect extends true ? CompiledCollector : CompiledPredicate<T>} A validation function
951
964
  * @example
952
965
  * const validate = validator.compile({
@@ -968,5 +981,5 @@ export declare class JarenValidator<TCollect extends boolean = false> {
968
981
  * const result = collecting.compile(schema)({ name: 123 });
969
982
  * // result = { valid: false, errors: [...] }
970
983
  */
971
- compile<T = unknown>(schema: JSONSchema | boolean, schemas?: (JSONSchema | boolean)[]): TCollect extends true ? CompiledCollector : CompiledPredicate<T>;
984
+ compile<T = unknown>(schema: JSONSchemaLike | boolean, schemas?: (JSONSchemaLike | boolean)[]): TCollect extends true ? CompiledCollector : CompiledPredicate<T>;
972
985
  }
@@ -14,6 +14,22 @@ export declare function isOfSchemaType(schema: any, type: any): boolean;
14
14
  export declare function hasSchemaRef(schema: any): boolean;
15
15
  export declare function hasSchemaRecursiveRef(schema: any): boolean;
16
16
  export declare function hasSchemaDynamicRef(schema: any): boolean;
17
+ /**
18
+ * The keywords that assert something beside a `$ref`: the siblings draft
19
+ * 2019-09+ applies alongside the reference and draft-07 ignores.
20
+ * Membership-only (order-insensitive): the shared constraint groups plus
21
+ * the applicators and extras. One list, read by the schema compiler (does
22
+ * this `$ref` carry siblings to compile?) and by the reference resolver
23
+ * (may this hop of a `$ref` chain be flattened away?).
24
+ */
25
+ export declare const REF_SIBLING_KEYWORDS: readonly string[];
26
+ /**
27
+ * Whether a schema carrying a `$ref` also carries a keyword of
28
+ * {@link REF_SIBLING_KEYWORDS}.
29
+ * @param {object} schema
30
+ * @returns {boolean}
31
+ */
32
+ export declare function hasRefSiblings(schema: object): boolean;
17
33
  /**
18
34
  * Whether a sibling keyword of unevaluatedProperties already evaluates every
19
35
  * property of the instance. additionalProperties (boolean or schema) applies
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@jarenjs/validate",
3
3
  "private": false,
4
- "version": "0.34.2",
4
+ "version": "0.43.3",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
7
7
  "types": "./dist/types/index.d.ts",
@@ -55,7 +55,7 @@
55
55
  "prepack": "npm run build:types"
56
56
  },
57
57
  "dependencies": {
58
- "@jarenjs/core": "^0.34.2",
59
- "@jarenjs/json": "^0.34.2"
58
+ "@jarenjs/core": "^0.43.3",
59
+ "@jarenjs/json": "^0.43.3"
60
60
  }
61
61
  }
package/src/index.js CHANGED
@@ -168,6 +168,19 @@ export { TraverseOptions };
168
168
  * @typedef {JSONSchemaKeywords & Record<string, unknown>} JSONSchema
169
169
  */
170
170
 
171
+ /**
172
+ * A schema as a validator boundary ACCEPTS it. {@link JSONSchema}
173
+ * documents what a schema IS — typed keywords, open for extensions —
174
+ * but a schema held as a plain `Record<string, unknown>` map (the
175
+ * natural type for a document that crossed a wire or a package
176
+ * boundary) does not assign to that keyword intersection under strict
177
+ * TypeScript, and forcing the consumer to cast at every `compile` and
178
+ * `addSchema` call site teaches them to cast, which is worse than the
179
+ * looser parameter. The boundary methods accept this union; both arms
180
+ * are treated identically at runtime, which was always true.
181
+ * @typedef {JSONSchema | Record<string, unknown>} JSONSchemaLike
182
+ */
183
+
171
184
  /**
172
185
  * A format compiler function.
173
186
  * Called once per schema location at compile time with the compiling
@@ -1420,7 +1433,7 @@ export class JarenValidator {
1420
1433
  * Adds schema(s) to the validator instance.
1421
1434
  * This method does not compile schemas - it only registers them for reference.
1422
1435
  * Dependencies can be added in any order, and circular dependencies are supported.
1423
- * @param {JSONSchema | boolean | (JSONSchema | boolean)[]} schema - The schema(s) to add
1436
+ * @param {JSONSchemaLike | boolean | (JSONSchemaLike | boolean)[]} schema - The schema(s) to add
1424
1437
  * @param {string} [key] - Optional key/URI to register the schema under
1425
1438
  * @returns {this} This validator instance for chaining (the polymorphic `this` keeps the collectErrors type parameter across a chain)
1426
1439
  * @example
@@ -1569,7 +1582,7 @@ export class JarenValidator {
1569
1582
  /**
1570
1583
  * Adds meta-schema(s) that can be used to validate schemas.
1571
1584
  * Meta-schemas are schemas that describe the structure of valid JSON schemas.
1572
- * @param {JSONSchema | boolean | (JSONSchema | boolean)[]} schema - The meta-schema(s) to add
1585
+ * @param {JSONSchemaLike | boolean | (JSONSchemaLike | boolean)[]} schema - The meta-schema(s) to add
1573
1586
  * @param {string} [key] - Optional key/URI for the meta-schema
1574
1587
  * @returns {this} This validator instance for chaining (the polymorphic `this` keeps the collectErrors type parameter across a chain)
1575
1588
  * @example
@@ -1623,7 +1636,7 @@ export class JarenValidator {
1623
1636
  /**
1624
1637
  * Validates a schema against a registered meta-schema.
1625
1638
  * This is used to ensure schemas are valid according to the JSON Schema specification.
1626
- * @param {JSONSchema | boolean} schema - The schema to validate
1639
+ * @param {JSONSchemaLike | boolean} schema - The schema to validate
1627
1640
  * @returns {boolean} True if the schema is valid
1628
1641
  * @example
1629
1642
  * validator.addMetaSchema(draft7MetaSchema);
@@ -1806,8 +1819,8 @@ export class JarenValidator {
1806
1819
  * what a checked contract wrapper wants; pair it with a schema-to-type
1807
1820
  * generator if you need the shape derived mechanically.
1808
1821
  * @template [T=unknown]
1809
- * @param {JSONSchema | boolean} schema - The schema to compile
1810
- * @param {(JSONSchema | boolean)[]} [schemas] - Additional schemas to reference during compilation
1822
+ * @param {JSONSchemaLike | boolean} schema - The schema to compile
1823
+ * @param {(JSONSchemaLike | boolean)[]} [schemas] - Additional schemas to reference during compilation
1811
1824
  * @returns {TCollect extends true ? CompiledCollector : CompiledPredicate<T>} A validation function
1812
1825
  * @example
1813
1826
  * const validate = validator.compile({
package/src/schema.js CHANGED
@@ -5,10 +5,6 @@ import {
5
5
  getStringType,
6
6
  isObjectClass,
7
7
  } from '@jarenjs/core';
8
- import {
9
- NUMERIC_CONSTRAINTS, STRING_CONSTRAINTS,
10
- ARRAY_CONSTRAINTS, OBJECT_CONSTRAINTS,
11
- } from '@jarenjs/core/schema';
12
8
 
13
9
  import {
14
10
  getUniqueArray,
@@ -29,6 +25,7 @@ import {
29
25
  hasSchemaRef,
30
26
  hasSchemaRecursiveRef,
31
27
  hasSchemaDynamicRef,
28
+ hasRefSiblings,
32
29
  } from './tools.js';
33
30
 
34
31
  import {
@@ -453,18 +450,10 @@ export function compileSchemaObject(schemaObj, jsonSchema) {
453
450
  let refWithSiblings = false;
454
451
  if (hasSchemaRef(jsonSchema) && !hasSchemaRecursiveRef(jsonSchema)) {
455
452
  // Check if there are any validation-related sibling keywords
456
- // In draft 2019-09+, if there are validation siblings, we process
457
- // them together. Membership-only (order-insensitive): the shared
458
- // constraint groups plus the applicators and extras.
459
- const validationKeywords = ['type', 'const', 'enum',
460
- ...NUMERIC_CONSTRAINTS, ...STRING_CONSTRAINTS,
461
- ...ARRAY_CONSTRAINTS, 'maxContains', 'minContains',
462
- ...OBJECT_CONSTRAINTS, 'required',
463
- 'dependentRequired', 'properties', 'patternProperties', 'additionalProperties', 'items',
464
- 'prefixItems', 'additionalItems', 'contains', 'allOf', 'anyOf', 'oneOf', 'not', 'if',
465
- 'then', 'else', 'propertyNames', 'contentEncoding', 'contentMediaType',
466
- 'unevaluatedProperties', 'unevaluatedItems', '$query'];
467
- const hasValidationSiblings = keys.some(k => validationKeywords.includes(k));
453
+ // (REF_SIBLING_KEYWORDS the one list the reference resolver reads
454
+ // too). In draft 2019-09+, if there are validation siblings, we
455
+ // process them together.
456
+ const hasValidationSiblings = hasRefSiblings(jsonSchema);
468
457
  // In draft 7 and earlier, $ref always overrides siblings regardless
469
458
  // In draft 2019-09+, $ref can have validation siblings
470
459
  if (!hasValidationSiblings || draftVersion < 2019) {
package/src/tools.js CHANGED
@@ -31,6 +31,11 @@ import {
31
31
  JSONPOINTER_NOTHING,
32
32
  } from '@jarenjs/json';
33
33
 
34
+ import {
35
+ NUMERIC_CONSTRAINTS, STRING_CONSTRAINTS,
36
+ ARRAY_CONSTRAINTS, OBJECT_CONSTRAINTS,
37
+ } from '@jarenjs/core/schema';
38
+
34
39
  //#region Object
35
40
  export function isBoolOrObjectClass(obj) {
36
41
  return isBooleanType(obj)
@@ -85,6 +90,37 @@ export function hasSchemaDynamicRef(schema) {
85
90
  && !isStringWhiteSpace(schema.$dynamicRef);
86
91
  }
87
92
 
93
+ /**
94
+ * The keywords that assert something beside a `$ref`: the siblings draft
95
+ * 2019-09+ applies alongside the reference and draft-07 ignores.
96
+ * Membership-only (order-insensitive): the shared constraint groups plus
97
+ * the applicators and extras. One list, read by the schema compiler (does
98
+ * this `$ref` carry siblings to compile?) and by the reference resolver
99
+ * (may this hop of a `$ref` chain be flattened away?).
100
+ */
101
+ export const REF_SIBLING_KEYWORDS = Object.freeze(['type', 'const', 'enum',
102
+ ...NUMERIC_CONSTRAINTS, ...STRING_CONSTRAINTS,
103
+ ...ARRAY_CONSTRAINTS, 'maxContains', 'minContains',
104
+ ...OBJECT_CONSTRAINTS, 'required',
105
+ 'dependentRequired', 'properties', 'patternProperties', 'additionalProperties', 'items',
106
+ 'prefixItems', 'additionalItems', 'contains', 'allOf', 'anyOf', 'oneOf', 'not', 'if',
107
+ 'then', 'else', 'propertyNames', 'contentEncoding', 'contentMediaType',
108
+ 'unevaluatedProperties', 'unevaluatedItems', '$query']);
109
+
110
+ /**
111
+ * Whether a schema carrying a `$ref` also carries a keyword of
112
+ * {@link REF_SIBLING_KEYWORDS}.
113
+ * @param {object} schema
114
+ * @returns {boolean}
115
+ */
116
+ export function hasRefSiblings(schema) {
117
+ const keys = Object.keys(schema);
118
+ for (let i = 0; i < keys.length; i++) {
119
+ if (REF_SIBLING_KEYWORDS.includes(keys[i])) return true;
120
+ }
121
+ return false;
122
+ }
123
+
88
124
  /**
89
125
  * Whether a sibling keyword of unevaluatedProperties already evaluates every
90
126
  * property of the instance. additionalProperties (boolean or schema) applies
package/src/traverse.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  isBoolOrObjectClass,
3
3
  hasSchemaRef,
4
+ hasRefSiblings,
4
5
  } from './tools.js';
5
6
 
6
7
  import {
@@ -416,10 +417,18 @@ export function resolveRefSchemaDeep(schemas, baseUri, refschema, opts = new Tra
416
417
  if (!isObjectClass(item))
417
418
  return { id: base, schema: item };
418
419
 
419
- // In draft 7 and earlier, $ref completely replaces the schema
420
- // and all sibling keywords must be ignored. We only keep the $ref
421
- // to resolve it, discarding all other keywords from this item.
420
+ // A pure `$ref` hop is flattened away: only the `$ref` is kept to
421
+ // resolve it, the other (annotation) keywords of this item are
422
+ // discarded. A target reached THROUGH the chain that asserts
423
+ // keywords beside its `$ref` is the end of what may be flattened:
424
+ // draft 2019-09+ applies those siblings alongside the reference, so
425
+ // collapsing the chain past them would drop assertions (an OpenAPI
426
+ // document schema is nothing but such hops). The item is kept as
427
+ // written and compiled as a schema object, where the resource's
428
+ // draft decides whether its siblings assert (draft-07 ignores them).
422
429
  if (hasSchemaRef(item)) {
430
+ if (item !== refschema && hasRefSiblings(item))
431
+ return { id: base, schema: item };
423
432
  const ref = item.$ref;
424
433
  const { id, schema } = resolveRefSchemaShallow(schemas, ref, base, opts);
425
434