@hyperjump/json-schema 0.23.3 → 1.0.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 (202) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +459 -120
  3. package/draft-04/additionalItems.js +29 -0
  4. package/{lib/keywords → draft-04}/dependencies.js +9 -5
  5. package/draft-04/exclusiveMaximum.js +5 -0
  6. package/draft-04/exclusiveMinimum.js +5 -0
  7. package/draft-04/id.js +1 -0
  8. package/draft-04/index.d.ts +43 -0
  9. package/draft-04/index.js +69 -0
  10. package/draft-04/items.js +35 -0
  11. package/draft-04/maximum.js +26 -0
  12. package/draft-04/minimum.js +25 -0
  13. package/draft-04/ref.js +1 -0
  14. package/draft-04/schema.js +149 -0
  15. package/draft-06/contains.js +13 -0
  16. package/draft-06/index.d.ts +47 -0
  17. package/draft-06/index.js +66 -0
  18. package/draft-06/schema.js +154 -0
  19. package/{lib/draft-07.d.ts → draft-07/index.d.ts} +21 -19
  20. package/draft-07/index.js +72 -0
  21. package/draft-07/schema.js +172 -0
  22. package/{lib/keywords/contains-minContains-maxContains.js → draft-2019-09/contains.js} +14 -7
  23. package/{lib/draft-2019-09.d.ts → draft-2019-09/index.d.ts} +24 -22
  24. package/draft-2019-09/index.js +117 -0
  25. package/draft-2019-09/meta/applicator.js +55 -0
  26. package/draft-2019-09/meta/content.js +17 -0
  27. package/draft-2019-09/meta/core.js +57 -0
  28. package/draft-2019-09/meta/format.js +14 -0
  29. package/draft-2019-09/meta/meta-data.js +37 -0
  30. package/draft-2019-09/meta/validation.js +98 -0
  31. package/draft-2019-09/recursiveAnchor.js +1 -0
  32. package/draft-2019-09/recursiveRef.js +19 -0
  33. package/draft-2019-09/schema.js +42 -0
  34. package/draft-2020-12/dynamicAnchor.js +1 -0
  35. package/draft-2020-12/dynamicRef.js +35 -0
  36. package/draft-2020-12/index.d.ts +66 -0
  37. package/draft-2020-12/index.js +124 -0
  38. package/draft-2020-12/meta/applicator.js +46 -0
  39. package/draft-2020-12/meta/content.js +14 -0
  40. package/draft-2020-12/meta/core.js +54 -0
  41. package/draft-2020-12/meta/format-annotation.js +11 -0
  42. package/draft-2020-12/meta/format-assertion.js +11 -0
  43. package/draft-2020-12/meta/meta-data.js +34 -0
  44. package/draft-2020-12/meta/unevaluated.js +12 -0
  45. package/draft-2020-12/meta/validation.js +95 -0
  46. package/draft-2020-12/schema.js +44 -0
  47. package/lib/common.d.ts +5 -1
  48. package/lib/common.js +80 -9
  49. package/lib/configuration.d.ts +9 -0
  50. package/lib/configuration.js +18 -0
  51. package/lib/core.d.ts +48 -0
  52. package/lib/core.js +102 -0
  53. package/lib/experimental.d.ts +8 -0
  54. package/lib/experimental.js +4 -0
  55. package/lib/fetch.browser.js +1 -0
  56. package/lib/fetch.js +19 -0
  57. package/lib/index.d.ts +11 -42
  58. package/lib/index.js +130 -23
  59. package/lib/instance.d.ts +75 -0
  60. package/lib/instance.js +58 -0
  61. package/lib/invalid-schema-error.d.ts +8 -0
  62. package/lib/invalid-schema-error.js +7 -0
  63. package/lib/keywords/additionalProperties.js +19 -17
  64. package/lib/keywords/allOf.js +10 -7
  65. package/lib/keywords/anchor.js +1 -0
  66. package/lib/keywords/anyOf.js +10 -7
  67. package/lib/keywords/comment.js +4 -0
  68. package/lib/keywords/const.js +6 -3
  69. package/lib/keywords/contains.js +48 -5
  70. package/lib/keywords/contentEncoding.js +4 -0
  71. package/lib/keywords/contentMediaType.js +4 -0
  72. package/lib/keywords/contentSchema.js +4 -0
  73. package/lib/keywords/default.js +4 -0
  74. package/lib/keywords/definitions.js +7 -4
  75. package/lib/keywords/dependentRequired.js +6 -3
  76. package/lib/keywords/dependentSchemas.js +10 -6
  77. package/lib/keywords/deprecated.js +4 -0
  78. package/lib/keywords/description.js +4 -0
  79. package/lib/keywords/dynamicAnchor.js +1 -0
  80. package/lib/keywords/dynamicRef.js +13 -17
  81. package/lib/keywords/else.js +16 -11
  82. package/lib/keywords/enum.js +6 -3
  83. package/lib/keywords/examples.js +4 -0
  84. package/lib/keywords/exclusiveMaximum.js +5 -2
  85. package/lib/keywords/exclusiveMinimum.js +5 -2
  86. package/lib/keywords/format.js +4 -0
  87. package/lib/keywords/id.js +1 -0
  88. package/lib/keywords/if.js +8 -6
  89. package/lib/keywords/items.js +17 -19
  90. package/lib/keywords/maxContains.js +4 -0
  91. package/lib/keywords/maxItems.js +5 -2
  92. package/lib/keywords/maxLength.js +5 -2
  93. package/lib/keywords/maxProperties.js +5 -2
  94. package/lib/keywords/maximum.js +5 -2
  95. package/lib/keywords/meta-data.js +4 -0
  96. package/lib/keywords/minContains.js +4 -0
  97. package/lib/keywords/minItems.js +5 -2
  98. package/lib/keywords/minLength.js +5 -2
  99. package/lib/keywords/minProperties.js +5 -2
  100. package/lib/keywords/minimum.js +5 -2
  101. package/lib/keywords/multipleOf.js +5 -2
  102. package/lib/keywords/not.js +6 -4
  103. package/lib/keywords/oneOf.js +9 -6
  104. package/lib/keywords/pattern.js +5 -2
  105. package/lib/keywords/patternProperties.js +9 -5
  106. package/lib/keywords/prefixItems.js +28 -0
  107. package/lib/keywords/properties.js +11 -6
  108. package/lib/keywords/propertyDependencies.js +49 -0
  109. package/lib/keywords/propertyNames.js +7 -4
  110. package/lib/keywords/readOnly.js +4 -0
  111. package/lib/keywords/ref.js +9 -6
  112. package/lib/keywords/requireAllExcept.js +24 -0
  113. package/lib/keywords/required.js +5 -2
  114. package/lib/keywords/then.js +16 -11
  115. package/lib/keywords/title.js +4 -0
  116. package/lib/keywords/type.js +5 -2
  117. package/lib/keywords/unevaluatedItems.js +9 -5
  118. package/lib/keywords/unevaluatedProperties.js +9 -5
  119. package/lib/keywords/uniqueItems.js +6 -3
  120. package/lib/keywords/validation.js +123 -0
  121. package/lib/keywords/vocabulary.js +1 -0
  122. package/lib/keywords/writeOnly.js +4 -0
  123. package/lib/keywords.d.ts +19 -0
  124. package/lib/keywords.js +59 -0
  125. package/lib/media-types.d.ts +9 -0
  126. package/lib/media-types.js +26 -0
  127. package/lib/pubsub.js +42 -0
  128. package/lib/reference.d.ts +11 -0
  129. package/lib/reference.js +11 -0
  130. package/lib/schema.d.ts +64 -0
  131. package/lib/schema.js +308 -0
  132. package/package.json +31 -22
  133. package/{lib/draft-2020-12.d.ts → stable/index.d.ts} +26 -24
  134. package/stable/index.js +118 -0
  135. package/stable/meta/applicator.js +49 -0
  136. package/stable/meta/content.js +12 -0
  137. package/stable/meta/core.js +49 -0
  138. package/stable/meta/format-annotation.js +10 -0
  139. package/stable/meta/format-assertion.js +10 -0
  140. package/stable/meta/meta-data.js +16 -0
  141. package/stable/meta/unevaluated.js +11 -0
  142. package/stable/meta/validation.js +67 -0
  143. package/stable/validation.js +24 -0
  144. package/dist/json-schema-amd.js +0 -6602
  145. package/dist/json-schema-amd.js.map +0 -1
  146. package/dist/json-schema-amd.min.js +0 -3
  147. package/dist/json-schema-amd.min.js.map +0 -1
  148. package/dist/json-schema-cjs.js +0 -6600
  149. package/dist/json-schema-cjs.js.map +0 -1
  150. package/dist/json-schema-cjs.min.js +0 -3
  151. package/dist/json-schema-cjs.min.js.map +0 -1
  152. package/dist/json-schema-esm.js +0 -6596
  153. package/dist/json-schema-esm.js.map +0 -1
  154. package/dist/json-schema-esm.min.js +0 -3
  155. package/dist/json-schema-esm.min.js.map +0 -1
  156. package/dist/json-schema-iife.js +0 -6605
  157. package/dist/json-schema-iife.js.map +0 -1
  158. package/dist/json-schema-iife.min.js +0 -3
  159. package/dist/json-schema-iife.min.js.map +0 -1
  160. package/dist/json-schema-system.js +0 -6603
  161. package/dist/json-schema-system.js.map +0 -1
  162. package/dist/json-schema-system.min.js +0 -3
  163. package/dist/json-schema-system.min.js.map +0 -1
  164. package/dist/json-schema-umd.js +0 -6606
  165. package/dist/json-schema-umd.js.map +0 -1
  166. package/dist/json-schema-umd.min.js +0 -3
  167. package/dist/json-schema-umd.min.js.map +0 -1
  168. package/lib/draft-04.d.ts +0 -41
  169. package/lib/draft-04.js +0 -46
  170. package/lib/draft-06.d.ts +0 -45
  171. package/lib/draft-06.js +0 -51
  172. package/lib/draft-07.js +0 -55
  173. package/lib/draft-2019-09.js +0 -92
  174. package/lib/draft-2020-12.js +0 -103
  175. package/lib/index.mjs +0 -19
  176. package/lib/keywords/additionalItems.js +0 -27
  177. package/lib/keywords/additionalItems6.js +0 -23
  178. package/lib/keywords/additionalProperties6.js +0 -28
  179. package/lib/keywords/index.js +0 -53
  180. package/lib/keywords/items202012.js +0 -23
  181. package/lib/keywords/maximum-exclusiveMaximum.js +0 -20
  182. package/lib/keywords/minimum-exclusiveMinimum.js +0 -20
  183. package/lib/keywords/tupleItems.js +0 -24
  184. package/meta/draft/2019-09/meta/applicator.js +0 -55
  185. package/meta/draft/2019-09/meta/content.js +0 -17
  186. package/meta/draft/2019-09/meta/core.js +0 -57
  187. package/meta/draft/2019-09/meta/format.js +0 -14
  188. package/meta/draft/2019-09/meta/meta-data.js +0 -37
  189. package/meta/draft/2019-09/meta/validation.js +0 -98
  190. package/meta/draft/2019-09/schema.js +0 -42
  191. package/meta/draft/2020-12/meta/applicator.js +0 -49
  192. package/meta/draft/2020-12/meta/content.js +0 -17
  193. package/meta/draft/2020-12/meta/core.js +0 -57
  194. package/meta/draft/2020-12/meta/format-annotation.js +0 -14
  195. package/meta/draft/2020-12/meta/format-assertion.js +0 -14
  196. package/meta/draft/2020-12/meta/meta-data.js +0 -37
  197. package/meta/draft/2020-12/meta/unevaluated.js +0 -15
  198. package/meta/draft/2020-12/meta/validation.js +0 -98
  199. package/meta/draft/2020-12/schema.js +0 -44
  200. package/meta/draft-04/schema.js +0 -149
  201. package/meta/draft-06/schema.js +0 -154
  202. package/meta/draft-07/schema.js +0 -172
@@ -0,0 +1,59 @@
1
+ import metaData from "./keywords/meta-data.js";
2
+
3
+
4
+ const _keywords = {};
5
+ export const getKeyword = (id) => _keywords[id] || id.startsWith("https://json-schema.org/keyword/unknown#") && { id, ...metaData };
6
+
7
+ export const addKeyword = (keywordHandler) => {
8
+ _keywords[keywordHandler.id] = {
9
+ collectEvaluatedItems: (keywordValue, instance, ast, dynamicAnchors, isTop) => keywordHandler.interpret(keywordValue, instance, ast, dynamicAnchors, isTop) && new Set(),
10
+ collectEvaluatedProperties: (keywordValue, instance, ast, dynamicAnchors, isTop) => keywordHandler.interpret(keywordValue, instance, ast, dynamicAnchors, isTop) && [],
11
+ ...keywordHandler
12
+ };
13
+ };
14
+
15
+ const _vocabularies = {};
16
+ export const defineVocabulary = (id, keywords) => {
17
+ _vocabularies[id] = keywords;
18
+ };
19
+
20
+ const _dialects = {};
21
+ const _allowUnknownKeywords = {};
22
+ export const getKeywordId = (dialectId, keyword) => _dialects[dialectId]?.[keyword]
23
+ || _allowUnknownKeywords[dialectId] && `https://json-schema.org/keyword/unknown#${keyword}`;
24
+ export const getKeywordName = (dialectId, keywordId) => {
25
+ for (const keyword in _dialects[dialectId]) {
26
+ if (_dialects[dialectId][keyword] === keywordId) {
27
+ return keyword;
28
+ }
29
+ }
30
+ };
31
+
32
+ export const hasDialect = (dialectId) => dialectId in _dialects;
33
+
34
+ export const loadDialect = (dialectId, dialect, allowUnknownKeywords = false) => {
35
+ _allowUnknownKeywords[dialectId] = allowUnknownKeywords;
36
+
37
+ _dialects[dialectId] = {};
38
+ Object.entries(dialect)
39
+ .forEach(([vocabularyId, isRequired]) => {
40
+ if (vocabularyId in _vocabularies) {
41
+ Object.entries(_vocabularies[vocabularyId])
42
+ .forEach(([keyword, keywordId]) => {
43
+ if (!(keywordId in _keywords)) {
44
+ if (isRequired) {
45
+ delete _dialects[dialectId];
46
+ throw Error(`The '${keywordId}' keyword is not supported. This keyword was included in the '${vocabularyId}' vocabulary which is required by the '${dialectId}' dialect.`);
47
+ } else {
48
+ // Allow keyword to be ignored
49
+ keywordId = `https://json-schema.org/keyword/unknown#${keyword}`;
50
+ }
51
+ }
52
+ _dialects[dialectId][keyword] = keywordId;
53
+ });
54
+ } else {
55
+ delete _dialects[dialectId];
56
+ throw Error(`Unrecognized vocabulary: ${vocabularyId}. You can define this vocabulary with the 'defineVocabulary' function.`);
57
+ }
58
+ });
59
+ };
@@ -0,0 +1,9 @@
1
+ import type { SchemaObject } from "./schema.js";
2
+
3
+
4
+ export const addMediaTypePlugin: (contentType: string, plugin: MediaTypePlugin) => void;
5
+
6
+ export type MediaTypePlugin = {
7
+ parse: (response: Response, mediaTypeParameters: { [parameter: string]: string }) => Promise<[SchemaObject, string | undefined]>;
8
+ matcher: (path: string) => boolean;
9
+ };
@@ -0,0 +1,26 @@
1
+ import contentTypeParser from "content-type";
2
+
3
+
4
+ const mediaTypePlugins = {};
5
+
6
+ export const addMediaTypePlugin = (contentType, plugin) => {
7
+ mediaTypePlugins[contentType] = plugin;
8
+ };
9
+
10
+ export const parse = (response) => {
11
+ const contentType = contentTypeParser.parse(response.headers.get("content-type"));
12
+ if (!(contentType.type in mediaTypePlugins)) {
13
+ throw Error(`${response.url} is not a schema. Found a document with media type: ${contentType.type}`);
14
+ }
15
+ return mediaTypePlugins[contentType.type].parse(response, contentType.parameters);
16
+ };
17
+
18
+ export const getContentType = (path) => {
19
+ for (const contentType in mediaTypePlugins) {
20
+ if (mediaTypePlugins[contentType].matcher(path)) {
21
+ return contentType;
22
+ }
23
+ }
24
+
25
+ return "application/octet-stream";
26
+ };
package/lib/pubsub.js ADDED
@@ -0,0 +1,42 @@
1
+ const subscriptions = {};
2
+ let uid = 0;
3
+
4
+ export const subscribe = (message, fn) => {
5
+ if (!(message in subscriptions)) {
6
+ subscriptions[message] = {};
7
+ }
8
+
9
+ const subscriptionId = `pubsub_subscription_${uid++}`;
10
+ subscriptions[message][subscriptionId] = fn;
11
+
12
+ return subscriptionId;
13
+ };
14
+
15
+ export const unsubscribe = (message, token) => {
16
+ delete subscriptions[message][token];
17
+ };
18
+
19
+ export const publish = (message, data) => {
20
+ for (const subscribedMessage in subscriptions) {
21
+ if (subscribedMessage === message || message.startsWith(`${subscribedMessage}.`)) {
22
+ for (const subscriptionId in subscriptions[subscribedMessage]) {
23
+ subscriptions[subscribedMessage][subscriptionId](message, data);
24
+ }
25
+ }
26
+ }
27
+ };
28
+
29
+ export const publishAsync = async (message, data) => {
30
+ const promises = [];
31
+ for (const subscribedMessage in subscriptions) {
32
+ if (subscribedMessage === message || message.startsWith(`${subscribedMessage}.`)) {
33
+ for (const subscriptionId in subscriptions[subscribedMessage]) {
34
+ promises.push(subscriptions[message][subscriptionId](message, data));
35
+ }
36
+ }
37
+ }
38
+
39
+ await Promise.all(promises);
40
+
41
+ return;
42
+ };
@@ -0,0 +1,11 @@
1
+ export const cons: (href: string, value: unknown) => Ref;
2
+ export const isReference: (ref: unknown) => ref is Ref;
3
+ export const href: (ref: Ref) => string;
4
+ export const value: <A>(ref: Ref) => A;
5
+
6
+ declare const $__value: unique symbol;
7
+ declare const $__href: unique symbol;
8
+ export type Ref = {
9
+ [$__href]: string;
10
+ [$__value]: unknown;
11
+ };
@@ -0,0 +1,11 @@
1
+ const $__value = Symbol("$__value");
2
+ const $__href = Symbol("$__href");
3
+
4
+ export const cons = (href, value) => Object.freeze({
5
+ [$__href]: href,
6
+ [$__value]: value
7
+ });
8
+
9
+ export const isReference = (ref) => ref && ref[$__href] !== undefined;
10
+ export const href = (ref) => ref[$__href];
11
+ export const value = (ref) => ref[$__value];
@@ -0,0 +1,64 @@
1
+ import type { JsonType } from "./common.js";
2
+
3
+
4
+ export const add: <A extends SchemaObject | boolean>(schema: A, url?: string, defaultSchemaVersion?: string) => string;
5
+ export const get: (url: string, context?: SchemaDocument) => Promise<SchemaDocument>;
6
+ export const markValidated: (id: string) => void;
7
+ export const uri: (doc: SchemaDocument) => string;
8
+ export const value: <A extends SchemaFragment>(doc: SchemaDocument<A>) => A;
9
+ export const typeOf: (
10
+ (doc: SchemaDocument, type: "null") => doc is SchemaDocument<null>
11
+ ) & (
12
+ (doc: SchemaDocument, type: "boolean") => doc is SchemaDocument<boolean>
13
+ ) & (
14
+ (doc: SchemaDocument, type: "object") => doc is SchemaDocument<SchemaObject>
15
+ ) & (
16
+ (doc: SchemaDocument, type: "array") => doc is SchemaDocument<SchemaFragment[]>
17
+ ) & (
18
+ (doc: SchemaDocument, type: "number" | "integer") => doc is SchemaDocument<number>
19
+ ) & (
20
+ (doc: SchemaDocument, type: "string") => doc is SchemaDocument<string>
21
+ ) & (
22
+ (doc: SchemaDocument, type: JsonType) => boolean
23
+ ) & (
24
+ (doc: SchemaDocument) => (type: JsonType) => boolean
25
+ );
26
+ export const has: (key: string, doc: SchemaDocument) => boolean;
27
+ export const step: (key: string, doc: SchemaDocument) => Promise<SchemaDocument>;
28
+ export const keys: (doc: SchemaDocument) => string[];
29
+ export const entries: (doc: SchemaDocument) => Promise<SchemaEntry[]>;
30
+ export const map: (
31
+ <A>(fn: MapFn<Promise<A> | A>, doc: SchemaDocument) => Promise<A[]>
32
+ ) & (
33
+ <A>(fn: MapFn<Promise<A> | A>) => (doc: SchemaDocument) => Promise<A[]>
34
+ );
35
+ export const length: (doc: SchemaDocument) => number;
36
+ export const toSchema: (doc: SchemaDocument, options: ToSchemaOptions) => SchemaObject;
37
+
38
+ type MapFn<A> = (element: SchemaDocument, index: number) => A;
39
+ export type SchemaEntry = [string, SchemaDocument];
40
+
41
+ export type ToSchemaOptions = {
42
+ parentId?: string;
43
+ parentDialect?: string;
44
+ includeEmbedded?: boolean;
45
+ };
46
+
47
+ export type SchemaDocument<A extends SchemaFragment = SchemaFragment> = {
48
+ id: string;
49
+ dialectId: string;
50
+ pointer: string;
51
+ schema: SchemaObject;
52
+ value: A;
53
+ anchors: Anchors;
54
+ dynamicAnchors: Anchors;
55
+ vocabulary: Record<string, boolean>;
56
+ validated: boolean;
57
+ };
58
+
59
+ export type SchemaFragment = string | number | boolean | null | SchemaObject | SchemaFragment[];
60
+ export type SchemaObject = { // eslint-disable-line @typescript-eslint/consistent-indexed-object-style
61
+ [keyword: string]: SchemaFragment;
62
+ };
63
+
64
+ export type Anchors = Record<string, string>;
package/lib/schema.js ADDED
@@ -0,0 +1,308 @@
1
+ import curry from "just-curry-it";
2
+ import * as Pact from "@hyperjump/pact";
3
+ import * as Json from "@hyperjump/json";
4
+ import * as JsonPointer from "@hyperjump/json-pointer";
5
+ import { jsonTypeOf, resolveUrl, urlFragment, pathRelative } from "./common.js";
6
+ import fetch from "./fetch.js";
7
+ import * as Keywords from "./keywords.js";
8
+ import * as MediaTypes from "./media-types.js";
9
+ import * as Reference from "./reference.js";
10
+
11
+
12
+ // Schema Management
13
+ const schemaStore = {};
14
+ const schemaStoreAlias = {};
15
+
16
+ const defaultDialectId = "https://json-schema.org/validation";
17
+
18
+ export const add = (schema, url = undefined, contextDialectId = undefined) => {
19
+ schema = JSON.parse(JSON.stringify(schema));
20
+
21
+ // Dialect / JSON Schema Version
22
+ const dialectId = resolveUrl("", schema.$schema || contextDialectId || defaultDialectId);
23
+ delete schema.$schema;
24
+
25
+ if (!Keywords.hasDialect(dialectId)) {
26
+ throw Error(`Encountered unknown dialect '${dialectId}'`);
27
+ }
28
+
29
+ // Identifiers
30
+ const idToken = Keywords.getKeywordName(dialectId, "https://json-schema.org/keyword/id")
31
+ || Keywords.getKeywordName(dialectId, "https://json-schema.org/keyword/draft-04/id");
32
+ const internalUrl = resolveUrl(schema[idToken] || url, url);
33
+ const id = resolveUrl("", internalUrl);
34
+ delete schema[idToken];
35
+ if (url) {
36
+ const externalId = resolveUrl("", url);
37
+ schemaStoreAlias[externalId] = id;
38
+ }
39
+
40
+ // Vocabulary
41
+ const vocabularyToken = Keywords.getKeywordName(dialectId, "https://json-schema.org/keyword/vocabulary");
42
+ if (jsonTypeOf(schema[vocabularyToken], "object")) {
43
+ const allowUnknownKeywords = schema[vocabularyToken]["https://json-schema.org/draft/2019-09/vocab/core"]
44
+ || schema[vocabularyToken]["https://json-schema.org/draft/2020-12/vocab/core"];
45
+
46
+ Keywords.loadDialect(id, schema[vocabularyToken], allowUnknownKeywords);
47
+ delete schema[vocabularyToken];
48
+ }
49
+
50
+ const dynamicAnchors = {};
51
+
52
+ // Recursive anchor
53
+ const recursiveAnchorToken = Keywords.getKeywordName(dialectId, "https://json-schema.org/keyword/draft-2019-09/recursiveAnchor");
54
+ if (schema[recursiveAnchorToken] === true) {
55
+ dynamicAnchors[""] = `${id}#`;
56
+ }
57
+ delete schema[recursiveAnchorToken];
58
+
59
+ // Store Schema
60
+ const anchors = { "": "" };
61
+ schemaStore[id] = {
62
+ id: id,
63
+ dialectId: dialectId,
64
+ schema: processSchema(schema, id, dialectId, JsonPointer.nil, anchors, dynamicAnchors),
65
+ anchors: anchors,
66
+ dynamicAnchors: dynamicAnchors,
67
+ validated: false
68
+ };
69
+
70
+ return id;
71
+ };
72
+
73
+ const processSchema = (subject, id, dialectId, pointer, anchors, dynamicAnchors) => {
74
+ if (jsonTypeOf(subject, "object")) {
75
+ // Legacy id
76
+ const legacyIdToken = Keywords.getKeywordName(dialectId, "https://json-schema.org/keyword/draft-04/id");
77
+ if (typeof subject[legacyIdToken] === "string") {
78
+ if (subject[legacyIdToken][0] === "#") {
79
+ const anchor = decodeURIComponent(subject[legacyIdToken].slice(1));
80
+ anchors[anchor] = pointer;
81
+ } else {
82
+ delete subject[legacyIdToken].$schema;
83
+ subject[legacyIdToken] = resolveUrl(subject[legacyIdToken], id);
84
+ add(subject, undefined, dialectId);
85
+ return Reference.cons(subject[legacyIdToken], subject);
86
+ }
87
+ delete subject[legacyIdToken];
88
+ }
89
+
90
+ // Embedded Schema
91
+ const embeddedDialectId = typeof subject.$schema === "string" ? resolveUrl("", subject.$schema) : dialectId;
92
+ const idToken = Keywords.getKeywordName(embeddedDialectId, "https://json-schema.org/keyword/id");
93
+ if (typeof subject[idToken] === "string") {
94
+ subject[idToken] = resolveUrl(subject[idToken], id);
95
+ add(subject, undefined, dialectId);
96
+ return Reference.cons(subject[idToken], subject);
97
+ }
98
+
99
+ // Legacy dynamic anchor
100
+ const legacyDynamicAnchorToken = Keywords.getKeywordName(dialectId, "https://json-schema.org/keyword/draft-2020-12/dynamicAnchor");
101
+ if (typeof subject[legacyDynamicAnchorToken] === "string") {
102
+ dynamicAnchors[subject[legacyDynamicAnchorToken]] = `${id}#${encodeURI(pointer)}`;
103
+ anchors[subject[legacyDynamicAnchorToken]] = pointer;
104
+ delete subject[legacyDynamicAnchorToken];
105
+ }
106
+
107
+ const dynamicAnchorToken = Keywords.getKeywordName(dialectId, "https://json-schema.org/keyword/dynamicAnchor");
108
+ if (typeof subject[dynamicAnchorToken] === "string") {
109
+ dynamicAnchors[subject[dynamicAnchorToken]] = `${id}#${encodeURI(pointer)}`;
110
+ delete subject[dynamicAnchorToken];
111
+ }
112
+
113
+ const anchorToken = Keywords.getKeywordName(dialectId, "https://json-schema.org/keyword/anchor");
114
+ if (typeof subject[anchorToken] === "string") {
115
+ anchors[subject[anchorToken]] = pointer;
116
+ delete subject[anchorToken];
117
+ }
118
+
119
+ // Legacy $ref
120
+ const jrefToken = Keywords.getKeywordName(dialectId, "https://json-schema.org/keyword/draft-04/ref");
121
+ if (typeof subject[jrefToken] === "string") {
122
+ return Reference.cons(subject[jrefToken], subject);
123
+ }
124
+
125
+ for (const key in subject) {
126
+ subject[key] = processSchema(subject[key], id, dialectId, JsonPointer.append(key, pointer), anchors, dynamicAnchors);
127
+ }
128
+ } else if (Array.isArray(subject)) {
129
+ for (let index = 0; index < subject.length; index++) {
130
+ subject[index] = processSchema(subject[index], id, dialectId, JsonPointer.append(index, pointer), anchors, dynamicAnchors);
131
+ }
132
+ }
133
+
134
+ return subject;
135
+ };
136
+
137
+ const hasStoredSchema = (id) => id in schemaStore || id in schemaStoreAlias;
138
+ const getStoredSchema = (id) => schemaStore[schemaStoreAlias[id]] || schemaStore[id];
139
+
140
+ export const markValidated = (id) => {
141
+ schemaStore[id].validated = true;
142
+ };
143
+
144
+ // Schema Retrieval
145
+ const nil = Object.freeze({
146
+ id: undefined,
147
+ dialectId: undefined,
148
+ pointer: JsonPointer.nil,
149
+ schema: undefined,
150
+ value: undefined,
151
+ anchors: {},
152
+ dynamicAnchors: {},
153
+ validated: true
154
+ });
155
+
156
+ export const get = async (url, contextDoc = nil) => {
157
+ const resolvedUrl = resolveUrl(url, uri(contextDoc));
158
+ const id = resolveUrl("", resolvedUrl);
159
+ const fragment = urlFragment(resolvedUrl);
160
+
161
+ if (!hasStoredSchema(id)) {
162
+ const response = await fetch(id, { headers: { Accept: "application/schema+json" } });
163
+ if (response.status >= 400) {
164
+ await response.text(); // Sometimes node hangs without this hack
165
+ throw Error(`Failed to retrieve schema with id: ${id}`);
166
+ }
167
+
168
+ const [schema, contextDialectId] = await MediaTypes.parse(response);
169
+
170
+ // Try to determine the dialect from the meta-schema if it isn't already known
171
+ const dialectId = resolveUrl("", schema.$schema || contextDialectId || defaultDialectId);
172
+ if (!Keywords.hasDialect(dialectId) && !hasStoredSchema(dialectId)) {
173
+ await get(dialectId);
174
+ }
175
+
176
+ add(schema, id, contextDialectId);
177
+ }
178
+
179
+ const storedSchema = getStoredSchema(id);
180
+ const pointer = fragment[0] !== "/" ? getAnchorPointer(storedSchema, fragment) : fragment;
181
+ const doc = Object.freeze({
182
+ ...storedSchema,
183
+ pointer: pointer,
184
+ value: JsonPointer.get(pointer, storedSchema.schema)
185
+ });
186
+
187
+ return followReferences(doc);
188
+ };
189
+
190
+ const followReferences = (doc) => Reference.isReference(doc.value) ? get(Reference.href(doc.value), doc) : doc;
191
+
192
+ const getAnchorPointer = (schema, fragment) => {
193
+ if (!(fragment in schema.anchors)) {
194
+ throw Error(`No such anchor '${encodeURI(schema.id)}#${encodeURI(fragment)}'`);
195
+ }
196
+
197
+ return schema.anchors[fragment];
198
+ };
199
+
200
+ // Utility Functions
201
+ export const uri = (doc) => doc.id ? `${doc.id}#${encodeURI(doc.pointer)}` : undefined;
202
+ export const value = (doc) => Reference.isReference(doc.value) ? Reference.value(doc.value) : doc.value;
203
+ export const has = (key, doc) => key in value(doc);
204
+ export const typeOf = (doc, type) => jsonTypeOf(value(doc), type);
205
+
206
+ export const step = (key, doc) => {
207
+ const storedSchema = getStoredSchema(doc.id);
208
+ const nextDoc = Object.freeze({
209
+ ...doc,
210
+ pointer: JsonPointer.append(`${key}`, doc.pointer),
211
+ value: value(doc)[key],
212
+ validated: storedSchema.validated
213
+ });
214
+ return followReferences(nextDoc);
215
+ };
216
+
217
+ export const keys = (doc) => Object.keys(value(doc));
218
+
219
+ export const entries = (doc) => Pact.pipeline([
220
+ value,
221
+ Object.keys,
222
+ Pact.map(async (key) => [key, await step(key, doc)]),
223
+ Pact.all
224
+ ], doc);
225
+
226
+ export const map = curry((fn, doc) => Pact.pipeline([
227
+ value,
228
+ Pact.map(async (item, ndx) => fn(await step(ndx, doc), ndx)),
229
+ Pact.all
230
+ ], doc));
231
+
232
+ export const length = (doc) => value(doc).length;
233
+
234
+ const toSchemaDefaultOptions = {
235
+ parentId: "",
236
+ parentDialect: "",
237
+ includeEmbedded: true
238
+ };
239
+ export const toSchema = (schemaDoc, options = {}) => {
240
+ const fullOptions = { ...toSchemaDefaultOptions, ...options };
241
+
242
+ const idToken = Keywords.getKeywordName(schemaDoc.dialectId, "https://json-schema.org/keyword/id");
243
+ const anchorToken = Keywords.getKeywordName(schemaDoc.dialectId, "https://json-schema.org/keyword/anchor");
244
+ const dynamicAnchorToken = Keywords.getKeywordName(schemaDoc.dialectId, "https://json-schema.org/keyword/dynamicAnchor");
245
+ const legacyDynamicAnchorToken = Keywords.getKeywordName(schemaDoc.dialectId, "https://json-schema.org/keyword/draft-2020-12/dynamicAnchor");
246
+ const recursiveAnchorToken = Keywords.getKeywordName(schemaDoc.dialectId, "https://json-schema.org/keyword/recursiveAnchor");
247
+
248
+ const anchors = {};
249
+ for (const anchor in schemaDoc.anchors) {
250
+ if (anchor !== "" && !schemaDoc.dynamicAnchors[anchor]) {
251
+ anchors[schemaDoc.anchors[anchor]] = anchor;
252
+ }
253
+ }
254
+
255
+ const dynamicAnchors = {};
256
+ for (const anchor in schemaDoc.dynamicAnchors) {
257
+ const pointer = urlFragment(schemaDoc.dynamicAnchors[anchor]);
258
+ dynamicAnchors[pointer] = anchor;
259
+ }
260
+
261
+ const schema = JSON.parse(Json.stringify(schemaDoc.schema, (key, value, pointer) => {
262
+ if (Reference.isReference(value)) {
263
+ const refValue = Reference.value(value);
264
+ if (!fullOptions.includeEmbedded && idToken in refValue) {
265
+ return;
266
+ } else {
267
+ return Reference.value(value);
268
+ }
269
+ } else {
270
+ if (pointer in anchors) {
271
+ value = { [anchorToken]: anchors[pointer], ...value };
272
+ }
273
+ if (pointer in dynamicAnchors) {
274
+ if (dynamicAnchorToken) {
275
+ value = { [dynamicAnchorToken]: dynamicAnchors[pointer], ...value };
276
+ }
277
+
278
+ // Legacy dynamic anchor
279
+ if (legacyDynamicAnchorToken) {
280
+ value = { [legacyDynamicAnchorToken]: dynamicAnchors[pointer], ...value };
281
+ }
282
+
283
+ // Recursive anchor
284
+ if (recursiveAnchorToken) {
285
+ value = { [recursiveAnchorToken]: true, ...value };
286
+ }
287
+ }
288
+ return value;
289
+ }
290
+ }));
291
+
292
+ const id = relativeUri(fullOptions.parentId, schemaDoc.id);
293
+ const dialect = fullOptions.parentDialect === schemaDoc.dialectId ? "" : schemaDoc.dialectId;
294
+ return {
295
+ ...(id && { [idToken]: id }),
296
+ ...(dialect && { $schema: dialect }),
297
+ ...schema
298
+ };
299
+ };
300
+
301
+ const relativeUri = (from, to) => {
302
+ if (to.startsWith("file://")) {
303
+ const pathToSchema = from.slice(7, from.lastIndexOf("/"));
304
+ return from === "" ? "" : pathRelative(pathToSchema, to.slice(7));
305
+ } else {
306
+ return to;
307
+ }
308
+ };
package/package.json CHANGED
@@ -1,28 +1,36 @@
1
1
  {
2
2
  "name": "@hyperjump/json-schema",
3
- "version": "0.23.3",
4
- "description": "A JSON Schema Validator",
5
- "main": "lib/index.js",
3
+ "version": "1.0.0",
4
+ "description": "A JSON Schema validator with support for custom keywords, vocabularies, and dialects",
5
+ "type": "module",
6
6
  "exports": {
7
- "require": "./lib/index.js",
8
- "import": "./lib/index.mjs"
7
+ ".": "./stable/index.js",
8
+ "./draft-04": "./draft-04/index.js",
9
+ "./draft-06": "./draft-06/index.js",
10
+ "./draft-07": "./draft-07/index.js",
11
+ "./draft-2019-09": "./draft-2019-09/index.js",
12
+ "./draft-2020-12": "./draft-2020-12/index.js",
13
+ "./experimental": "./lib/experimental.js",
14
+ "./schema/experimental": "./lib/schema.js",
15
+ "./instance/experimental": "./lib/instance.js"
16
+ },
17
+ "browser": {
18
+ "./lib/fetch.js": "./lib/fetch.browser.js"
9
19
  },
10
20
  "scripts": {
11
21
  "clean": "xargs -a .gitignore rm -rf",
12
22
  "lint": "eslint lib",
13
- "test": "mocha --require ts-node/register 'lib/**/*.spec.ts'",
14
- "build": "rollup --config rollup.config.js",
15
- "prepublishOnly": "npm run build",
16
- "postinstall": "node -e \"require('fs').rmSync('dist', { recursive: true })\""
23
+ "test": "mocha 'lib/**/*.spec.ts' 'stable/**/*.spec.ts' 'draft-*/**/*.spec.ts'"
17
24
  },
18
- "repository": "github:hyperjump-io/json-schema-validator",
25
+ "repository": "github:hyperjump-io/json-schema",
19
26
  "keywords": [
20
27
  "JSON Schema",
21
- "draft-04",
28
+ "JSON",
29
+ "Schema",
30
+ "2020-12",
31
+ "2019-09",
22
32
  "draft-06",
23
- "draft-07",
24
- "draft 2019-09",
25
- "draft 2020-12",
33
+ "draft-04",
26
34
  "vocabulary",
27
35
  "vocabularies"
28
36
  ],
@@ -33,8 +41,6 @@
33
41
  "url": "https://github.com/sponsors/jdesrosiers"
34
42
  },
35
43
  "devDependencies": {
36
- "@rollup/plugin-commonjs": "*",
37
- "@rollup/plugin-node-resolve": "*",
38
44
  "@types/chai": "*",
39
45
  "@types/mocha": "*",
40
46
  "@typescript-eslint/eslint-plugin": "*",
@@ -46,14 +52,17 @@
46
52
  "eslint-plugin-import": "*",
47
53
  "json-schema-test-suite": "github:json-schema-org/JSON-Schema-Test-Suite",
48
54
  "mocha": "*",
49
- "rimraf": "*",
50
- "rollup": "*",
51
- "rollup-plugin-terser": "*",
55
+ "nock": "*",
52
56
  "ts-node": "*",
53
- "typescript": "*"
57
+ "typescript": "*",
58
+ "yaml": "*"
54
59
  },
55
60
  "dependencies": {
56
- "@hyperjump/json-schema-core": "^0.28.0",
57
- "fastest-stable-stringify": "^2.0.2"
61
+ "@hyperjump/json": "^0.1.0",
62
+ "@hyperjump/json-pointer": "^0.9.5",
63
+ "@hyperjump/pact": "^0.2.4",
64
+ "content-type": "^1.0.4",
65
+ "fastest-stable-stringify": "^2.0.2",
66
+ "node-fetch": "^3.3.0"
58
67
  }
59
68
  }