@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
package/README.md CHANGED
@@ -1,24 +1,16 @@
1
- # Hyperjump - JSON Schema Validator
2
- JSON Schema Validator (JSV) is built on [JSON Schema Core](https://github.com/hyperjump-io/json-schema-core).
3
-
4
- * Supported JSON Schema Dialects
5
- * draft-04 | draft-06 | draft-07 | Draft 2019-09 | Draft 2020-12
6
- * Create your own dialect with [JSC](https://github.com/hyperjump-io/json-schema-core)
7
- * Schemas can reference other schemas using a different draft
8
- * Supported vocabularies (new in Draft 2019-09)
9
- * Draft 2019-09: core | applicator | validation | meta-data | content
10
- * Draft 2020-12: core | applicator | validation | meta-data | content |
11
- format-annotations
12
- * Create your own keywords and vocabularies with [JSC](https://github.com/hyperjump-io/json-schema-core)
13
- * Output formats
14
- * FLAG, BASIC, DETAILED, VERBOSE
15
- * Create your own output format with [JSC](https://github.com/hyperjump-io/json-schema-core)
16
- * Load schemas from filesystem (file://), network (http(s)://), or JavaScript
17
- * Build non-validation JSON-Schema based tools with [JSC](https://github.com/hyperjump-io/json-schema-core)
1
+ # Hyperjump - JSON Schema
2
+
3
+ A collection of modules for working with JSON Schemas.
4
+
5
+ * Validate JSON-compatible values against a JSON Schema
6
+ * Dialects: draft-2020-12, draft-2019-09, draft-07, draft-06, draft-04
7
+ * Schemas can reference other schemas using a different dialect
8
+ * Work directly with schemas on the filesystem or HTTP
9
+ * Create custom keywords, vocabularies, and dialects
10
+ * Provides utilities for building non-validation JSON Schema tooling
18
11
 
19
12
  ## Install
20
- JSV includes support for node.js JavaScript (CommonJS and ES Modules),
21
- TypeScript, and browsers.
13
+ Includes support for node.js (ES Modules, TypeScript) and browsers.
22
14
 
23
15
  ### Node.js
24
16
  ```bash
@@ -26,7 +18,7 @@ npm install @hyperjump/json-schema
26
18
  ```
27
19
 
28
20
  ### Browser
29
- When in a browser context, JSV is designed to use the browser's `fetch`
21
+ When in a browser context, this library is designed to use the browser's `fetch`
30
22
  implementation instead of a node.js fetch clone. The Webpack bundler does this
31
23
  properly without any extra configuration, but if you are using the Rollup
32
24
  bundler you will need to include the `browser: true` option in your Rollup
@@ -36,72 +28,162 @@ configuration.
36
28
  plugins: [
37
29
  resolve({
38
30
  browser: true
39
- }),
40
- commonjs()
31
+ })
41
32
  ]
42
33
  ```
43
34
 
44
35
  ### Versioning
45
- This project is in beta and there may be breaking changes at any time. When it's
46
- stable enough, I'll publish v1.0.0 and follow semantic versioning from there on
47
- out.
36
+ The API for this library is divided into two categories: Stable and
37
+ Experimental. The Stable API strictly follows semantic versioning, but the
38
+ Experimental API may have backward-incompatible changes between minor versions.
48
39
 
49
40
  ## Usage
41
+ This library supports many versions of JSON Schema. Use the pattern
42
+ `@hyperjump/json-schema/*` to import the version you need.
43
+
50
44
  ```javascript
51
- const JsonSchema = require("@hyperjump/json-schema");
45
+ import { addSchema, validate } from "@hyperjump/json-schema/draft-2020-12";
46
+ ```
52
47
 
48
+ You can import support for additional versions as needed.
53
49
 
54
- // Example: Inline schema
55
- const schemaJson = {
56
- "$schema": "https://json-schema.org/draft/2020-12/schema",
57
- "$id": "http://example.com/schemas/string",
58
- "type": "string"
59
- };
60
- JsonSchema.add(schemaJson);
61
- const schema = await JsonSchema.get("http://example.com/schemas/string");
50
+ ```javascript
51
+ import { addSchema, validate } from "@hyperjump/json-schema/draft-2020-12";
52
+ import "@hyperjump/json-schema/draft-07";
53
+ ```
62
54
 
63
- // Example: Fetch from the web
64
- const schema = await JsonSchema.get("http://example.com/schemas/string");
55
+ **Note**: The default export (`@hyperjump/json-schema`) is reserved for the
56
+ stable version of JSON Schema that will hopefully be released in 2023.
65
57
 
66
- // Example: Fetch from file
67
- const schema = await JsonSchema.get("file:///path/to/my/schemas/string.schema.json");
58
+ **Validate schema from JavaScript**
59
+ ```javascript
60
+ addSchema({
61
+ $schema: "https://json-schema.org/draft/2020-12/schema",
62
+ type: "string"
63
+ }, "http://example.com/schemas/string");
68
64
 
69
- // Example: Validate instance
70
- const output = await JsonSchema.validate(schema, "foo");
65
+ const output = await validate("http://example.com/schemas/string", "foo");
71
66
  if (output.valid) {
72
67
  console.log("Instance is valid :-)");
73
68
  } else {
74
69
  console.log("Instance is invalid :-(");
75
70
  }
71
+ ```
76
72
 
77
- // Example: Precompile validator
78
- const isString = await JsonSchema.validate(schema);
79
- const output = isString("foo");
73
+ **Compile schema**
74
+
75
+ If you need to validate multiple instances against the same schema, you can
76
+ compile the schema into a reusable validation function.
77
+
78
+ ```javascript
79
+ const isString = await validate("http://example.com/schemas/string");
80
+ const output1 = isString("foo");
81
+ const output2 = isString(42);
82
+ ```
83
+
84
+ **Fetching schemas**
85
+
86
+ You can fetch schemas from the web or from the file system, but when fetching
87
+ from the file system, there are limitations for security reasons. If your schema
88
+ has an identifier with an http(s) scheme (**https**://example.com), it's not
89
+ allowed to reference schemas with a file scheme
90
+ (**file**:///path/to/my/schemas).
91
+
92
+ ```javascript
93
+ const output = await validate("http://example.com/schemas/string", "foo");
94
+ ```
80
95
 
81
- // Example: Specify output format
82
- const output = await JsonSchema.validate(schema, "foo", JsonSchema.VERBOSE);
96
+ ```javascript
97
+ const output = await validate(`file://${__dirname}/string.schema.json`, "foo");
98
+ ```
99
+
100
+ **Media type plugins**
101
+
102
+ There is a plugin system for adding support for different media types. By
103
+ default it's configured to accept schemas that have the
104
+ `application/schema+json` Content-Type (web) or a `.schema.json` file extension
105
+ (filesystem). If, for example, you want to fetch schemas that are written in
106
+ YAML, you can add a MediaTypePlugin to support that.
107
+
108
+ ```javascript
109
+ import { addMediaTypePlugin, validate } from "@hyperjump/json-schema/draft-2020-12";
110
+ import YAML from "yaml";
83
111
 
84
- // Example: Specify meta-validation output format
85
- JsonSchema.setMetaOutputFormat(JsonSchema.FLAG);
86
112
 
87
- // Example: Disable meta-validation
88
- JsonSchema.setShouldMetaValidate(false);
113
+ // Add support for JSON Schemas written in YAML
114
+ addMediaTypePlugin("application/schema+yaml", {
115
+ parse: async (response) => [YAML.parse(await response.text()), undefined],
116
+ matcher: (path) => path.endsWith(".schema.yaml")
117
+ });
118
+
119
+ // Example: Fetch schema with Content-Type: application/schema+yaml from the web
120
+ const isString = await validate("http://example.com/schemas/string");
121
+
122
+ // Example: Fetch from file with JSON Schema YAML file extension
123
+ const isString = await validate(`file://${__dirname}/string.schema.yaml`);
124
+
125
+ // Then validate against your schema like normal
126
+ const output = isString("foo");
89
127
  ```
90
128
 
91
- ### Media Types
92
- JSV has a plugin system for adding support for different media types. By default
93
- it's configured to accept schemas that have the `application/schema+json`
94
- Content-Type (web) or a `.schema.json` file extension (filesystem). If for
95
- example, you want to fetch schemas that are written in YAML, you can add a
96
- MediaTypePlugin to support that.
129
+ ## API
130
+ These are available from any of the exports that refer to a version of JSON
131
+ Schema, such as `@hyperjump/json-schema/draft-2020-12`.
132
+
133
+ * **addSchema**: (schema: object, retrievalUri?: string, defaultDialectId?: string) => void
97
134
 
98
- * **Core.addMediaTypePlugin**: (contentType: string, plugin: MediaTypePlugin) => void
135
+ Load a schema manually rather than fetching it from the filesystem or over
136
+ the network.
137
+ * **validate**: (schemaURI: string, instance: any, outputFormat: OutputFormat = FLAG) => Promise<OutputUnit>
138
+
139
+ Validate an instance against a schema. This function is curried to allow
140
+ compiling the schema once and applying it to multiple instances.
141
+ * **validate**: (schemaURI: string) => Promise<(instance: any, outputFormat: OutputFormat = FLAG) => OutputUnit>
142
+
143
+ Compiling a schema to a validation function.
144
+ * **FLAG**: "FLAG"
145
+
146
+ An identifier for the `FLAG` output format as defined by the 2019-09 and
147
+ 2020-12 specifications.
148
+ * **InvalidSchemaError**: Error & { output: OutputUnit }
149
+
150
+ This error is thrown if the schema being compiled is found to be invalid.
151
+ The `output` field contains an `OutputUnit` with information about the
152
+ error. You can use the `setMetaOutputFormat` configuration to set the output
153
+ format that is returned in `output`.
154
+ * **setMetaOutputFormat**: (outputFormat: OutputFormat) => void
155
+
156
+ Set the output format used for validating schemas.
157
+ * **getMetaOutputFormat**: () => OutputFormat
158
+
159
+ Get the output format used for validating schemas.
160
+ * **setShouldMetaValidate**: (isEnabled: boolean) => void
161
+
162
+ Enable or disable validating schemas.
163
+ * **getShouldMetaValidate**: (isEnabled: boolean) => void
164
+
165
+ Determine if validating schemas is enabled.
166
+ * **addMediaTypePlugin**: (contentType: string, plugin: MediaTypePlugin) => void
99
167
 
100
168
  Add a custom media type handler to support things like YAML or to change the
101
169
  way JSON is supported.
170
+
171
+ **Type Definitions**
172
+
173
+ The following types are used in the above definitions
174
+
175
+ * **OutputFormat**: **FLAG**
176
+
177
+ Only the `FLAG` output format is part of the Stable API. Additional output
178
+ formats are included as part of the Experimental API.
179
+ * **OutputUnit**: { valid: boolean }
180
+
181
+ Output is an experimental feature of the JSON Schema specification. There
182
+ may be additional fields present in the OutputUnit, but only the `valid`
183
+ property should be considered part of the Stable API.
102
184
  * **MediaTypePlugin**: object
103
185
 
104
- * parse: (response: Response, mediaTypeParameters: object) => [SchemaObject, string]
186
+ * parse: (response: Response, mediaTypeParameters: object) => [object | boolean, string?]
105
187
 
106
188
  Given a fetch Response object, parse the body of the request. Return the
107
189
  parsed schema and an optional default dialectId.
@@ -110,96 +192,353 @@ MediaTypePlugin to support that.
110
192
  Given a filesystem path, return whether or not the file should be
111
193
  considered a member of this media type.
112
194
 
195
+ ## Experimental
196
+ The JSON Schema specification includes several features that are experimental in
197
+ nature including the Vocabulary System, Output Formats, and Annotations. This
198
+ implementation aims to support only the latest version of experimental features
199
+ as they evolve. There will not be a major version bump if there needs to be
200
+ backward incompatible changes to the Experimental API.
201
+
202
+ ### Usage
203
+ All experimental features are segregated into exports that include the word
204
+ "experimental" so you never accidentally depend on something that could change
205
+ or be removed in future releases.
206
+
113
207
  ```javascript
114
- const JsonSchema = require("@hyperjump/json-schema");
115
- const YAML = require("yaml");
208
+ import { BASIC } from "@hyperjump/json-schema/experimental";
209
+ ```
116
210
 
211
+ **Change the validation output format**
117
212
 
118
- // Add support for JSON Schemas written in YAML
119
- JsonSchema.addMediaTypePlugin("application/schema+yaml", {
120
- parse: async (response) => [YAML.parse(await response.text()), undefined],
121
- matcher: (path) => path.endsWith(".schema.yaml")
122
- });
213
+ The `FLAG` output format isn't very informative. You can change the output
214
+ format used for validation to get more information.
123
215
 
124
- // Example: Fetch schema with Content-Type: application/schema+yaml from the web
125
- const schema = await JsonSchema.get("http://example.com/schemas/string");
216
+ ```javascript
217
+ const output = await validate("https://example.com/schema1", 42, BASIC);
218
+ ```
126
219
 
127
- // Example: Fetch from file with JSON Schema YAML file extension
128
- const schema = await JsonSchema.get("file:///path/to/my/schemas/string.schema.yaml");
220
+ **Change the schema validation output format**
129
221
 
130
- // Then validate against your schema like normal
222
+ The output format used for validating schemas can be changed as well.
223
+
224
+ ```javascript
225
+ setMetaOutputFormat(BASIC);
226
+ try {
227
+ const output = await validate("https://example.com/invalid-schema");
228
+ } catch (error) {
229
+ console.log(error.output);
230
+ }
131
231
  ```
132
232
 
133
- ## TypeScript
134
- Although the package is written in JavaScript, type definitions are included for
135
- TypeScript support. The following example shows the types you might want to
136
- know.
233
+ **Keywords, Vocabularies, and Dialects**
137
234
 
138
- ```typescript
139
- import JsonSchema, { InvalidSchemaError } from "@hyperjump/json-schema";
140
- import type { SchemaDocument, Validator, Result, Draft202012Schema } from "@hyperjump/json-schema";
235
+ In order to create and use a custom keyword, you need to define your keyword's
236
+ behavior, create a vocabulary that includes that keyword, and then create a
237
+ dialect that includes your vocabulary.
141
238
 
239
+ ```javascript
240
+ import { addSchema, validate } from "@hyperjump/json-schema/draft-2020-12";
241
+ import { addKeyword, defineVocabulary, Validation } from "@hyperjump/json-schema/experimental";
242
+ import * as Schema from "@hyperjump/json-schema/schema/experimental";
142
243
 
143
- const schemaJson: Draft202012Schema = {
144
- "$id": "https://json-schema.hyperjump.io/schema",
145
- "$schema": "https://json-schema.org/draft/2020-12/schema",
146
244
 
147
- "type": "string"
148
- };
149
- JsonSchema.add(schemaJson);
245
+ // Define a keyword that's an array of schemas that are applied sequentially
246
+ // using implication: A -> B -> C -> D
247
+ addKeyword({
248
+ id: "https://example.com/keyword/implication",
150
249
 
151
- const schema: SchemaDocument = await JsonSchema.get("https://json-schema.hyperjump.io/schema");
152
- try {
153
- const isString: Validator = await JsonSchema.validate(schema);
154
- const result: Result = isString("foo");
155
- console.log("isString:", result.valid);
156
- } catch (error: unknown) {
157
- if (error instanceof InvalidSchemaError) {
158
- console.log(error.output);
159
- } else {
160
- console.log(error);
250
+ compile: (schema, ast) => {
251
+ return Schema.map(async (itemSchema) => Validation.compile(await itemSchema, ast), schema);
252
+ },
253
+
254
+ interpret: (implies, instance, ast, dynamicAnchors) => {
255
+ return implies.reduce((acc, schema) => {
256
+ return !acc || Validation.interpret(schema, instance, ast, dynamicAnchors);
257
+ }, true);
161
258
  }
162
- }
259
+ });
260
+
261
+ // Create a vocabulary with this keyword and call it "implies"
262
+ defineVocabulary("https://example.com/vocab/logic", {
263
+ "implies": "https://example.com/keyword/implication"
264
+ });
265
+
266
+ // Create a vocabulary schema for this vocabulary
267
+ addSchema({
268
+ "$id": "https://example.com/meta/logic",
269
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
270
+
271
+ "$dynamicAnchor": "meta",
272
+ "properties": {
273
+ "implies": {
274
+ "type": "array",
275
+ "items": { "$dynamicRef": "meta" },
276
+ "minItems": 2
277
+ }
278
+ }
279
+ });
280
+
281
+ // Create a dialect schema adding this vocabulary to the standard JSON Schema
282
+ // vocabularies
283
+ addSchema({
284
+ "$id": "https://example.com/dialect/logic",
285
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
286
+
287
+ "$vocabulary": {
288
+ "https://json-schema.org/vocab/core": true,
289
+ "https://json-schema.org/vocab/applicator": true,
290
+ "https://json-schema.org/vocab/unevaluated": true,
291
+ "https://json-schema.org/vocab/validation": true,
292
+ "https://json-schema.org/vocab/meta-data": true,
293
+ "https://json-schema.org/vocab/format-annotation": true,
294
+ "https://json-schema.org/vocab/content": true,
295
+ "https://example.com/vocab/logic": true
296
+ },
297
+
298
+ "$dynamicAnchor": "meta",
299
+
300
+ "allOf": [
301
+ { "$ref": "https://json-schema.org/draft/2020-12/schema" },
302
+ { "$ref": "/meta/logic" }
303
+ ]
304
+ });
305
+
306
+ // Use your dialect to validate a JSON instance
307
+ addSchema({
308
+ "$schema": "https://example.com/dialect/logic",
309
+
310
+ "type": "number",
311
+ "implies": [
312
+ { "minimum": 10 },
313
+ { "multipleOf": 2 }
314
+ ]
315
+ }, "https://example.com/schema1");
316
+ const output = await validate("https://example.com/schema1", 42);
163
317
  ```
164
318
 
165
- ## API
166
- * **add**: (schema: object, url?: URI, dialectId?: string) => SDoc
319
+ **Custom Meta Schema**
167
320
 
168
- Load a schema. See [JSC - $id](https://github.com/hyperjump-io/json-schema-core#id)
169
- and [JSC - $schema](https://github.com/hyperjump-io/json-schema-core#schema-1)
170
- for more information.
171
- * **get**: (url: URI, contextDoc?: SDoc, recursive: boolean = false) => Promise<SDoc>
321
+ You can use a custom meta-schema to restrict users to a subset of JSON Schema
322
+ functionality. This example requires that no unknown keywords are used in the
323
+ schema.
172
324
 
173
- Fetch a schema. Schemas can come from an HTTP request, a file, or a schema
174
- that was added with `add`.
175
- * **validate**: (schema: SDoc, instance: any, outputFormat: OutputFormat = FLAG) => Promise<OutputUnit>
325
+ ```javascript
326
+ addSchema({
327
+ "$id": "https://example.com/meta-schema1",
328
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
176
329
 
177
- Validate an instance against a schema. The function is curried to allow
178
- compiling the schema once and applying it to multiple instances.
179
- * **compile**: (schema: SDoc) => Promise<CompiledSchema>
330
+ "$vocabulary": {
331
+ "https://json-schema.org/draft/2020-12/vocab/core": true,
332
+ "https://json-schema.org/draft/2020-12/vocab/applicator": true,
333
+ "https://json-schema.org/draft/2020-12/vocab/unevaluated": true,
334
+ "https://json-schema.org/draft/2020-12/vocab/validation": true,
335
+ "https://json-schema.org/draft/2020-12/vocab/meta-data": true,
336
+ "https://json-schema.org/draft/2020-12/vocab/format-annotation": true,
337
+ "https://json-schema.org/draft/2020-12/vocab/content": true
338
+ },
180
339
 
181
- Compile a schema to be interpreted later. A compiled schema is a JSON
182
- serializable structure that can be serialized an restored for later use.
183
- * **interpret**: (schema: CompiledSchema, instance: any, outputFormat: OutputFormat = FLAG) => OutputUnit
340
+ "$dynamicAnchor": "meta",
341
+
342
+ "$ref": "https://json-schema.org/draft/2020-12/schema",
343
+ "unevaluatedProperties": false
344
+ });
345
+
346
+ addSchema({
347
+ $schema: "https://example.com/meta-schema1",
348
+ type: "number",
349
+ foo: 42
350
+ }, "https://example.com/schema1");
351
+
352
+ const output = await validate("https://example.com/schema1", 42); // Expect InvalidSchemaError
353
+ ```
354
+
355
+ ### API
356
+ These are available from the `@hyperjump/json-schema/experimental` export.
357
+
358
+ * **compile**: (schema: SchemaDocument) => Promise<CompiledSchema>
359
+
360
+ Return a compiled schema. This is useful if you're creating tooling for
361
+ something other than validation.
362
+ * **interpret**: (schema: CompiledSchema, instance: Instance, outputFormat: OutputFormat = BASIC) => OutputUnit
184
363
 
185
364
  A curried function for validating an instance against a compiled schema.
186
- * **setMetaOutputFormat**: (outputFormat: OutputFormat = DETAILED) => undefined
365
+ This can be useful for creating custom output formats.
366
+ * **addKeyword**: (keywordHandler: Keyword) => void
367
+
368
+ Define a keyword for use in a vocabulary.
369
+ * **defineVocabulary**: (id: string, keywords: { [keyword: string]: string }) => void
370
+
371
+ Define a vocabulary that maps keyword name to keyword URIs defined using
372
+ `addKeyword`.
373
+ * **getKeyword**: (keywordId: string) => Keyword
374
+
375
+ Get a keyword object by its URI. This is useful for building non-validation
376
+ tooling.
377
+ * **getKeywordName**: (dialectId: string, keywordId: string) => string
378
+
379
+ Determine a keyword's name given its URI a dialect URI. This is useful when
380
+ defining a keyword that depends on the value of another keyword (such as how
381
+ `contains` depends on `minContains` and `maxContains`).
382
+ * **loadDialect**: (dialectId: string, dialect: { [vocabularyId: string] }) => void
383
+
384
+ Define a dialect. In most cases, dialects are loaded automatically from the
385
+ `$vocabulary` keyword in the meta-schema. The only time you would need to
386
+ load a dialect manually is if you're creating a distinct version of JSON
387
+ Schema rather than creating a dialect of an existing version of JSON Schema.
388
+ * **Validation**: Keyword
389
+
390
+ A Keyword object that represents a "validate" operation. You would use this
391
+ for compiling and evaluating sub-schemas when defining a custom keyword.
392
+
393
+ **Type Definitions**
394
+
395
+ The following types are used in the above definitions
396
+
397
+ * **OutputFormat**: **FLAG** | **BASIC** | **DETAILED** | **VERBOSE**
398
+
399
+ In addition to the `FLAG` output format in the Stable API, the Experimental
400
+ API includes support for the `BASIC`, `DETAILED`, and `VERBOSE` formats as
401
+ specified in the 2019-09 specification (with some minor customizations).
402
+ This implementation doesn't include annotations or human readable error
403
+ messages. The output can be processed to create human readable error
404
+ messages as needed.
405
+ * **Keyword**: object
406
+ * id: string
407
+
408
+ A URI that uniquely identifies the keyword. It should use a domain you
409
+ own to avoid conflict with keywords defined by others.
410
+ * compile: (schema: SchemaDocument, ast: AST, parentSchema: SchemaDocument) => Promise<A>
411
+
412
+ This function takes the keyword value, does whatever preprocessing it
413
+ can on it without an instance, and returns the result. The returned
414
+ value will be passed to the `interpret` function. The `ast` parameter is
415
+ needed for compiling sub-schemas. The `parentSchema` parameter is
416
+ primarily useful for looking up the value of an adjacent keyword that
417
+ might effect this one.
418
+ * interpret: (compiledKeywordValue: A, instance: JsonDocument, ast: AST, dynamicAnchors: Anchors) => boolean
419
+
420
+ This function takes the value returned by the `compile` function and the
421
+ instance value that is being validated and returns whether the value is
422
+ valid or not. The other parameters are only needed for validating
423
+ sub-schemas.
424
+ * collectEvaluatedProperties?: (compiledKeywordValue: A, instance: JsonDocument, ast: AST, dynamicAnchors: Anchors) => string[] | false
425
+
426
+ If the keyword is an applicator, it will need to implements this
427
+ function for `unevaluatedProperties` to work as expected.
428
+ * collectEvaluatedItems?: (compiledKeywordValue: A, instance: JsonDocument, ast: AST, dynamicAnchors: Anchors) => Set<number> | false
429
+
430
+ If the keyword is an applicator, it will need to implements this
431
+ function for `unevaluatedItems` to work as expected.
432
+
433
+ ### Schema
434
+ These functions are available from the
435
+ `@hyperjump/json-schema/schema/experimental` export.
436
+
437
+ This library uses SchemaDocument objects to represent a value in a schema.
438
+ You'll work with these objects if you create a custom keyword. This module is a
439
+ set of functions for working with SchemaDocuments.
440
+
441
+ * **Schema.add**: (schema: object, retrievalUri?: string, dialectId?: string) => string
442
+
443
+ Load a schema. Returns the identifier for the schema.
444
+ * **Schema.get**: (url: string, contextDoc?: SchemaDocument) => Promise<SchemaDocument>
445
+
446
+ Fetch a schema. Schemas can come from an HTTP request, a file, or a schema
447
+ that was added with `Schema.add`.
448
+ * **Schema.uri**: (doc: SchemaDocument) => string
449
+
450
+ Returns a URI for the value the SchemaDocument represents.
451
+ * **Schema.value**: (doc: SchemaDocument) => any
452
+
453
+ Returns the value the SchemaDocument represents.
454
+ * **Schema.typeOf**: (doc: SchemaDocument, type: string) => boolean
455
+
456
+ Determines if the JSON type of the given doc matches the given type.
457
+ * **Schema.has**: (key: string, doc: SchemaDocument) => Promise<SchemaDocument>
458
+
459
+ Similar to `key in schema`.
460
+ * **Schema.step**: (key: string, doc: SchemaDocument) => Promise<SchemaDocument>
461
+
462
+ Similar to `schema[key]`, but returns an SchemaDocument.
463
+ * **Schema.entries**: (doc: SchemaDocument) => Promise<[[string, SchemaDocument]]>
464
+
465
+ Similar to `Object.entries`, but returns SchemaDocuments for values.
466
+ * **Schema.keys**: (doc: SchemaDocument) => [string]
467
+
468
+ Similar to `Object.keys`.
469
+ * **Schema.map**: (fn: (item: Promise<SchemaDocument>, index: integer) => T, doc: SchemaDocument) => Promise<[T]>
470
+
471
+ A `map` function for an SchemaDocument whose value is an array.
472
+ * **Schema.length**: (doc: SchemaDocument) => number
473
+
474
+ Similar to `Array.prototype.length`.
475
+ * **Schema.toSchema**: (doc: SchemaDocument, options: ToSchemaOptions) => object
476
+
477
+ Get a raw schema from a Schema Document.
478
+
479
+ **Type Definitions**
480
+
481
+ The following types are used in the above definitions
482
+
483
+ * **ToSchemaOptions**: object
484
+
485
+ * parentId: string (default: "") -- `file://` URIs will be generated
486
+ relative to this path.
487
+ * parentDialect: string (default: "") -- If the dialect of the schema
488
+ * matches this value, the `$schema` keyword will be omitted.
489
+ * includeEmbedded: boolean (default: true) -- If false, embedded schemas
490
+ will be unbundled from the schema.
491
+
492
+ ### Instance
493
+ These functions are available from the
494
+ `@hyperjump/json-schema/instance/experimental` export.
495
+
496
+ This library uses InstanceDocument objects to represent a value in an instance.
497
+ You'll work with these objects if you create a custom keyword. This module is a
498
+ set of functions for working with InstanceDocuments.
499
+
500
+ * **Instance.cons**: (instance: any, uri?: string) => InstanceDocument
501
+
502
+ Construct an InstanceDocument from a value.
503
+ * **Instance.get**: (url: string, contextDoc: InstanceDocument) => InstanceDocument
504
+
505
+ Apply a same-resource reference to a InstanceDocument.
506
+ * **Instance.uri**: (doc: InstanceDocument) => string
507
+
508
+ Returns a URI for the value the InstanceDocument represents.
509
+ * **Instance.value**: (doc: InstanceDocument) => any
510
+
511
+ Returns the value the InstanceDocument represents.
512
+ * **Instance.has**: (key: string, doc: InstanceDocument) => any
513
+
514
+ Similar to `key in instance`.
515
+ * **Instance.typeOf**: (doc: InstanceDocument, type: string) => boolean
516
+
517
+ Determines if the JSON type of the given doc matches the given type.
518
+ * **Instance.step**: (key: string, doc: InstanceDocument) => InstanceDocument
519
+
520
+ Similar to `schema[key]`, but returns a InstanceDocument.
521
+ * **Instance.entries**: (doc: InstanceDocument) => [string, InstanceDocument]
522
+
523
+ Similar to `Object.entries`, but returns IDocs for values.
524
+ * **Instance.keys**: (doc: InstanceDocument) => [string]
525
+
526
+ Similar to `Object.keys`.
527
+ * **Instance.map**: (fn: (item: InstanceDocument, index: integer) => T, doc: InstanceDocument) => [T]
187
528
 
188
- Set the output format for meta-validation. Meta-validation output is only
189
- returned if meta-validation results in an error.
190
- * **setShouldMetaValidate**: (isEnabled: boolean) => undefined
529
+ A `map` function for an InstanceDocument whose value is an array.
530
+ * **Instance.reduce**: (fn: (accumulator: T, item: InstanceDocument, index: integer) => T, initial: T, doc: InstanceDocument) => T
191
531
 
192
- Enable or disable meta-validation.
193
- * **OutputFormat**: [**FLAG** | **BASIC** | **DETAILED** | **VERBOSE**]
532
+ A `reduce` function for an InstanceDocument whose value is an array.
533
+ * **Instance.every**: (fn: (doc: InstanceDocument, index: integer) => boolean, doc: InstanceDocument) => boolean
194
534
 
195
- See [JSC - Output](https://github.com/hyperjump-io/json-schema-core#output)
196
- for more information on output formats.
535
+ An `every` function for an InstanceDocument whose value is an array.
536
+ * **Instance.some**: (fn: (doc: InstanceDocument, index: integer) => boolean, doc: InstanceDocument) => boolean
197
537
 
198
- ## Not (yet) Supported
199
- This implementation supports all required features of JSON Schema. The following
200
- optional features are not supported yet.
538
+ A `some` function for an InstanceDocument whose value is an array.
539
+ * **Instance.length**: (doc: InstanceDocument) => number
201
540
 
202
- * The format-assertion vocabulary
541
+ Similar to `Array.prototype.length`.
203
542
 
204
543
  ## Contributing
205
544