@hyperjump/json-schema 1.6.6 → 1.7.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 (114) hide show
  1. package/README.md +247 -255
  2. package/annotations/annotated-instance.js +3 -3
  3. package/annotations/index.d.ts +7 -1
  4. package/annotations/index.js +3 -3
  5. package/bundle/index.d.ts +1 -5
  6. package/bundle/index.js +125 -121
  7. package/draft-04/additionalItems.js +6 -7
  8. package/draft-04/dependencies.js +5 -5
  9. package/draft-04/index.js +2 -2
  10. package/draft-04/items.js +5 -5
  11. package/draft-04/maximum.js +8 -8
  12. package/draft-04/minimum.js +8 -8
  13. package/draft-06/contains.js +2 -2
  14. package/draft-06/index.js +3 -2
  15. package/draft-07/index.js +3 -2
  16. package/draft-2019-09/index.js +9 -11
  17. package/draft-2020-12/dynamicRef.js +5 -5
  18. package/draft-2020-12/index.js +11 -13
  19. package/lib/common.d.ts +1 -1
  20. package/lib/common.js +44 -60
  21. package/lib/configuration.js +0 -6
  22. package/lib/core.js +32 -30
  23. package/lib/experimental.d.ts +75 -5
  24. package/lib/experimental.js +2 -2
  25. package/lib/index.d.ts +43 -11
  26. package/lib/index.js +11 -11
  27. package/lib/instance.d.ts +1 -17
  28. package/lib/instance.js +3 -3
  29. package/lib/keywords/additionalProperties.js +12 -13
  30. package/lib/keywords/allOf.js +3 -3
  31. package/lib/keywords/anyOf.js +3 -3
  32. package/lib/keywords/conditional.js +6 -7
  33. package/lib/keywords/const.js +2 -2
  34. package/lib/keywords/contains.js +14 -35
  35. package/lib/keywords/contentSchema.js +1 -1
  36. package/lib/keywords/definitions.js +2 -2
  37. package/lib/keywords/dependentRequired.js +4 -4
  38. package/lib/keywords/dependentSchemas.js +5 -5
  39. package/lib/keywords/dynamicRef.js +10 -5
  40. package/lib/keywords/else.js +5 -6
  41. package/lib/keywords/enum.js +4 -4
  42. package/lib/keywords/exclusiveMaximum.js +3 -3
  43. package/lib/keywords/exclusiveMinimum.js +3 -3
  44. package/lib/keywords/if.js +1 -1
  45. package/lib/keywords/itemPattern.js +17 -14
  46. package/lib/keywords/items.js +6 -7
  47. package/lib/keywords/maxItems.js +3 -3
  48. package/lib/keywords/maxLength.js +3 -3
  49. package/lib/keywords/maxProperties.js +3 -3
  50. package/lib/keywords/maximum.js +3 -3
  51. package/lib/keywords/meta-data.js +1 -1
  52. package/lib/keywords/minItems.js +3 -3
  53. package/lib/keywords/minLength.js +3 -3
  54. package/lib/keywords/minProperties.js +3 -3
  55. package/lib/keywords/minimum.js +3 -3
  56. package/lib/keywords/multipleOf.js +3 -3
  57. package/lib/keywords/not.js +1 -1
  58. package/lib/keywords/oneOf.js +3 -3
  59. package/lib/keywords/pattern.js +3 -3
  60. package/lib/keywords/patternProperties.js +5 -5
  61. package/lib/keywords/prefixItems.js +5 -5
  62. package/lib/keywords/properties.js +5 -5
  63. package/lib/keywords/propertyDependencies.js +6 -7
  64. package/lib/keywords/propertyNames.js +2 -2
  65. package/lib/keywords/ref.js +2 -7
  66. package/lib/keywords/requireAllExcept.js +8 -9
  67. package/lib/keywords/required.js +3 -3
  68. package/lib/keywords/then.js +5 -5
  69. package/lib/keywords/type.js +9 -3
  70. package/lib/keywords/unevaluatedItems.js +4 -4
  71. package/lib/keywords/unevaluatedProperties.js +4 -5
  72. package/lib/keywords/uniqueItems.js +3 -3
  73. package/lib/keywords/validation.js +11 -23
  74. package/lib/keywords.js +30 -4
  75. package/lib/openapi.js +19 -6
  76. package/lib/schema.js +235 -233
  77. package/openapi-3-0/index.js +5 -5
  78. package/openapi-3-0/type.js +13 -7
  79. package/openapi-3-1/index.d.ts +1 -1
  80. package/openapi-3-1/index.js +22 -21
  81. package/openapi-3-1/{schema-base/2022-10-07.js → schema-base.js} +12 -2
  82. package/openapi-3-1/schema-draft-04.js +33 -0
  83. package/openapi-3-1/schema-draft-06.js +33 -0
  84. package/openapi-3-1/schema-draft-07.js +33 -0
  85. package/openapi-3-1/schema-draft-2019-09.js +33 -0
  86. package/openapi-3-1/schema-draft-2020-12.js +33 -0
  87. package/package.json +14 -16
  88. package/stable/index.js +10 -10
  89. package/annotations/tests/applicators.json +0 -375
  90. package/annotations/tests/content.json +0 -57
  91. package/annotations/tests/core.json +0 -33
  92. package/annotations/tests/format.json +0 -20
  93. package/annotations/tests/meta-data.json +0 -128
  94. package/annotations/tests/unevaluated.json +0 -557
  95. package/annotations/tests/unknown.json +0 -87
  96. package/annotations/tests/validation.json +0 -328
  97. package/annotations/validation-error.d.ts +0 -8
  98. package/bundle/file.json +0 -57
  99. package/draft-2019-09/contains.js +0 -44
  100. package/lib/configuration.d.ts +0 -9
  101. package/lib/context-uri.browser.js +0 -1
  102. package/lib/context-uri.js +0 -4
  103. package/lib/core.d.ts +0 -48
  104. package/lib/fetch.browser.js +0 -1
  105. package/lib/fetch.js +0 -20
  106. package/lib/invalid-schema-error.d.ts +0 -8
  107. package/lib/keywords.d.ts +0 -19
  108. package/lib/media-types.d.ts +0 -11
  109. package/lib/media-types.js +0 -48
  110. package/lib/reference.d.ts +0 -11
  111. package/lib/reference.js +0 -11
  112. package/lib/schema.d.ts +0 -60
  113. /package/openapi-3-0/{schema/2021-09-28.js → schema.js} +0 -0
  114. /package/openapi-3-1/{schema/2022-10-07.js → schema.js} +0 -0
package/README.md CHANGED
@@ -2,47 +2,35 @@
2
2
 
3
3
  A collection of modules for working with JSON Schemas.
4
4
 
5
- * Validate JSON-compatible values against a JSON Schema
5
+ * Validate JSON-compatible values against a JSON Schemas
6
6
  * Dialects: draft-2020-12, draft-2019-09, draft-07, draft-06, draft-04
7
- * OpenAPI
8
- * Versions/Dialects: 3.0, 3.1
9
- * Validate an OpenAPI document
10
- * Validate values against a schema from an OpenAPI document
11
7
  * Schemas can reference other schemas using a different dialect
12
8
  * Work directly with schemas on the filesystem or HTTP
9
+ * OpenAPI
10
+ * Versions: 3.0, 3.1
11
+ * Validate an OpenAPI document
12
+ * Validate values against a schema from an OpenAPI document
13
13
  * Create custom keywords, vocabularies, and dialects
14
14
  * Bundle multiple schemas into one document
15
15
  * Uses the process defined in the 2020-12 specification but works with any
16
16
  dialect.
17
- * Provides utilities for building non-validation JSON Schema tooling
18
- * Provides utilities for working with annotations
17
+ * Utilities for building non-validation JSON Schema tooling
18
+ * Utilities for working with annotations
19
19
 
20
20
  ## Install
21
+
21
22
  Includes support for node.js/bun.js (ES Modules, TypeScript) and browsers (works
22
23
  with CSP
23
24
  [`unsafe-eval`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src#unsafe_eval_expressions)).
24
25
 
25
26
  ### Node.js
27
+
26
28
  ```bash
27
29
  npm install @hyperjump/json-schema
28
30
  ```
29
31
 
30
- ### Browser
31
- When in a browser context, this library is designed to use the browser's `fetch`
32
- implementation instead of a node.js fetch clone. The Webpack bundler does this
33
- properly without any extra configuration, but if you are using the Rollup
34
- bundler you will need to include the `browser: true` option in your Rollup
35
- configuration.
36
-
37
- ```javascript
38
- plugins: [
39
- resolve({
40
- browser: true
41
- })
42
- ]
43
- ```
44
-
45
32
  ### TypeScript
33
+
46
34
  This package uses the package.json "exports" field. [TypeScript understands
47
35
  "exports"](https://devblogs.microsoft.com/typescript/announcing-typescript-4-5-beta/#packagejson-exports-imports-and-self-referencing),
48
36
  but you need to change a couple settings in your `tsconfig.json` for it to work.
@@ -53,36 +41,40 @@ but you need to change a couple settings in your `tsconfig.json` for it to work.
53
41
  ```
54
42
 
55
43
  ### Versioning
44
+
56
45
  The API for this library is divided into two categories: Stable and
57
- Experimental. The Stable API strictly follows semantic versioning, but the
58
- Experimental API may have backward-incompatible changes between minor versions.
46
+ Experimental. The Stable API follows semantic versioning, but the Experimental
47
+ API may have backward-incompatible changes between minor versions.
59
48
 
60
49
  All experimental features are segregated into exports that include the word
61
50
  "experimental" so you never accidentally depend on something that could change
62
51
  or be removed in future releases.
63
52
 
64
53
  ## Validation
54
+
65
55
  ### Usage
56
+
66
57
  This library supports many versions of JSON Schema. Use the pattern
67
58
  `@hyperjump/json-schema/*` to import the version you need.
68
59
 
69
60
  ```javascript
70
- import { addSchema, validate } from "@hyperjump/json-schema/draft-2020-12";
61
+ import { registerSchema, validate } from "@hyperjump/json-schema/draft-2020-12";
71
62
  ```
72
63
 
73
64
  You can import support for additional versions as needed.
74
65
 
75
66
  ```javascript
76
- import { addSchema, validate } from "@hyperjump/json-schema/draft-2020-12";
67
+ import { registerSchema, validate } from "@hyperjump/json-schema/draft-2020-12";
77
68
  import "@hyperjump/json-schema/draft-07";
78
69
  ```
79
70
 
80
71
  **Note**: The default export (`@hyperjump/json-schema`) is reserved for the
81
- stable version of JSON Schema that will hopefully be released in 2023.
72
+ stable version of JSON Schema that will hopefully be released in near future.
82
73
 
83
74
  **Validate schema from JavaScript**
75
+
84
76
  ```javascript
85
- addSchema({
77
+ registerSchema({
86
78
  $schema: "https://json-schema.org/draft/2020-12/schema",
87
79
  type: "string"
88
80
  }, "http://example.com/schemas/string");
@@ -116,50 +108,27 @@ const output = await validate("http://example.com/schemas/string", "foo");
116
108
  ```
117
109
 
118
110
  When running on the server, you can also load schemas directly from the
119
- filesystem using `file:` URIs. When fetching from the file system, there are
120
- limitations for security reasons. If your schema has an identifier with an
121
- http(s) scheme (**https**://example.com), it's not allowed to reference schemas
122
- with a file scheme (**file**:///path/to/my/schemas).
111
+ filesystem. When fetching from the file system, there are limitations for
112
+ security reasons. You can only reference a schema identified by a file URI
113
+ scheme (**file**:///path/to/my/schemas) from another schema identified by a file
114
+ URI scheme. Also, a schema is not allowed to self-identify (`$id`) with a
115
+ `file:` URI scheme.
123
116
 
124
117
  ```javascript
125
118
  const output = await validate(`file://${__dirname}/string.schema.json`, "foo");
126
119
  ```
127
120
 
128
121
  If the schema URI is relative, the base URI in the browser is the browser
129
- location and the base URI on the server is the current working directory.
122
+ location and the base URI on the server is the current working directory. This
123
+ is the preferred way to work with file-based schemas on the server.
130
124
 
131
125
  ```javascript
132
126
  const output = await validate(`./string.schema.json`, "foo");
133
127
  ```
134
128
 
135
- **Media type plugins**
136
-
137
- There is a plugin system for adding support for different media types. By
138
- default it's configured to accept schemas that have the
139
- `application/schema+json` Content-Type (web) or a `.schema.json` file extension
140
- (filesystem). If, for example, you want to fetch schemas that are written in
141
- YAML, you can add a MediaTypePlugin to support that.
142
-
143
- ```javascript
144
- import { addMediaTypePlugin, validate } from "@hyperjump/json-schema/draft-2020-12";
145
- import YAML from "yaml";
146
-
147
-
148
- // Add support for JSON Schemas written in YAML
149
- addMediaTypePlugin("application/schema+yaml", {
150
- parse: async (response) => [YAML.parse(await response.text()), undefined],
151
- matcher: (path) => path.endsWith(".schema.yaml")
152
- });
153
-
154
- // Example: Fetch schema with Content-Type: application/schema+yaml from the web
155
- const isString = await validate("http://example.com/schemas/string");
156
-
157
- // Example: Fetch from file with JSON Schema YAML file extension
158
- const isString = await validate(`file://${__dirname}/string.schema.yaml`);
159
-
160
- // Then validate against your schema like normal
161
- const output = isString("foo");
162
- ```
129
+ You can add/modify/remove support for any URI scheme using the [plugin
130
+ system](https://github.com/hyperjump-io/browser/#uri-schemes) provided by
131
+ `@hyperjump/browser`.
163
132
 
164
133
  **OpenAPI**
165
134
 
@@ -171,30 +140,73 @@ of `application/openapi+json` (web) or a file extension of `openapi.json`
171
140
  Use the pattern `@hyperjump/json-schema/*` to import the version you need. The
172
141
  available versions are `openapi-3-0` for 3.0 and `openapi-3-1` for 3.1.
173
142
 
174
- YAML support isn't built in, but you can add it by writing a MediaTypePlugin.
175
- You can use the one at `lib/openapi.js` as an example and replacing the JSON
176
- parts with YAML.
177
-
178
143
  ```javascript
179
- import { addSchema, validate } from "@hyperjump/json-schema/openapi-3-1";
144
+ import { validate } from "@hyperjump/json-schema/openapi-3-1";
180
145
 
181
146
 
182
147
  // Validate an OpenAPI document
183
148
  const output = await validate("https://spec.openapis.org/oas/3.1/schema-base", openapi);
184
149
 
185
150
  // Validate an instance against a schema in an OpenAPI document
186
- const output = await validate(`file://${__dirname}/example.openapi.json#/components/schemas/foo`, 42);
151
+ const output = await validate("./example.openapi.json#/components/schemas/foo", 42);
152
+ ```
153
+
154
+ YAML support isn't built in, but you can add it by writing a
155
+ [MediaTypePlugin](https://github.com/hyperjump-io/browser/#media-types). You can
156
+ use the one at `lib/openapi.js` as an example and replace the JSON parts with
157
+ YAML.
158
+
159
+ **Media types**
160
+
161
+ This library uses media types to determine how to parse a retrieved document. It
162
+ will never assume the retrieved document is a schema. By default it's configured
163
+ to accept documents with a `application/schema+json` Content-Type header (web)
164
+ or a `.schema.json` file extension (filesystem).
165
+
166
+ You can add/modify/remove support for any media-type using the [plugin
167
+ system](https://github.com/hyperjump-io/browser/#media-types) provided by
168
+ `@hyperjump/browser`. The following example shows how to add support for JSON
169
+ Schemas written in YAML.
170
+
171
+ ```javascript
172
+ import YAML from "yaml";
173
+ import contentTypeParser from "content-type";
174
+ import { addMediaTypePlugin } from "@hyperjump/browser";
175
+ import { buildSchemaDocument } from "@hyperjump/json-schema/experimental";
176
+
177
+
178
+ addMediaTypePlugin("application/schema+yaml", {
179
+ parse: async (response) => {
180
+ const contentType = contentTypeParser.parse(response.headers.get("content-type") ?? "");
181
+ const contextDialectId = contentType.parameters.schema ?? contentType.parameters.profile;
182
+
183
+ const foo = YAML.parse(await response.text());
184
+ return buildSchemaDocument(foo, response.url, contextDialectId);
185
+ },
186
+ fileMatcher: (path) => path.endsWith(".schema.yml")
187
+ });
187
188
  ```
188
189
 
189
190
  ### API
191
+
190
192
  These are available from any of the exports that refer to a version of JSON
191
193
  Schema, such as `@hyperjump/json-schema/draft-2020-12`.
192
194
 
193
- * **addSchema**: (schema: object, retrievalUri?: string, defaultDialectId?: string) => void
195
+ * **registerSchema**: (schema: object, retrievalUri?: string, defaultDialectId?: string) => void
196
+
197
+ Add a schema the local schema registry. When this schema is needed, it will
198
+ be loaded from the register rather than the filesystem or network. If a
199
+ schema with the same identifier is already registered, an exception will be
200
+ throw.
201
+ * **unregisterSchema**: (uri: string) => void
202
+
203
+ Remove a schema from the local schema registry.
204
+ * _(deprecated)_ **addSchema**: (schema: object, retrievalUri?: string, defaultDialectId?: string) => void
194
205
 
195
206
  Load a schema manually rather than fetching it from the filesystem or over
196
- the network.
197
- * **validate**: (schemaURI: string, instance: any, outputFormat: OutputFormat = * FLAG) => Promise\<OutputUnit>
207
+ the network. Any schema already registered with the same identifier will be
208
+ replaced with no warning.
209
+ * **validate**: (schemaURI: string, instance: any, outputFormat: OutputFormat = FLAG) => Promise\<OutputUnit>
198
210
 
199
211
  Validate an instance against a schema. This function is curried to allow
200
212
  compiling the schema once and applying it to multiple instances.
@@ -223,10 +235,6 @@ Schema, such as `@hyperjump/json-schema/draft-2020-12`.
223
235
  * **getShouldMetaValidate**: (isEnabled: boolean) => void
224
236
 
225
237
  Determine if validating schemas is enabled.
226
- * **addMediaTypePlugin**: (contentType: string, plugin: MediaTypePlugin) => void
227
-
228
- Add a custom media type handler to support things like YAML or to change the
229
- way JSON is supported.
230
238
 
231
239
  **Type Definitions**
232
240
 
@@ -241,26 +249,11 @@ The following types are used in the above definitions
241
249
  Output is an experimental feature of the JSON Schema specification. There
242
250
  may be additional fields present in the OutputUnit, but only the `valid`
243
251
  property should be considered part of the Stable API.
244
- * **MediaTypePlugin**: object
245
-
246
- * parse: (response: Response, mediaTypeParameters: object) => [object | boolean, string?]
247
-
248
- Given a fetch Response object, parse the body of the request. Return the
249
- parsed schema and an optional default dialectId.
250
- * matcher: (path) => boolean
251
-
252
- Given a filesystem path, return whether or not the file should be
253
- considered a member of this media type.
254
- * quality (optional): string
255
-
256
- The registered media type plugins are used to create the `Accept` header
257
- for HTTP requests. This property allows you to specify a quality value for
258
- your media type. A [quality value](https://developer.mozilla.org/en-US/docs/Glossary/Quality_values)
259
- is a string representation of a number between 0 and 1 with up to three
260
- digits.
261
252
 
262
253
  ## Bundling
254
+
263
255
  ### Usage
256
+
264
257
  You can bundle schemas with external references into a single deliverable using
265
258
  the official JSON Schema bundling process introduced in the 2020-12
266
259
  specification. Given a schema with external references, any external schemas
@@ -272,28 +265,26 @@ references which means you get the same output details whether you validate the
272
265
  bundle or the original unbundled schemas.
273
266
 
274
267
  ```javascript
275
- import { addSchema } from "@hyperjump/json-schema/draft-2020-12";
268
+ import { registerSchema } from "@hyperjump/json-schema/draft-2020-12";
276
269
  import { bundle } from "@hyperjump/json-schema/bundle";
277
270
 
278
- addSchema({
279
- "$id": "https://example.com/main",
271
+
272
+ registerSchema({
280
273
  "$schema": "https://json-schema.org/draft/2020-12/schema",
281
274
 
282
275
  "type": "object",
283
276
  "properties": {
284
277
  "foo": { "$ref": "/string" }
285
278
  }
286
- });
279
+ }, "https://example.com/main");
287
280
 
288
- addSchema({
289
- "$id": "https://example.com/string",
281
+ registerSchema({
290
282
  "$schema": "https://json-schema.org/draft/2020-12/schema",
291
283
 
292
284
  "type": "string"
293
- });
285
+ }, "https://example.com/string");
294
286
 
295
287
  const bundledSchema = await bundle("https://example.com/main"); // {
296
- // "$id": "https://example.com/main",
297
288
  // "$schema": "https://json-schema.org/draft/2020-12/schema",
298
289
  //
299
290
  // "type": "object",
@@ -302,7 +293,7 @@ const bundledSchema = await bundle("https://example.com/main"); // {
302
293
  // },
303
294
  //
304
295
  // "$defs": {
305
- // "https://example.com/string": {
296
+ // "string": {
306
297
  // "$id": "https://example.com/string",
307
298
  // "type": "string"
308
299
  // }
@@ -311,40 +302,40 @@ const bundledSchema = await bundle("https://example.com/main"); // {
311
302
  ```
312
303
 
313
304
  ### API
305
+
314
306
  These are available from the `@hyperjump/json-schema/bundle` export.
315
307
 
316
308
  * **bundle**: (uri: string, options: Options) => Promise\<SchemaObject>
317
309
 
318
310
  Create a bundled schema starting with the given schema. External schemas
319
- will be fetched from the filesystem, the network, or internally as needed.
311
+ will be fetched from the filesystem, the network, or the local schema
312
+ registry as needed.
320
313
 
321
314
  Options:
322
315
  * alwaysIncludeDialect: boolean (default: false) -- Include dialect even
323
316
  when it isn't strictly needed
324
- * bundleMode: "flat" | "full" (default: "flat") -- When bundling schemas
325
- that already contain bundled schemas, "flat" mode with remove nested
326
- embedded schemas and put them all in the top level `$defs`. When using
327
- "full" mode, it will keep the already embedded schemas around, which will
328
- result in some embedded schema duplication.
329
317
  * definitionNamingStrategy: "uri" | "uuid" (default: "uri") -- By default
330
318
  the name used in definitions for embedded schemas will match the
331
- identifier of the embedded schema. This naming is unlikely to collide
332
- with actual definitions, but if you want to be sure, you can use the
333
- "uuid" strategy instead to be sure you get a unique name.
319
+ identifier of the embedded schema. Alternatively, you can use a UUID
320
+ instead of the schema's URI.
334
321
  * externalSchemas: string[] (default: []) -- A list of schemas URIs that
335
322
  are available externally and should not be included in the bundle.
336
323
 
337
- ## Output Formats (Experimental)
338
- ### Usage
324
+ ## Experimental
325
+
326
+ ### Output Formats
339
327
 
340
328
  **Change the validation output format**
341
329
 
342
330
  The `FLAG` output format isn't very informative. You can change the output
343
- format used for validation to get more information about failures.
331
+ format used for validation to get more information about failures. The official
332
+ output format is still evolving, so these may change or be replaced in the
333
+ future.
344
334
 
345
335
  ```javascript
346
336
  import { BASIC } from "@hyperjump/json-schema/experimental";
347
337
 
338
+
348
339
  const output = await validate("https://example.com/schema1", 42, BASIC);
349
340
  ```
350
341
 
@@ -356,6 +347,7 @@ The output format used for validating schemas can be changed as well.
356
347
  import { validate, setMetaSchemaOutputFormat } from "@hyperjump/json-schema/draft-2020-12";
357
348
  import { BASIC } from "@hyperjump/json-schema/experimental";
358
349
 
350
+
359
351
  setMetaSchemaOutputFormat(BASIC);
360
352
  try {
361
353
  const output = await validate("https://example.com/invalid-schema");
@@ -364,28 +356,24 @@ try {
364
356
  }
365
357
  ```
366
358
 
367
- ### API
368
- **Type Definitions**
369
-
370
- * **OutputFormat**: **FLAG** | **BASIC** | **DETAILED** | **VERBOSE**
359
+ ### Custom Keywords, Vocabularies, and Dialects
371
360
 
372
- In addition to the `FLAG` output format in the Stable API, the Experimental
373
- API includes support for the `BASIC`, `DETAILED`, and `VERBOSE` formats as
374
- specified in the 2019-09 specification (with some minor customizations).
375
- This implementation doesn't include annotations or human readable error
376
- messages. The output can be processed to create human readable error
377
- messages as needed.
378
-
379
- ## Meta-Schemas, Keywords, Vocabularies, and Dialects (Experimental)
380
- ### Usage
381
361
  In order to create and use a custom keyword, you need to define your keyword's
382
362
  behavior, create a vocabulary that includes that keyword, and then create a
383
363
  dialect that includes your vocabulary.
384
364
 
365
+ Schemas are represented using the
366
+ [`@hyperjump/browser`](https://github.com/hyperjump-io/browser) package. You'll
367
+ use that API to traverse schemas. `@hyperjump/browser` uses async generators to
368
+ iterate over arrays and objects. If you like using higher order functions like
369
+ `map`/`filter`/`reduce`, see
370
+ [`@hyperjump/pact`](https://github.com/hyperjump-io/pact) for utilities for
371
+ working with generators and async generators.
372
+
385
373
  ```javascript
386
- import { addSchema, validate } from "@hyperjump/json-schema/draft-2020-12";
374
+ import { registerSchema, validate } from "@hyperjump/json-schema/draft-2020-12";
387
375
  import { addKeyword, defineVocabulary, Validation } from "@hyperjump/json-schema/experimental";
388
- import * as Schema from "@hyperjump/json-schema/schema/experimental";
376
+ import * as Browser from "@hyperjump/browser";
389
377
 
390
378
 
391
379
  // Define a keyword that's an array of schemas that are applied sequentially
@@ -393,13 +381,24 @@ import * as Schema from "@hyperjump/json-schema/schema/experimental";
393
381
  addKeyword({
394
382
  id: "https://example.com/keyword/implication",
395
383
 
396
- compile: (schema, ast) => {
397
- return Schema.map(async (itemSchema) => Validation.compile(await itemSchema, ast), schema);
384
+ compile: async (schema, ast) => {
385
+ const subSchemas = [];
386
+ for await (const subSchema of Browser.iter(schema)) {
387
+ subSchemas.push(Validation.compile(subSchema, ast));
388
+ }
389
+ return subSchemas;
390
+
391
+ // Alternative using @hyperjump/pact
392
+ // return pipe(
393
+ // Browser.iter(schema),
394
+ // asyncMap((subSchema) => Validation.compile(subSchema, ast)),
395
+ // asyncCollectArray
396
+ // );
398
397
  },
399
398
 
400
399
  interpret: (implies, instance, ast, dynamicAnchors, quiet) => {
401
- return implies.reduce((acc, schema) => {
402
- return !acc || Validation.interpret(schema, instance, ast, dynamicAnchors, quiet);
400
+ return implies.reduce((valid, schema) => {
401
+ return !valid || Validation.interpret(schema, instance, ast, dynamicAnchors, quiet);
403
402
  }, true);
404
403
  }
405
404
  });
@@ -410,7 +409,7 @@ defineVocabulary("https://example.com/vocab/logic", {
410
409
  });
411
410
 
412
411
  // Create a vocabulary schema for this vocabulary
413
- addSchema({
412
+ registerSchema({
414
413
  "$id": "https://example.com/meta/logic",
415
414
  "$schema": "https://json-schema.org/draft/2020-12/schema",
416
415
 
@@ -426,7 +425,7 @@ addSchema({
426
425
 
427
426
  // Create a dialect schema adding this vocabulary to the standard JSON Schema
428
427
  // vocabularies
429
- addSchema({
428
+ registerSchema({
430
429
  "$id": "https://example.com/dialect/logic",
431
430
  "$schema": "https://json-schema.org/draft/2020-12/schema",
432
431
 
@@ -450,7 +449,7 @@ addSchema({
450
449
  });
451
450
 
452
451
  // Use your dialect to validate a JSON instance
453
- addSchema({
452
+ registerSchema({
454
453
  "$schema": "https://example.com/dialect/logic",
455
454
 
456
455
  "type": "number",
@@ -462,14 +461,14 @@ addSchema({
462
461
  const output = await validate("https://example.com/schema1", 42);
463
462
  ```
464
463
 
465
- **Custom Meta Schema**
464
+ ### Custom Meta Schema
466
465
 
467
466
  You can use a custom meta-schema to restrict users to a subset of JSON Schema
468
467
  functionality. This example requires that no unknown keywords are used in the
469
468
  schema.
470
469
 
471
470
  ```javascript
472
- addSchema({
471
+ registerSchema({
473
472
  "$id": "https://example.com/meta-schema1",
474
473
  "$schema": "https://json-schema.org/draft/2020-12/schema",
475
474
 
@@ -489,7 +488,7 @@ addSchema({
489
488
  "unevaluatedProperties": false
490
489
  });
491
490
 
492
- addSchema({
491
+ registerSchema({
493
492
  $schema: "https://example.com/meta-schema1",
494
493
  type: "number",
495
494
  foo: 42
@@ -499,25 +498,69 @@ const output = await validate("https://example.com/schema1", 42); // Expect Inva
499
498
  ```
500
499
 
501
500
  ### API
501
+
502
502
  These are available from the `@hyperjump/json-schema/experimental` export.
503
503
 
504
504
  * **addKeyword**: (keywordHandler: Keyword) => void
505
505
 
506
506
  Define a keyword for use in a vocabulary.
507
+
508
+ * **Keyword**: object
509
+ * id: string
510
+
511
+ A URI that uniquely identifies the keyword. It should use a domain you
512
+ own to avoid conflict with keywords defined by others.
513
+ * compile: (schema: Browser, ast: AST, parentSchema: Browser) => Promise\<any>
514
+
515
+ This function takes the keyword value, does whatever preprocessing it
516
+ can on it without an instance, and returns the result. The returned
517
+ value will be passed to the `interpret` function. The `ast` parameter
518
+ is needed for compiling sub-schemas. The `parentSchema` parameter is
519
+ primarily useful for looking up the value of an adjacent keyword that
520
+ might effect this one.
521
+ * interpret: (compiledKeywordValue: any, instance: InstanceDocument, ast: AST, dynamicAnchors: object, quiet: boolean) => boolean
522
+
523
+ This function takes the value returned by the `compile` function and
524
+ the instance value that is being validated and returns whether the
525
+ value is valid or not. The other parameters are only needed for
526
+ validating sub-schemas.
527
+ * collectEvaluatedProperties?: (compiledKeywordValue: any, instance: InstanceDocument, ast: AST, dynamicAnchors: object) => Set\<string> | false
528
+
529
+ If the keyword is an applicator, it will need to implement this
530
+ function for `unevaluatedProperties` to work as expected.
531
+ * collectEvaluatedItems?: (compiledKeywordValue: A, instance: InstanceDocument, ast: AST, dynamicAnchors: object) => Set\<number> | false
532
+
533
+ If the keyword is an applicator, it will need to implement this
534
+ function for `unevaluatedItems` to work as expected.
535
+ * collectExternalIds?: (visited: Set\<string>, parentSchema: Browser, schema: Browser) => Set\<string>
536
+ If the keyword is an applicator, it will need to implement this
537
+ function to work properly with the [bundle](#bundling) feature.
538
+ * annotation?: (compiledKeywordValue: any) => any
539
+
540
+ If the keyword is an annotation, it will need to implement this
541
+ function to work with the [annotation](#annotations-experimental)
542
+ functions.
507
543
  * **defineVocabulary**: (id: string, keywords: { [keyword: string]: string }) => void
508
544
 
509
545
  Define a vocabulary that maps keyword name to keyword URIs defined using
510
546
  `addKeyword`.
547
+ * **getKeywordId**: (keywordName: string, dialectId: string) => string
548
+
549
+ Get the identifier for a keyword by its name.
511
550
  * **getKeyword**: (keywordId: string) => Keyword
512
551
 
513
552
  Get a keyword object by its URI. This is useful for building non-validation
514
553
  tooling.
554
+ * **getKeywordByName**: (keywordName: string, dialectId: string) => Keyword
555
+
556
+ Get a keyword object by its name. This is useful for building non-validation
557
+ tooling.
515
558
  * **getKeywordName**: (dialectId: string, keywordId: string) => string
516
559
 
517
560
  Determine a keyword's name given its URI a dialect URI. This is useful when
518
561
  defining a keyword that depends on the value of another keyword (such as how
519
562
  `contains` depends on `minContains` and `maxContains`).
520
- * **loadDialect**: (dialectId: string, dialect: { [vocabularyId: string] }) => void
563
+ * **loadDialect**: (dialectId: string, dialect: { [vocabularyId: string] }, allowUnknownKeywords: boolean = false) => void
521
564
 
522
565
  Define a dialect. In most cases, dialects are loaded automatically from the
523
566
  `$vocabulary` keyword in the meta-schema. The only time you would need to
@@ -527,98 +570,55 @@ These are available from the `@hyperjump/json-schema/experimental` export.
527
570
 
528
571
  A Keyword object that represents a "validate" operation. You would use this
529
572
  for compiling and evaluating sub-schemas when defining a custom keyword.
573
+
574
+ * **getSchema**: (uri: string, browser?: Browser) => Promise\<Browser>
530
575
 
531
- * **Keyword**: object
532
- * id: string
533
-
534
- A URI that uniquely identifies the keyword. It should use a domain you
535
- own to avoid conflict with keywords defined by others.
536
- * compile: (schema: SchemaDocument, ast: AST, parentSchema: SchemaDocument) * => Promise\<A>
537
-
538
- This function takes the keyword value, does whatever preprocessing it
539
- can on it without an instance, and returns the result. The returned
540
- value will be passed to the `interpret` function. The `ast` parameter is
541
- needed for compiling sub-schemas. The `parentSchema` parameter is
542
- primarily useful for looking up the value of an adjacent keyword that
543
- might effect this one.
544
- * interpret: (compiledKeywordValue: A, instance: JsonDocument, ast: AST, dynamicAnchors: Anchors, quiet: boolean) => boolean
545
-
546
- This function takes the value returned by the `compile` function and the
547
- instance value that is being validated and returns whether the value is
548
- valid or not. The other parameters are only needed for validating
549
- sub-schemas.
550
- * collectEvaluatedProperties?: (compiledKeywordValue: A, instance: JsonDocument, ast: AST, dynamicAnchors: Anchors) => string[] | false
551
-
552
- If the keyword is an applicator, it will need to implements this
553
- function for `unevaluatedProperties` to work as expected.
554
- * collectEvaluatedItems?: (compiledKeywordValue: A, instance: JsonDocument, * ast: AST, dynamicAnchors: Anchors) => Set\<number> | false
555
-
556
- If the keyword is an applicator, it will need to implements this
557
- function for `unevaluatedItems` to work as expected.
558
-
559
- ### Schema API
560
- These functions are available from the
561
- `@hyperjump/json-schema/schema/experimental` export.
562
-
563
- This library uses SchemaDocument objects to represent a value in a schema.
564
- You'll work with these objects if you create a custom keyword. This module is a
565
- set of functions for working with SchemaDocuments.
566
-
567
- * **Schema.add**: (schema: object, retrievalUri?: string, dialectId?: string) => string
568
-
569
- Load a schema. Returns the identifier for the schema.
570
- * **Schema.get**: (url: string, contextDoc?: SchemaDocument) => Promise\<SchemaDocument>
571
-
572
- Fetch a schema. Schemas can come from an HTTP request, a file, or a schema
573
- that was added with `Schema.add`.
574
- * **Schema.uri**: (doc: SchemaDocument) => string
575
-
576
- Returns a URI for the value the SchemaDocument represents.
577
- * **Schema.value**: (doc: SchemaDocument) => any
578
-
579
- Returns the value the SchemaDocument represents.
580
- * **Schema.typeOf**: (doc: SchemaDocument, type: string) => boolean
581
-
582
- Determines if the JSON type of the given doc matches the given type.
583
- * **Schema.has**: (key: string, doc: SchemaDocument) => Promise\<SchemaDocument>
584
-
585
- Similar to `key in schema`.
586
- * **Schema.step**: (key: string, doc: SchemaDocument) => Promise\<SchemaDocument>
587
-
588
- Similar to `schema[key]`, but returns an SchemaDocument.
589
- * **Schema.iter**: (doc: SchemaDocument) => AsyncGenerator\<SchemaDocument>
590
-
591
- Iterate over the items in the array that the SchemaDocument represents
592
- * **Schema.entries**: (doc: SchemaDocument) => AsyncGenerator\<[string, SchemaDocument]>
576
+ Get a schema by it's URI taking the local schema registry into account.
577
+ * buildSchemaDocument: (schema: SchemaObject | boolean, retrievalUri?: string, contextDialectId?: string) => SchemaDocument
593
578
 
594
- Similar to `Object.entries`, but yields SchemaDocuments for values.
595
- * **Schema.values**: (doc: SchemaDocument) => AsyncGenerator\<SchemaDocument>
579
+ Build a SchemaDocument from a JSON-compatible value. You might use this if
580
+ you're creating a custom media type plugin, such as supporting JSON Schemas
581
+ in YAML.
582
+ * **canonicalUri**: (schema: Browser) => string
596
583
 
597
- Similar to `Object.values`, but yields SchemaDocuments for values.
598
- * **Schema.keys**: (doc: SchemaDocument) => Generator\<string>
584
+ Returns a URI for the schema.
585
+ * **toSchema**: (schema: Browser, options: ToSchemaOptions) => object
599
586
 
600
- Similar to `Object.keys`.
601
- * **Schema.length**: (doc: SchemaDocument) => number
587
+ Get a raw schema from a Schema Document.
602
588
 
603
- Similar to `Array.prototype.length`.
604
- * **Schema.toSchema**: (doc: SchemaDocument, options: ToSchemaOptions) => object
589
+ * **ToSchemaOptions**: object
590
+
591
+ * contextDialectId: string (default: "") -- If the dialect of the schema
592
+ matches this value, the `$schema` keyword will be omitted.
593
+ * includeDialect: "auto" | "always" | "never" (default: "auto") -- If
594
+ "auto", `$schema` will only be included if it differs from
595
+ `contextDialectId`.
596
+ * selfIdentify: boolean (default: false) -- If true, `$id` will be
597
+ included.
598
+ * contextUri: string (default: "") -- `$id`s will be relative to this
599
+ URI.
600
+ * includeEmbedded: boolean (default: true) -- If false, embedded schemas
601
+ will be unbundled from the schema.
602
+ * **compile**: (schema: Browser) => Promise\<CompiledSchema>
605
603
 
606
- Get a raw schema from a Schema Document.
604
+ Return a compiled schema. This is useful if you're creating tooling for
605
+ something other than validation.
606
+ * **interpret**: (schema: CompiledSchema, instance: Instance, outputFormat: OutputFormat = BASIC) => OutputUnit
607
607
 
608
- **Type Definitions**
608
+ A curried function for validating an instance against a compiled schema.
609
+ This can be useful for creating custom output formats.
609
610
 
610
- The following types are used in the above definitions
611
+ * **OutputFormat**: **FLAG** | **BASIC** | **DETAILED** | **VERBOSE**
611
612
 
612
- * **ToSchemaOptions**: object
613
+ In addition to the `FLAG` output format in the Stable API, the Experimental
614
+ API includes support for the `BASIC`, `DETAILED`, and `VERBOSE` formats as
615
+ specified in the 2019-09 specification (with some minor customizations).
616
+ This implementation doesn't include annotations or human readable error
617
+ messages. The output can be processed to create human readable error
618
+ messages as needed.
613
619
 
614
- * parentId: string (default: "") -- `file://` URIs will be generated
615
- relative to this path.
616
- * parentDialect: string (default: "") -- If the dialect of the schema
617
- * matches this value, the `$schema` keyword will be omitted.
618
- * includeEmbedded: boolean (default: true) -- If false, embedded schemas
619
- will be unbundled from the schema.
620
+ ## Instance API (experimental)
620
621
 
621
- ### Instance API
622
622
  These functions are available from the
623
623
  `@hyperjump/json-schema/instance/experimental` export.
624
624
 
@@ -626,44 +626,49 @@ This library uses InstanceDocument objects to represent a value in an instance.
626
626
  You'll work with these objects if you create a custom keyword. This module is a
627
627
  set of functions for working with InstanceDocuments.
628
628
 
629
- * **Instance.cons**: (instance: any, uri?: string) => InstanceDocument
629
+ This API uses generators to iterate over arrays and objects. If you like using
630
+ higher order functions like `map`/`filter`/`reduce`, see
631
+ [`@hyperjump/pact`](https://github.com/hyperjump-io/pact) for utilities for
632
+ working with generators and async generators.
633
+
634
+ * **cons**: (instance: any, uri?: string) => InstanceDocument
630
635
 
631
636
  Construct an InstanceDocument from a value.
632
- * **Instance.get**: (url: string, contextDoc: InstanceDocument) => InstanceDocument
637
+ * **get**: (url: string, contextDoc: InstanceDocument) => InstanceDocument
633
638
 
634
639
  Apply a same-resource reference to a InstanceDocument.
635
- * **Instance.uri**: (doc: InstanceDocument) => string
640
+ * **uri**: (doc: InstanceDocument) => string
636
641
 
637
642
  Returns a URI for the value the InstanceDocument represents.
638
- * **Instance.value**: (doc: InstanceDocument) => any
643
+ * **value**: (doc: InstanceDocument) => any
639
644
 
640
645
  Returns the value the InstanceDocument represents.
641
- * **Instance.has**: (key: string, doc: InstanceDocument) => any
646
+ * **has**: (key: string, doc: InstanceDocument) => any
642
647
 
643
648
  Similar to `key in instance`.
644
- * **Instance.typeOf**: (doc: InstanceDocument, type: string) => boolean
649
+ * **typeOf**: (doc: InstanceDocument) => string
645
650
 
646
651
  Determines if the JSON type of the given doc matches the given type.
647
- * **Instance.step**: (key: string, doc: InstanceDocument) => InstanceDocument
652
+ * **step**: (key: string, doc: InstanceDocument) => InstanceDocument
648
653
 
649
654
  Similar to `schema[key]`, but returns a InstanceDocument.
650
- * **Instance.iter**: (doc: InstanceDocument) => Generator\<InstanceDocument>
655
+ * **iter**: (doc: InstanceDocument) => Generator\<InstanceDocument>
651
656
 
652
657
  Iterate over the items in the array that the SchemaDocument represents.
653
- * **Instance.entries**: (doc: InstanceDocument) => Generator\<[string, InstanceDocument]>
658
+ * **entries**: (doc: InstanceDocument) => Generator\<[string, InstanceDocument]>
654
659
 
655
660
  Similar to `Object.entries`, but yields InstanceDocuments for values.
656
- * **Instance.values**: (doc: InstanceDocument) => Generator\<InstanceDocument>
661
+ * **values**: (doc: InstanceDocument) => Generator\<InstanceDocument>
657
662
 
658
663
  Similar to `Object.values`, but yields InstanceDocuments for values.
659
- * **Instance.keys**: (doc: InstanceDocument) => Generator\<string>
664
+ * **keys**: (doc: InstanceDocument) => Generator\<string>
660
665
 
661
666
  Similar to `Object.keys`.
662
- * **Instance.length**: (doc: InstanceDocument) => number
667
+ * **length**: (doc: InstanceDocument) => number
663
668
 
664
669
  Similar to `Array.prototype.length`.
665
670
 
666
- ## Annotations (Experimental)
671
+ ## Annotations (experimental)
667
672
  JSON Schema is for annotating JSON instances as well as validating them. This
668
673
  module provides utilities for working with JSON documents annotated with JSON
669
674
  Schema.
@@ -674,14 +679,14 @@ object is a wrapper around your JSON document with functions that allow you to
674
679
  traverse the data structure and get annotations for the values within.
675
680
 
676
681
  ```javascript
677
- import { annotate, annotatedWith, addSchema } from "@hyperjump/json-schema/annotations/experimental";
682
+ import { annotate, annotatedWith, registerSchema } from "@hyperjump/json-schema/annotations/experimental";
678
683
  import * as AnnotatedInstance from "@hyperjump/json-schema/annotated-instance/experimental";
679
684
 
680
685
 
681
686
  const schemaId = "https://example.com/foo";
682
687
  const dialectId = "https://json-schema.org/draft/2020-12/schema";
683
688
 
684
- addSchema({
689
+ registerSchema({
685
690
  "$schema": dialectId,
686
691
 
687
692
  "title": "Person",
@@ -743,28 +748,28 @@ for (const deprecated of AnnotatedInstance.annotatedWith(instance, "deprecated",
743
748
  These are available from the `@hyperjump/json-schema/annotations/experimental`
744
749
  export.
745
750
 
746
- * **annotate**: (schemaUri: string, instance: any, outputFormat: OutputFormat = * FLAG) => Promise\<AnnotatedInstance>
751
+ * **annotate**: (schemaUri: string, instance: any, outputFormat: OutputFormat = FLAG) => Promise\<AnnotatedInstance>
747
752
 
748
753
  Annotate an instance using the given schema. The function is curried to
749
754
  allow compiling the schema once and applying it to multiple instances. This
750
755
  may throw an [InvalidSchemaError](#api) if there is a problem with the
751
756
  schema or a ValidationError if the instance doesn't validate against the
752
757
  schema.
753
- * **ValidationError**:
754
- output: OutputUnit -- The errors that were found while validating the
755
- instance.
758
+ * **ValidationError**: Error & { output: OutputUnit }
759
+ The `output` field contains an `OutputUnit` with information about the
760
+ error.
756
761
 
757
- ### AnnotatedInstance API
762
+ ## AnnotatedInstance API (experimental)
758
763
  These are available from the
759
764
  `@hyperjump/json-schema/annotated-instance/experimental` export. The
760
765
  following functions are available in addition to the functions available in the
761
- [Instance API](#instance-api).
766
+ [Instance API](#instance-api-experimental).
762
767
 
763
- * **annotation**: (instance: AnnotatedInstance, keyword: string, dialectId?: string) => [any]
768
+ * **annotation**: (instance: AnnotatedInstance, keyword: string, dialectId?: string) => any[]
764
769
 
765
770
  Get the annotations for a given keyword at the location represented by the
766
771
  instance object.
767
- * **annotatedWith**: (instance: AnnotatedInstance, keyword: string, dialectId?: string) => [AnnotatedInstance]
772
+ * **annotatedWith**: (instance: AnnotatedInstance, keyword: string, dialectId?: string) => AnnotatedInstance[]
768
773
 
769
774
  Get an array of instances for all the locations that are annotated with the
770
775
  given keyword.
@@ -773,19 +778,6 @@ following functions are available in addition to the functions available in the
773
778
  Add an annotation to an instance. This is used internally, you probably
774
779
  don't need it.
775
780
 
776
- ## Low-level Utilities (Experimental)
777
- ### API
778
- These are available from the `@hyperjump/json-schema/experimental` export.
779
-
780
- * **compile**: (schemaUri: string) => Promise\<CompiledSchema>
781
-
782
- Return a compiled schema. This is useful if you're creating tooling for
783
- something other than validation.
784
- * **interpret**: (schema: CompiledSchema, instance: Instance, outputFormat: OutputFormat = BASIC) => OutputUnit
785
-
786
- A curried function for validating an instance against a compiled schema.
787
- This can be useful for creating custom output formats.
788
-
789
781
  ## Contributing
790
782
 
791
783
  ### Tests