@hyperjump/json-schema 1.5.2 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -18,8 +18,8 @@ A collection of modules for working with JSON Schemas.
18
18
  * Provides utilities for working with annotations
19
19
 
20
20
  ## Install
21
- Includes support for node.js (ES Modules, TypeScript) and browsers (works with
22
- CSP
21
+ Includes support for node.js/bun.js (ES Modules, TypeScript) and browsers (works
22
+ with CSP
23
23
  [`unsafe-eval`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src#unsafe_eval_expressions)).
24
24
 
25
25
  ### Node.js
@@ -42,6 +42,16 @@ configuration.
42
42
  ]
43
43
  ```
44
44
 
45
+ ### TypeScript
46
+ This package uses the package.json "exports" field. [TypeScript understands
47
+ "exports"](https://devblogs.microsoft.com/typescript/announcing-typescript-4-5-beta/#packagejson-exports-imports-and-self-referencing),
48
+ but you need to change a couple settings in your `tsconfig.json` for it to work.
49
+
50
+ ```jsonc
51
+ "module": "Node16", // or "NodeNext"
52
+ "moduleResolution": "Node16", // or "NodeNext"
53
+ ```
54
+
45
55
  ### Versioning
46
56
  The API for this library is divided into two categories: Stable and
47
57
  Experimental. The Stable API strictly follows semantic versioning, but the
@@ -98,20 +108,30 @@ const output2 = isString(42);
98
108
 
99
109
  **Fetching schemas**
100
110
 
101
- You can fetch schemas from the web or from the file system, but when fetching
102
- from the file system, there are limitations for security reasons. If your schema
103
- has an identifier with an http(s) scheme (**https**://example.com), it's not
104
- allowed to reference schemas with a file scheme
105
- (**file**:///path/to/my/schemas).
111
+ Schemas that are available on the web can be loaded automatically without
112
+ needing to load them manually.
106
113
 
107
114
  ```javascript
108
115
  const output = await validate("http://example.com/schemas/string", "foo");
109
116
  ```
110
117
 
118
+ 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).
123
+
111
124
  ```javascript
112
125
  const output = await validate(`file://${__dirname}/string.schema.json`, "foo");
113
126
  ```
114
127
 
128
+ 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.
130
+
131
+ ```javascript
132
+ const output = await validate(`./string.schema.json`, "foo");
133
+ ```
134
+
115
135
  **Media type plugins**
116
136
 
117
137
  There is a plugin system for adding support for different media types. By
@@ -174,11 +194,11 @@ Schema, such as `@hyperjump/json-schema/draft-2020-12`.
174
194
 
175
195
  Load a schema manually rather than fetching it from the filesystem or over
176
196
  the network.
177
- * **validate**: (schemaURI: string, instance: any, outputFormat: OutputFormat = FLAG) => Promise<OutputUnit>
197
+ * **validate**: (schemaURI: string, instance: any, outputFormat: OutputFormat = * FLAG) => Promise\<OutputUnit>
178
198
 
179
199
  Validate an instance against a schema. This function is curried to allow
180
200
  compiling the schema once and applying it to multiple instances.
181
- * **validate**: (schemaURI: string) => Promise<(instance: any, outputFormat: OutputFormat = FLAG) => OutputUnit>
201
+ * **validate**: (schemaURI: string) => Promise\<(instance: any, outputFormat: OutputFormat = FLAG) => OutputUnit>
182
202
 
183
203
  Compiling a schema to a validation function.
184
204
  * **FLAG**: "FLAG"
@@ -293,7 +313,7 @@ const bundledSchema = await bundle("https://example.com/main"); // {
293
313
  ### API
294
314
  These are available from the `@hyperjump/json-schema/bundle` export.
295
315
 
296
- * **bundle**: (uri: string, options: Options) => Promise<SchemaObject>
316
+ * **bundle**: (uri: string, options: Options) => Promise\<SchemaObject>
297
317
 
298
318
  Create a bundled schema starting with the given schema. External schemas
299
319
  will be fetched from the filesystem, the network, or internally as needed.
@@ -513,7 +533,7 @@ These are available from the `@hyperjump/json-schema/experimental` export.
513
533
 
514
534
  A URI that uniquely identifies the keyword. It should use a domain you
515
535
  own to avoid conflict with keywords defined by others.
516
- * compile: (schema: SchemaDocument, ast: AST, parentSchema: SchemaDocument) => Promise<A>
536
+ * compile: (schema: SchemaDocument, ast: AST, parentSchema: SchemaDocument) * => Promise\<A>
517
537
 
518
538
  This function takes the keyword value, does whatever preprocessing it
519
539
  can on it without an instance, and returns the result. The returned
@@ -531,7 +551,7 @@ These are available from the `@hyperjump/json-schema/experimental` export.
531
551
 
532
552
  If the keyword is an applicator, it will need to implements this
533
553
  function for `unevaluatedProperties` to work as expected.
534
- * collectEvaluatedItems?: (compiledKeywordValue: A, instance: JsonDocument, ast: AST, dynamicAnchors: Anchors) => Set<number> | false
554
+ * collectEvaluatedItems?: (compiledKeywordValue: A, instance: JsonDocument, * ast: AST, dynamicAnchors: Anchors) => Set\<number> | false
535
555
 
536
556
  If the keyword is an applicator, it will need to implements this
537
557
  function for `unevaluatedItems` to work as expected.
@@ -547,7 +567,7 @@ set of functions for working with SchemaDocuments.
547
567
  * **Schema.add**: (schema: object, retrievalUri?: string, dialectId?: string) => string
548
568
 
549
569
  Load a schema. Returns the identifier for the schema.
550
- * **Schema.get**: (url: string, contextDoc?: SchemaDocument) => Promise<SchemaDocument>
570
+ * **Schema.get**: (url: string, contextDoc?: SchemaDocument) => Promise\<SchemaDocument>
551
571
 
552
572
  Fetch a schema. Schemas can come from an HTTP request, a file, or a schema
553
573
  that was added with `Schema.add`.
@@ -560,22 +580,22 @@ set of functions for working with SchemaDocuments.
560
580
  * **Schema.typeOf**: (doc: SchemaDocument, type: string) => boolean
561
581
 
562
582
  Determines if the JSON type of the given doc matches the given type.
563
- * **Schema.has**: (key: string, doc: SchemaDocument) => Promise<SchemaDocument>
583
+ * **Schema.has**: (key: string, doc: SchemaDocument) => Promise\<SchemaDocument>
564
584
 
565
585
  Similar to `key in schema`.
566
- * **Schema.step**: (key: string, doc: SchemaDocument) => Promise<SchemaDocument>
586
+ * **Schema.step**: (key: string, doc: SchemaDocument) => Promise\<SchemaDocument>
567
587
 
568
588
  Similar to `schema[key]`, but returns an SchemaDocument.
569
- * **Schema.iter**: (doc: SchemaDocument) => AsyncGenerator<SchemaDocument>
589
+ * **Schema.iter**: (doc: SchemaDocument) => AsyncGenerator\<SchemaDocument>
570
590
 
571
591
  Iterate over the items in the array that the SchemaDocument represents
572
- * **Schema.entries**: (doc: SchemaDocument) => AsyncGenerator<[string, SchemaDocument]>
592
+ * **Schema.entries**: (doc: SchemaDocument) => AsyncGenerator\<[string, SchemaDocument]>
573
593
 
574
594
  Similar to `Object.entries`, but yields SchemaDocuments for values.
575
- * **Schema.values**: (doc: SchemaDocument) => AsyncGenerator<SchemaDocument>
595
+ * **Schema.values**: (doc: SchemaDocument) => AsyncGenerator\<SchemaDocument>
576
596
 
577
597
  Similar to `Object.values`, but yields SchemaDocuments for values.
578
- * **Schema.keys**: (doc: SchemaDocument) => Generator<string>
598
+ * **Schema.keys**: (doc: SchemaDocument) => Generator\<string>
579
599
 
580
600
  Similar to `Object.keys`.
581
601
  * **Schema.length**: (doc: SchemaDocument) => number
@@ -627,16 +647,16 @@ set of functions for working with InstanceDocuments.
627
647
  * **Instance.step**: (key: string, doc: InstanceDocument) => InstanceDocument
628
648
 
629
649
  Similar to `schema[key]`, but returns a InstanceDocument.
630
- * **Instance.iter**: (doc: InstanceDocument) => Generator<InstanceDocument>
650
+ * **Instance.iter**: (doc: InstanceDocument) => Generator\<InstanceDocument>
631
651
 
632
652
  Iterate over the items in the array that the SchemaDocument represents.
633
- * **Instance.entries**: (doc: InstanceDocument) => Generator<[string, InstanceDocument]>
653
+ * **Instance.entries**: (doc: InstanceDocument) => Generator\<[string, InstanceDocument]>
634
654
 
635
655
  Similar to `Object.entries`, but yields InstanceDocuments for values.
636
- * **Instance.values**: (doc: InstanceDocument) => Generator<InstanceDocument>
656
+ * **Instance.values**: (doc: InstanceDocument) => Generator\<InstanceDocument>
637
657
 
638
658
  Similar to `Object.values`, but yields InstanceDocuments for values.
639
- * **Instance.keys**: (doc: InstanceDocument) => Generator<string>
659
+ * **Instance.keys**: (doc: InstanceDocument) => Generator\<string>
640
660
 
641
661
  Similar to `Object.keys`.
642
662
  * **Instance.length**: (doc: InstanceDocument) => number
@@ -723,7 +743,7 @@ for (const deprecated of AnnotatedInstance.annotatedWith(instance, "deprecated",
723
743
  These are available from the `@hyperjump/json-schema/annotations/experimental`
724
744
  export.
725
745
 
726
- * **annotate**: (schemaUri: string, instance: any, outputFormat: OutputFormat = FLAG) => Promise<AnnotatedInstance>
746
+ * **annotate**: (schemaUri: string, instance: any, outputFormat: OutputFormat = * FLAG) => Promise\<AnnotatedInstance>
727
747
 
728
748
  Annotate an instance using the given schema. The function is curried to
729
749
  allow compiling the schema once and applying it to multiple instances. This
@@ -757,7 +777,7 @@ following functions are available in addition to the functions available in the
757
777
  ### API
758
778
  These are available from the `@hyperjump/json-schema/experimental` export.
759
779
 
760
- * **compile**: (schema: SchemaDocument) => Promise<CompiledSchema>
780
+ * **compile**: (schemaUri: string) => Promise\<CompiledSchema>
761
781
 
762
782
  Return a compiled schema. This is useful if you're creating tooling for
763
783
  something other than validation.
@@ -0,0 +1 @@
1
+ export const contextUri = () => document.location.toString();
@@ -0,0 +1,4 @@
1
+ import { cwd } from "node:process";
2
+
3
+
4
+ export const contextUri = () => `file://${cwd()}/`;
package/lib/core.d.ts CHANGED
@@ -20,7 +20,7 @@ export const addSchema: typeof add;
20
20
 
21
21
  export type Validator = (value: unknown, outputFormat?: OutputFormat) => OutputUnit;
22
22
 
23
- export type OutputFormat = "FLAG" | "BASIC" | "DETAILED" | "VERBOSE" | string;
23
+ export type OutputFormat = "FLAG" | "BASIC" | "DETAILED" | "VERBOSE";
24
24
 
25
25
  export type OutputUnit = {
26
26
  keyword: string;
package/lib/fetch.js CHANGED
@@ -1,15 +1,15 @@
1
- import fs from "fs/promises";
1
+ import { createReadStream } from "node:fs";
2
+ import { Readable } from "node:stream";
3
+ import { fileURLToPath } from "node:url";
2
4
  import { fetch, Response } from "undici";
3
- import Url from "url";
4
5
  import * as MediaTypes from "./media-types.js";
5
6
 
6
7
 
7
- export default async (url, options) => {
8
+ export default (url, options) => {
8
9
  if (url.startsWith("file://")) {
9
- const filePath = Url.fileURLToPath(url);
10
- const fd = await fs.open(filePath);
11
- const stream = fd.createReadStream();
12
- const response = new Response(stream, {
10
+ const filePath = fileURLToPath(url);
11
+ const stream = createReadStream(filePath);
12
+ const response = new Response(Readable.toWeb(stream), {
13
13
  headers: { "Content-Type": MediaTypes.getContentType(filePath) }
14
14
  });
15
15
  Object.defineProperty(response, "url", { value: url });
package/lib/instance.js CHANGED
@@ -1,4 +1,4 @@
1
- import { append as pointerAppend } from "@hyperjump/json-pointer";
1
+ import { append as pointerAppend, get as pointerGet } from "@hyperjump/json-pointer";
2
2
  import { toAbsoluteIri } from "@hyperjump/uri";
3
3
  import curry from "just-curry-it";
4
4
  import { jsonTypeOf } from "./common.js";
@@ -18,7 +18,12 @@ export const get = (url, instance = nil) => {
18
18
  throw Error(`No JSON document found at '${url.split("#")[0]}'`);
19
19
  }
20
20
 
21
- return { ...instance, pointer: url.substr(1) };
21
+ const pointer = url.substr(1);
22
+ return {
23
+ ...instance,
24
+ pointer: pointer,
25
+ value: pointerGet(pointer, instance.instance)
26
+ };
22
27
  };
23
28
 
24
29
  export const uri = (doc) => `${doc.id || ""}#${encodeURI(doc.pointer)}`;
@@ -1,4 +1,4 @@
1
- import { concat, join, empty, map, filter, every, pipe, tap } from "@hyperjump/pact";
1
+ import { concat, join, empty, map, filter, every, pipe } from "@hyperjump/pact";
2
2
  import * as Schema from "../schema.js";
3
3
  import * as Instance from "../instance.js";
4
4
  import { getKeywordName } from "../keywords.js";
package/lib/schema.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { nil as nilPointer, append as pointerAppend, get as pointerGet } from "@hyperjump/json-pointer";
2
2
  import { toAbsoluteIri } from "@hyperjump/uri";
3
3
  import { jsonTypeOf, resolveUri, uriFragment, pathRelative, jsonStringify } from "./common.js";
4
+ import { contextUri } from "./context-uri.js";
4
5
  import fetch from "./fetch.js";
5
6
  import { hasDialect, loadDialect, getKeywordName } from "./keywords.js";
6
7
  import { parseResponse, acceptableMediaTypes } from "./media-types.js";
@@ -11,13 +12,14 @@ import * as Reference from "./reference.js";
11
12
  const schemaStore = {};
12
13
  const schemaStoreAlias = {};
13
14
 
14
- const defaultDialectId = "https://json-schema.org/validation";
15
-
16
15
  export const add = (schema, retrievalUri = undefined, contextDialectId = undefined) => {
17
16
  schema = JSON.parse(JSON.stringify(schema));
18
17
 
19
18
  // Dialect / JSON Schema Version
20
- const dialectId = toAbsoluteIri(schema.$schema || contextDialectId || defaultDialectId);
19
+ if ((typeof schema !== "object" || !("$schema" in schema)) && !contextDialectId) {
20
+ throw Error("Unable to determine a dialect for the schema. The dialect can be declared in a number of ways, but the recommended way is to use the '$schema' keyword in your schema.");
21
+ }
22
+ const dialectId = toAbsoluteIri(schema.$schema || contextDialectId);
21
23
  delete schema.$schema;
22
24
 
23
25
  if (!hasDialect(dialectId)) {
@@ -159,7 +161,7 @@ const nil = {
159
161
  };
160
162
 
161
163
  export const get = async (url, contextDoc = nil) => {
162
- const resolvedUrl = resolveUri(url, contextDoc.id);
164
+ const resolvedUrl = resolveUri(url, contextDoc.id || contextUri());
163
165
  const id = toAbsoluteIri(resolvedUrl);
164
166
  const fragment = uriFragment(resolvedUrl);
165
167
 
@@ -173,9 +175,11 @@ export const get = async (url, contextDoc = nil) => {
173
175
  const [schema, contextDialectId] = await parseResponse(response);
174
176
 
175
177
  // Try to determine the dialect from the meta-schema if it isn't already known
176
- const dialectId = toAbsoluteIri(schema.$schema || contextDialectId || defaultDialectId);
177
- if (!hasDialect(dialectId) && !hasStoredSchema(dialectId)) {
178
- await get(dialectId);
178
+ if (schema.$schema || contextDialectId) {
179
+ const dialectId = toAbsoluteIri(schema.$schema || contextDialectId);
180
+ if (!hasDialect(dialectId) && !hasStoredSchema(dialectId)) {
181
+ await get(dialectId);
182
+ }
179
183
  }
180
184
 
181
185
  add(schema, id, contextDialectId);
@@ -272,7 +276,7 @@ export const toSchema = (schemaDoc, options = {}) => {
272
276
  dynamicAnchors[pointer] = anchor;
273
277
  }
274
278
 
275
- const schema = JSON.parse(jsonStringify(schemaDoc.schema, (key, value, pointer) => {
279
+ const schema = JSON.parse(jsonStringify(schemaDoc.schema, (_key, value, pointer) => {
276
280
  if (Reference.isReference(value)) {
277
281
  const refValue = Reference.value(value);
278
282
  if (fullOptions.includeEmbedded || !(idToken in refValue)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperjump/json-schema",
3
- "version": "1.5.2",
3
+ "version": "1.6.1",
4
4
  "description": "A JSON Schema validator with support for custom keywords, vocabularies, and dialects",
5
5
  "type": "module",
6
6
  "main": "./stable/index.js",
@@ -21,20 +21,24 @@
21
21
  "./bundle": "./bundle/index.js"
22
22
  },
23
23
  "browser": {
24
- "./lib/fetch.js": "./lib/fetch.browser.js"
24
+ "./lib/fetch.js": "./lib/fetch.browser.js",
25
+ "./lib/context-uri.js": "./lib/context-uri.browser.js"
25
26
  },
26
27
  "scripts": {
27
28
  "clean": "xargs -a .gitignore rm -rf",
28
- "lint": "eslint lib stable draft-* openapi-*",
29
+ "lint": "eslint lib stable draft-* openapi-* bundle annotations",
29
30
  "test": "mocha 'lib/**/*.spec.ts' 'stable/**/*.spec.ts' 'draft-*/**/*.spec.ts' 'openapi-*/**/*.spec.ts' 'bundle/**/*.spec.ts' 'annotations/**/*.spec.ts'"
30
31
  },
31
32
  "repository": "github:hyperjump-io/json-schema",
32
33
  "keywords": [
33
34
  "JSON Schema",
35
+ "json-schema",
36
+ "jsonschema",
34
37
  "JSON",
35
38
  "Schema",
36
39
  "2020-12",
37
40
  "2019-09",
41
+ "draft-07",
38
42
  "draft-06",
39
43
  "draft-04",
40
44
  "vocabulary",
@@ -49,6 +53,7 @@
49
53
  "devDependencies": {
50
54
  "@types/chai": "*",
51
55
  "@types/mocha": "*",
56
+ "@types/node": "^20.6.2",
52
57
  "@typescript-eslint/eslint-plugin": "*",
53
58
  "@typescript-eslint/parser": "*",
54
59
  "chai": "*",
package/stable/index.js CHANGED
@@ -97,7 +97,8 @@ defineVocabulary("https://json-schema.org/vocab/unevaluated", {
97
97
  "unevaluatedProperties": "https://json-schema.org/keyword/unevaluatedProperties"
98
98
  });
99
99
 
100
- loadDialect("https://json-schema.org/validation", {
100
+ const dialectId = "https://json-schema.org/validation";
101
+ loadDialect(dialectId, {
101
102
  "https://json-schema.org/vocab/core": true,
102
103
  "https://json-schema.org/vocab/applicator": true,
103
104
  "https://json-schema.org/vocab/validation": true,
@@ -107,14 +108,14 @@ loadDialect("https://json-schema.org/validation", {
107
108
  "https://json-schema.org/vocab/unevaluated": true
108
109
  });
109
110
 
110
- addSchema(metaSchema);
111
- addSchema(coreMetaSchema);
112
- addSchema(applicatorMetaSchema);
113
- addSchema(validationMetaSchema);
114
- addSchema(metaDataMetaSchema);
115
- addSchema(formatAnnotationMetaSchema);
116
- addSchema(formatAssertionMetaSchema);
117
- addSchema(contentMetaSchema);
118
- addSchema(unevaluatedMetaSchema);
111
+ addSchema(metaSchema, dialectId);
112
+ addSchema(coreMetaSchema, "https://json-schema.org/meta/core");
113
+ addSchema(applicatorMetaSchema, "https://json-schema.org/meta/applicator");
114
+ addSchema(validationMetaSchema, "https://json-schema.org/meta/validation");
115
+ addSchema(metaDataMetaSchema, "https://json-schema.org/meta/meta-data");
116
+ addSchema(formatAnnotationMetaSchema, "https://json-schema.org/meta/format-annotation");
117
+ addSchema(formatAssertionMetaSchema, "https://json-schema.org/meta/format-assertion");
118
+ addSchema(contentMetaSchema, "https://json-schema.org/meta/content");
119
+ addSchema(unevaluatedMetaSchema, "https://json-schema.org/meta/unevaluated");
119
120
 
120
121
  export * from "../lib/index.js";
@@ -1,5 +1,5 @@
1
1
  export default {
2
- "$id": "https://json-schema.org/meta/applicator",
2
+ "$schema": "https://json-schema.org/validation",
3
3
  "title": "Applicator vocabulary meta-schema",
4
4
 
5
5
  "$dynamicAnchor": "meta",
@@ -1,5 +1,5 @@
1
1
  export default {
2
- "$id": "https://json-schema.org/meta/content",
2
+ "$schema": "https://json-schema.org/validation",
3
3
  "title": "Content vocabulary meta-schema",
4
4
 
5
5
  "$dynamicAnchor": "meta",
@@ -1,5 +1,5 @@
1
1
  export default {
2
- "$id": "https://json-schema.org/meta/core",
2
+ "$schema": "https://json-schema.org/validation",
3
3
  "title": "Core vocabulary meta-schema",
4
4
 
5
5
  "$dynamicAnchor": "meta",
@@ -1,5 +1,5 @@
1
1
  export default {
2
- "$id": "https://json-schema.org/meta/format-annotation",
2
+ "$schema": "https://json-schema.org/validation",
3
3
  "title": "Format vocabulary meta-schema for annotation results",
4
4
 
5
5
  "$dynamicAnchor": "meta",
@@ -1,5 +1,5 @@
1
1
  export default {
2
- "$id": "https://json-schema.org/meta/format-assertion",
2
+ "$schema": "https://json-schema.org/validation",
3
3
  "title": "Format vocabulary meta-schema for assertion results",
4
4
 
5
5
  "$dynamicAnchor": "meta",
@@ -1,5 +1,5 @@
1
1
  export default {
2
- "$id": "https://json-schema.org/meta/meta-data",
2
+ "$schema": "https://json-schema.org/validation",
3
3
  "title": "Meta-data vocabulary meta-schema",
4
4
 
5
5
  "$dynamicAnchor": "meta",
@@ -1,5 +1,5 @@
1
1
  export default {
2
- "$id": "https://json-schema.org/meta/unevaluated",
2
+ "$schema": "https://json-schema.org/validation",
3
3
  "title": "Unevaluated applicator vocabulary meta-schema",
4
4
 
5
5
  "$dynamicAnchor": "meta",
@@ -1,5 +1,5 @@
1
1
  export default {
2
- "$id": "https://json-schema.org/meta/validation",
2
+ "$schema": "https://json-schema.org/validation",
3
3
  "title": "Validation vocabulary meta-schema",
4
4
 
5
5
  "$dynamicAnchor": "meta",
@@ -1,5 +1,5 @@
1
1
  export default {
2
- "$id": "https://json-schema.org/validation",
2
+ "$schema": "https://json-schema.org/validation",
3
3
  "$vocabulary": {
4
4
  "https://json-schema.org/vocab/core": true,
5
5
  "https://json-schema.org/vocab/applicator": true,