@hyperjump/json-schema 1.6.7 → 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 (104) 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 +112 -156
  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 +27 -14
  75. package/lib/openapi.js +19 -6
  76. package/lib/schema.js +236 -227
  77. package/openapi-3-0/index.js +5 -5
  78. package/openapi-3-0/type.js +13 -7
  79. package/openapi-3-1/index.js +22 -21
  80. package/openapi-3-1/{schema-base/2022-10-07.js → schema-base.js} +12 -2
  81. package/openapi-3-1/schema-draft-04.js +33 -0
  82. package/openapi-3-1/schema-draft-06.js +33 -0
  83. package/openapi-3-1/schema-draft-07.js +33 -0
  84. package/openapi-3-1/schema-draft-2019-09.js +33 -0
  85. package/openapi-3-1/schema-draft-2020-12.js +33 -0
  86. package/package.json +11 -11
  87. package/stable/index.js +10 -10
  88. package/annotations/validation-error.d.ts +0 -8
  89. package/draft-2019-09/contains.js +0 -44
  90. package/lib/configuration.d.ts +0 -9
  91. package/lib/context-uri.browser.js +0 -1
  92. package/lib/context-uri.js +0 -4
  93. package/lib/core.d.ts +0 -48
  94. package/lib/fetch.browser.js +0 -1
  95. package/lib/fetch.js +0 -20
  96. package/lib/invalid-schema-error.d.ts +0 -8
  97. package/lib/keywords.d.ts +0 -19
  98. package/lib/media-types.d.ts +0 -11
  99. package/lib/media-types.js +0 -48
  100. package/lib/reference.d.ts +0 -11
  101. package/lib/reference.js +0 -11
  102. package/lib/schema.d.ts +0 -60
  103. /package/openapi-3-0/{schema/2021-09-28.js → schema.js} +0 -0
  104. /package/openapi-3-1/{schema/2022-10-07.js → schema.js} +0 -0
package/lib/schema.js CHANGED
@@ -1,44 +1,91 @@
1
- import { nil as nilPointer, append as pointerAppend, get as pointerGet } from "@hyperjump/json-pointer";
2
- import { toAbsoluteIri } from "@hyperjump/uri";
3
- import { jsonTypeOf, resolveUri, uriFragment, pathRelative, jsonStringify } from "./common.js";
4
- import { contextUri } from "./context-uri.js";
5
- import fetch from "./fetch.js";
6
- import { hasDialect, loadDialect, getKeywordName } from "./keywords.js";
7
- import { parseResponse, acceptableMediaTypes } from "./media-types.js";
8
- import * as Reference from "./reference.js";
1
+ import contentTypeParser from "content-type";
2
+ import { get as browserGet } from "@hyperjump/browser";
3
+ import { Reference } from "@hyperjump/browser/jref";
4
+ import { append } from "@hyperjump/json-pointer";
5
+ import { resolveIri, toAbsoluteIri, normalizeIri } from "@hyperjump/uri";
6
+ import { getKeywordName, loadDialect } from "./keywords.js";
7
+ import { uriFragment, jsonStringify, jsonTypeOf, toRelativeIri } from "./common.js";
8
+
9
+
10
+ export const schemaPlugin = {
11
+ parse: async (response) => {
12
+ const contentType = contentTypeParser.parse(response.headers.get("content-type") ?? "");
13
+ const contextDialectId = contentType.parameters.schema ?? contentType.parameters.profile;
14
+
15
+ return buildSchemaDocument(await response.json(), response.url, contextDialectId);
16
+ },
17
+ fileMatcher: (path) => path.endsWith(".schema.json")
18
+ };
19
+
20
+ const schemaRegistry = {};
9
21
 
22
+ export const getSchema = async (uri, browser = undefined) => {
23
+ if (!browser) {
24
+ const cache = {};
25
+
26
+ for (const uri in schemaRegistry) {
27
+ cache[uri] = schemaRegistry[uri];
28
+ }
10
29
 
11
- // Schema Management
12
- const schemaStore = {};
13
- const schemaStoreAlias = {};
30
+ browser = { _cache: cache };
31
+ }
32
+
33
+ const schema = await browserGet(uri, browser);
34
+ if (typeof schema.document.dialectId !== "string") {
35
+ throw Error(`The document at ${schema.document.baseUri} is not a schema.`);
36
+ }
14
37
 
15
- export const add = (schema, retrievalUri = undefined, contextDialectId = undefined) => {
16
- schema = JSON.parse(JSON.stringify(schema));
38
+ return schema;
39
+ };
40
+
41
+ export const registerSchema = (schema, retrievalUri, contextDialectId) => {
42
+ schema = structuredClone(schema);
43
+ const document = buildSchemaDocument(schema, retrievalUri, contextDialectId);
44
+
45
+ if (document.baseUri in schemaRegistry) {
46
+ throw Error(`A schema has already been registered for '${document.baseUri}. You can use 'unregisterSchema' to remove the old schema before registering the new one.`);
47
+ }
48
+
49
+ if (document.baseUri.startsWith("file:")) {
50
+ throw Error(`Registering a schema with a 'file:' URI scheme is not allowed: ${document.baseUri}`);
51
+ }
17
52
 
53
+ schemaRegistry[retrievalUri ? toAbsoluteIri(retrievalUri) : document.baseUri] = document;
54
+ };
55
+
56
+ export const unregisterSchema = (uri) => {
57
+ delete schemaRegistry[toAbsoluteIri(uri)];
58
+ };
59
+
60
+ export const buildSchemaDocument = (schema, id, dialectId, embedded = {}) => {
18
61
  // Dialect / JSON Schema Version
19
- if ((typeof schema !== "object" || !("$schema" in schema)) && !contextDialectId) {
62
+ if (typeof schema.$schema === "string") {
63
+ dialectId = schema.$schema;
64
+ delete schema.$schema;
65
+ }
66
+
67
+ if (!dialectId) {
20
68
  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
69
  }
22
- const dialectId = toAbsoluteIri(schema.$schema || contextDialectId);
23
- delete schema.$schema;
70
+ dialectId = toAbsoluteIri(dialectId);
24
71
 
25
72
  // Identifiers
26
- const idToken = getKeywordName(dialectId, "https://json-schema.org/keyword/id")
27
- || getKeywordName(dialectId, "https://json-schema.org/keyword/draft-04/id");
28
- if (!retrievalUri && typeof schema?.[idToken] !== "string") {
73
+ const legacyIdToken = getKeywordName(dialectId, "https://json-schema.org/keyword/draft-04/id");
74
+ const idToken = getKeywordName(dialectId, "https://json-schema.org/keyword/id") || legacyIdToken;
75
+ if (!schema[idToken] && !id) {
29
76
  throw Error(`Unable to determine an identifier for the schema. Use the '${idToken}' keyword or pass a retrievalUri when loading the schema.`);
30
77
  }
31
- const internalUrl = resolveUri(schema[idToken] || retrievalUri, retrievalUri);
32
- const id = toAbsoluteIri(internalUrl);
33
- delete schema[idToken];
34
- if (retrievalUri) {
35
- const externalId = toAbsoluteIri(retrievalUri);
36
- schemaStoreAlias[externalId] = id;
78
+ const resolvedId = resolveIri(schema[idToken] ?? "", id ?? "");
79
+ id = toAbsoluteIri(resolvedId);
80
+ if (legacyIdToken && resolvedId.length > id.length) {
81
+ schema[idToken] = "#" + uriFragment(resolvedId);
82
+ } else {
83
+ delete schema[idToken];
37
84
  }
38
85
 
39
86
  // Vocabulary
40
87
  const vocabularyToken = getKeywordName(dialectId, "https://json-schema.org/keyword/vocabulary");
41
- if (jsonTypeOf(schema[vocabularyToken], "object")) {
88
+ if (jsonTypeOf(schema[vocabularyToken]) === "object") {
42
89
  const allowUnknownKeywords = schema[vocabularyToken]["https://json-schema.org/draft/2019-09/vocab/core"]
43
90
  || schema[vocabularyToken]["https://json-schema.org/draft/2020-12/vocab/core"];
44
91
 
@@ -46,280 +93,242 @@ export const add = (schema, retrievalUri = undefined, contextDialectId = undefin
46
93
  delete schema[vocabularyToken];
47
94
  }
48
95
 
96
+ const anchors = { "": "" };
49
97
  const dynamicAnchors = {};
50
98
 
51
- // Recursive anchor
99
+ // Legacy Recursive anchor
52
100
  const recursiveAnchorToken = getKeywordName(dialectId, "https://json-schema.org/keyword/draft-2019-09/recursiveAnchor");
53
101
  if (schema[recursiveAnchorToken] === true) {
54
102
  dynamicAnchors[""] = `${id}#`;
55
103
  }
56
104
  delete schema[recursiveAnchorToken];
57
105
 
58
- // Store Schema
59
- const anchors = { "": "" };
60
- schemaStore[id] = {
61
- id: id,
106
+ embedded[id] = {
107
+ baseUri: id,
62
108
  dialectId: dialectId,
63
- schema: processSchema(schema, id, dialectId, nilPointer, anchors, dynamicAnchors),
109
+ root: processSchema(schema, id, dialectId, "", embedded, anchors, dynamicAnchors),
110
+ anchorLocation: (fragment) => {
111
+ if (fragment === undefined) {
112
+ return "";
113
+ }
114
+
115
+ fragment = decodeURI(fragment);
116
+ if (fragment[0] === "/") {
117
+ return fragment;
118
+ } else if (!(fragment in anchors)) {
119
+ throw Error(`No such anchor '${id}#${encodeURI(fragment)}'`);
120
+ } else {
121
+ return anchors[fragment];
122
+ }
123
+ },
64
124
  anchors: anchors,
65
125
  dynamicAnchors: dynamicAnchors,
66
- validated: false
126
+ embedded: embedded
67
127
  };
68
128
 
69
- return id;
129
+ return embedded[id];
70
130
  };
71
131
 
72
- const processSchema = (subject, id, dialectId, pointer, anchors, dynamicAnchors) => {
73
- if (jsonTypeOf(subject, "object")) {
132
+ const processSchema = (json, id, dialectId, cursor, embedded, anchors, dynamicAnchors) => {
133
+ if (jsonTypeOf(json) === "object") {
74
134
  // Embedded Schema
75
- const embeddedDialectId = typeof subject.$schema === "string" ? toAbsoluteIri(subject.$schema) : dialectId;
76
-
135
+ const embeddedDialectId = typeof json.$schema === "string" ? toAbsoluteIri(json.$schema) : dialectId;
77
136
  const idToken = getKeywordName(embeddedDialectId, "https://json-schema.org/keyword/id");
78
- if (typeof subject[idToken] === "string") {
79
- subject[idToken] = resolveUri(subject[idToken], id);
80
- add(subject, undefined, dialectId);
81
- return Reference.cons(subject[idToken], subject);
137
+
138
+ if (typeof json[idToken] === "string") {
139
+ const embeddedId = toAbsoluteIri(resolveIri(json[idToken], id));
140
+ json[idToken] = embeddedId;
141
+ embedded[embeddedId] = buildSchemaDocument(json, embeddedId, embeddedDialectId, embedded);
142
+ return new Reference(embeddedId, {});
82
143
  }
83
144
 
84
145
  // Legacy id
85
146
  const legacyIdToken = getKeywordName(embeddedDialectId, "https://json-schema.org/keyword/draft-04/id");
86
- if (typeof subject[legacyIdToken] === "string") {
87
- if (subject[legacyIdToken][0] === "#") {
88
- const anchor = decodeURIComponent(subject[legacyIdToken].slice(1));
89
- anchors[anchor] = pointer;
147
+ if (typeof json[legacyIdToken] === "string") {
148
+ if (json[legacyIdToken][0] === "#") {
149
+ const anchor = decodeURIComponent(json[legacyIdToken].slice(1));
150
+ anchors[anchor] = cursor;
151
+ delete json[legacyIdToken];
90
152
  } else {
91
- delete subject[legacyIdToken].$schema;
92
- subject[legacyIdToken] = resolveUri(subject[legacyIdToken], id);
93
- add(subject, undefined, dialectId);
94
- return Reference.cons(subject[legacyIdToken], subject);
153
+ const embeddedId = toAbsoluteIri(resolveIri(json[legacyIdToken], id));
154
+ json[legacyIdToken] = embeddedId;
155
+ embedded[embeddedId] = buildSchemaDocument(json, embeddedId, embeddedDialectId, embedded);
156
+ return new Reference(embeddedId, {});
95
157
  }
96
- delete subject[legacyIdToken];
97
- }
98
-
99
- const dynamicAnchorToken = getKeywordName(dialectId, "https://json-schema.org/keyword/dynamicAnchor");
100
- if (typeof subject[dynamicAnchorToken] === "string") {
101
- dynamicAnchors[subject[dynamicAnchorToken]] = `${id}#${encodeURI(pointer)}`;
102
- delete subject[dynamicAnchorToken];
103
- }
104
-
105
- // Legacy dynamic anchor
106
- const legacyDynamicAnchorToken = getKeywordName(dialectId, "https://json-schema.org/keyword/draft-2020-12/dynamicAnchor");
107
- if (typeof subject[legacyDynamicAnchorToken] === "string") {
108
- dynamicAnchors[subject[legacyDynamicAnchorToken]] = `${id}#${encodeURI(pointer)}`;
109
- anchors[subject[legacyDynamicAnchorToken]] = pointer;
110
- delete subject[legacyDynamicAnchorToken];
111
- }
112
-
113
- const anchorToken = getKeywordName(dialectId, "https://json-schema.org/keyword/anchor");
114
- if (typeof subject[anchorToken] === "string") {
115
- anchors[subject[anchorToken]] = pointer;
116
- delete subject[anchorToken];
117
158
  }
118
159
 
119
160
  // Legacy $ref
120
161
  const jrefToken = getKeywordName(dialectId, "https://json-schema.org/keyword/draft-04/ref");
121
- if (typeof subject[jrefToken] === "string") {
122
- return Reference.cons(subject[jrefToken], subject);
162
+ if (typeof json[jrefToken] === "string") {
163
+ return new Reference(json[jrefToken], json);
123
164
  }
124
165
 
125
- for (const key in subject) {
126
- subject[key] = processSchema(subject[key], id, dialectId, pointerAppend(key, pointer), anchors, dynamicAnchors);
127
- }
128
- } else if (Array.isArray(subject)) {
129
- for (let index = 0; index < subject.length; index++) {
130
- subject[index] = processSchema(subject[index], id, dialectId, pointerAppend(index, pointer), anchors, dynamicAnchors);
166
+ // Anchors
167
+ const anchorToken = getKeywordName(dialectId, "https://json-schema.org/keyword/anchor");
168
+ if (typeof json[anchorToken] === "string") {
169
+ anchors[json[anchorToken]] = cursor;
170
+ delete json[anchorToken];
131
171
  }
132
- }
133
-
134
- return subject;
135
- };
136
-
137
- const hasStoredSchema = (id) => id in schemaStore || id in schemaStoreAlias;
138
- const getStoredSchema = (id) => schemaStore[schemaStoreAlias[id]] || schemaStore[id];
139
-
140
- export const markValidated = (id) => {
141
- schemaStore[id].validated = true;
142
- };
143
-
144
- // Schema Retrieval
145
- const nil = {
146
- id: undefined,
147
- dialectId: undefined,
148
- pointer: nilPointer,
149
- schema: undefined,
150
- value: undefined,
151
- anchors: {},
152
- dynamicAnchors: {},
153
- validated: true
154
- };
155
-
156
- export const get = async (url, contextDoc = nil) => {
157
- const resolvedUrl = resolveUri(url, contextDoc.id || contextUri());
158
- const id = toAbsoluteIri(resolvedUrl);
159
- const fragment = uriFragment(resolvedUrl);
160
172
 
161
- if (!hasStoredSchema(id)) {
162
- const response = await fetch(id, { headers: { Accept: acceptableMediaTypes() } });
163
- if (response.status >= 400) {
164
- await response.text(); // Sometimes node hangs without this hack
165
- throw Error(`Failed to retrieve ${id} (${response.status} ${response.statusText})`);
173
+ // Dynamic Anchors
174
+ const dynamicAnchorToken = getKeywordName(dialectId, "https://json-schema.org/keyword/dynamicAnchor");
175
+ if (typeof json[dynamicAnchorToken] === "string") {
176
+ dynamicAnchors[json[dynamicAnchorToken]] = `${id}#${encodeURI(cursor)}`;
177
+ delete json[dynamicAnchorToken];
166
178
  }
167
179
 
168
- const [schema, contextDialectId] = await parseResponse(response);
180
+ // Legacy dynamic anchor
181
+ const legacyDynamicAnchorToken = getKeywordName(dialectId, "https://json-schema.org/keyword/draft-2020-12/dynamicAnchor");
182
+ if (typeof json[legacyDynamicAnchorToken] === "string") {
183
+ dynamicAnchors[json[legacyDynamicAnchorToken]] = `${id}#${encodeURI(cursor)}`;
184
+ anchors[json[legacyDynamicAnchorToken]] = cursor;
185
+ delete json[legacyDynamicAnchorToken];
186
+ }
169
187
 
170
- // Try to determine the dialect from the meta-schema if it isn't already known
171
- if (schema.$schema || contextDialectId) {
172
- const dialectId = toAbsoluteIri(schema.$schema || contextDialectId);
173
- if (!hasDialect(dialectId) && !hasStoredSchema(dialectId)) {
174
- await get(dialectId);
188
+ for (const key in json) {
189
+ // References
190
+ const referenceToken = getKeywordName(dialectId, "https://json-schema.org/keyword/ref");
191
+ if (key === referenceToken && typeof json[key] === "string") {
192
+ json[key] = new Reference(json[key], json[key]);
193
+ } else {
194
+ json[key] = processSchema(json[key], id, dialectId, append(key, cursor), embedded, anchors, dynamicAnchors);
175
195
  }
176
196
  }
177
-
178
- add(schema, id, contextDialectId);
179
- }
180
-
181
- const storedSchema = getStoredSchema(id);
182
- const pointer = fragment[0] === "/" ? fragment : getAnchorPointer(storedSchema, fragment);
183
- const doc = {
184
- ...storedSchema,
185
- pointer: pointer,
186
- value: pointerGet(pointer, storedSchema.schema)
187
- };
188
-
189
- return followReferences(doc);
190
- };
191
-
192
- const followReferences = (doc) => Reference.isReference(doc.value) ? get(Reference.href(doc.value), doc) : doc;
193
-
194
- const getAnchorPointer = (schema, fragment) => {
195
- if (!(fragment in schema.anchors)) {
196
- throw Error(`No such anchor '${encodeURI(schema.id)}#${encodeURI(fragment)}'`);
197
- }
198
-
199
- return schema.anchors[fragment];
200
- };
201
-
202
- // Utility Functions
203
- export const uri = (doc) => doc.id ? `${doc.id}#${encodeURI(doc.pointer)}` : undefined;
204
- export const value = (doc) => doc.value;
205
- export const has = (key, doc) => key in value(doc);
206
- export const typeOf = (doc, type) => jsonTypeOf(value(doc), type);
207
-
208
- export const step = (key, doc) => {
209
- const storedSchema = getStoredSchema(doc.id);
210
- return followReferences({
211
- ...doc,
212
- pointer: pointerAppend(`${key}`, doc.pointer),
213
- value: value(doc)[key],
214
- validated: storedSchema.validated
215
- });
216
- };
217
-
218
- export const iter = async function* (doc) {
219
- for (let index = 0; index < value(doc).length; index++) {
220
- yield step(index, doc);
221
- }
222
- };
223
-
224
- export const keys = function* (doc) {
225
- for (const key in value(doc)) {
226
- yield key;
227
- }
228
- };
229
-
230
- export const values = async function* (doc) {
231
- for (const key in value(doc)) {
232
- yield step(key, doc);
233
- }
234
- };
235
-
236
- export const entries = async function* (doc) {
237
- for (const key in value(doc)) {
238
- yield [key, await step(key, doc)];
197
+ } else if (Array.isArray(json)) {
198
+ for (let index = 0; index < json.length; index++) {
199
+ json[index] = processSchema(json[index], id, dialectId, append(index, cursor), embedded, anchors, dynamicAnchors);
200
+ }
239
201
  }
240
- };
241
-
242
- export const length = (doc) => value(doc).length;
243
202
 
244
- const toSchemaDefaultOptions = {
245
- parentId: "",
246
- parentDialect: "",
247
- includeEmbedded: true
203
+ return json;
248
204
  };
249
- export const toSchema = (schemaDoc, options = {}) => {
250
- const fullOptions = { ...toSchemaDefaultOptions, ...options };
251
205
 
252
- const idToken = getKeywordName(schemaDoc.dialectId, "https://json-schema.org/keyword/id")
253
- || getKeywordName(schemaDoc.dialectId, "https://json-schema.org/keyword/draft-04/id");
254
- const anchorToken = getKeywordName(schemaDoc.dialectId, "https://json-schema.org/keyword/anchor");
255
- const legacyAnchorToken = getKeywordName(schemaDoc.dialectId, "https://json-schema.org/keyword/draft-04/id");
256
- const dynamicAnchorToken = getKeywordName(schemaDoc.dialectId, "https://json-schema.org/keyword/dynamicAnchor");
257
- const legacyDynamicAnchorToken = getKeywordName(schemaDoc.dialectId, "https://json-schema.org/keyword/draft-2020-12/dynamicAnchor");
258
- const recursiveAnchorToken = getKeywordName(schemaDoc.dialectId, "https://json-schema.org/keyword/recursiveAnchor");
206
+ export const canonicalUri = (browser) => `${browser.document.baseUri}#${encodeURI(browser.cursor)}`;
259
207
 
208
+ export const toSchema = (browser, options = {}) => {
260
209
  const anchors = {};
261
- for (const anchor in schemaDoc.anchors) {
262
- if (anchor !== "" && !schemaDoc.dynamicAnchors[anchor]) {
263
- anchors[schemaDoc.anchors[anchor]] = anchor;
210
+ for (const anchor in browser.document.anchors) {
211
+ if (anchor !== "" && !browser.document.dynamicAnchors[anchor]) {
212
+ anchors[browser.document.anchors[anchor]] = anchor;
264
213
  }
265
214
  }
266
215
 
267
216
  const dynamicAnchors = {};
268
- for (const anchor in schemaDoc.dynamicAnchors) {
269
- const pointer = uriFragment(schemaDoc.dynamicAnchors[anchor]);
217
+ for (const anchor in browser.document.dynamicAnchors) {
218
+ const pointer = uriFragment(browser.document.dynamicAnchors[anchor]);
270
219
  dynamicAnchors[pointer] = anchor;
271
220
  }
272
221
 
273
- const schema = JSON.parse(jsonStringify(schemaDoc.schema, (_key, value, pointer) => {
274
- if (Reference.isReference(value)) {
275
- const refValue = Reference.value(value);
276
- if (fullOptions.includeEmbedded || !(idToken in refValue)) {
277
- return Reference.value(value);
222
+ const legacyIdToken = getKeywordName(browser.document.dialectId, "https://json-schema.org/keyword/draft-04/id");
223
+ const idToken = getKeywordName(browser.document.dialectId, "https://json-schema.org/keyword/id") || legacyIdToken;
224
+ const anchorToken = getKeywordName(browser.document.dialectId, "https://json-schema.org/keyword/anchor");
225
+ const legacyAnchorToken = getKeywordName(browser.document.dialectId, "https://json-schema.org/keyword/draft-04/id");
226
+ const dynamicAnchorToken = getKeywordName(browser.document.dialectId, "https://json-schema.org/keyword/dynamicAnchor");
227
+ const legacyDynamicAnchorToken = getKeywordName(browser.document.dialectId, "https://json-schema.org/keyword/draft-2020-12/dynamicAnchor");
228
+ const recursiveAnchorToken = getKeywordName(browser.document.dialectId, "https://json-schema.org/keyword/draft-2019-09/recursiveAnchor");
229
+ const refToken = getKeywordName(browser.document.dialectId, "https://json-schema.org/keyword/ref");
230
+ const legacyRefToken = getKeywordName(browser.document.dialectId, "https://json-schema.org/keyword/draft-04/ref");
231
+
232
+ let schema = JSON.parse(jsonStringify(browser.document.root, (key, value, pointer) => {
233
+ if (value instanceof Reference) {
234
+ if (key === refToken) {
235
+ return value.href;
236
+ } else if (legacyIdToken) {
237
+ if (JSON.stringify(value.toJSON()) === "{}") {
238
+ return toSchema({ document: browser.document.embedded[toAbsoluteIri(value.href)] }, {
239
+ ...options,
240
+ contextDialectId: browser.document.dialectId,
241
+ selfIdentify: true,
242
+ contextUri: browser.document.baseUri
243
+ });
244
+ } else {
245
+ return { [legacyRefToken]: value.href };
246
+ }
247
+ } else if (options.includeEmbedded ?? true) {
248
+ return toSchema({ document: browser.document.embedded[toAbsoluteIri(value.href)] }, {
249
+ ...options,
250
+ contextDialectId: browser.document.dialectId,
251
+ selfIdentify: true,
252
+ contextUri: browser.document.baseUri
253
+ });
254
+ } else {
255
+ return;
278
256
  }
279
- } else {
257
+ }
258
+
259
+ if (jsonTypeOf(value) === "object") {
260
+ value = { ...value };
280
261
  if (pointer in anchors) {
281
262
  if (anchorToken) {
282
- value = { [anchorToken]: anchors[pointer], ...value };
263
+ value[anchorToken] = anchors[pointer];
283
264
  }
284
265
 
285
266
  // Legacy anchor
286
267
  if (legacyAnchorToken) {
287
- value = { [legacyAnchorToken]: `#${anchors[pointer]}`, ...value };
268
+ value[legacyAnchorToken] = `#${anchors[pointer]}`;
288
269
  }
289
270
  }
271
+
290
272
  if (pointer in dynamicAnchors) {
291
273
  if (dynamicAnchorToken) {
292
- value = { [dynamicAnchorToken]: dynamicAnchors[pointer], ...value };
274
+ value[dynamicAnchorToken] = dynamicAnchors[pointer];
293
275
  }
294
276
 
295
277
  // Legacy dynamic anchor
296
278
  if (legacyDynamicAnchorToken) {
297
- value = { [legacyDynamicAnchorToken]: dynamicAnchors[pointer], ...value };
279
+ value[legacyDynamicAnchorToken] = dynamicAnchors[pointer];
298
280
  }
299
281
 
300
282
  // Recursive anchor
301
283
  if (recursiveAnchorToken) {
302
- value = { [recursiveAnchorToken]: true, ...value };
284
+ value[recursiveAnchorToken] = true;
303
285
  }
304
286
  }
305
- return value;
306
287
  }
288
+
289
+ return value;
307
290
  }));
308
291
 
309
- const id = relativeUri(fullOptions.parentId, schemaDoc.id);
310
- const dialect = fullOptions.parentDialect === schemaDoc.dialectId ? "" : schemaDoc.dialectId;
311
- return {
312
- ...id && { [idToken]: id },
313
- ...dialect && { $schema: dialect },
314
- ...schema
315
- };
316
- };
292
+ const definitionsToken = getKeywordName(browser.document.dialectId, "https://json-schema.org/keyword/definitions");
293
+ if (Reflect.ownKeys(schema[definitionsToken] ?? {}).length === 0) {
294
+ delete schema[definitionsToken];
295
+ }
317
296
 
318
- const relativeUri = (from, to) => {
319
- if (to.startsWith("file://")) {
320
- const pathToSchema = from.slice(7, from.lastIndexOf("/"));
321
- return from === "" ? "" : pathRelative(pathToSchema, to.slice(7));
322
- } else {
323
- return to;
297
+ // Self-identification
298
+ if (options.selfIdentify ?? false) {
299
+ if (options.contextUri) {
300
+ schema = {
301
+ [idToken]: toRelativeIri(normalizeIri(options.contextUri), browser.document.baseUri),
302
+ ...schema
303
+ };
304
+ } else {
305
+ schema = {
306
+ [idToken]: browser.document.baseUri,
307
+ ...schema
308
+ };
309
+ }
310
+ }
311
+
312
+ // $schema
313
+ switch (options.includeDialect ?? "auto") {
314
+ case "auto":
315
+ if (browser.document.dialectId === options.contextDialectId) {
316
+ break;
317
+ }
318
+ case "always":
319
+ schema = {
320
+ $schema: browser.document.dialectId,
321
+ ...schema
322
+ };
323
+ if (legacyIdToken) {
324
+ schema.$schema += "#";
325
+ }
326
+ break;
327
+ case "never":
328
+ break;
329
+ default:
330
+ throw Error(`Unsupported value ToSchemaOptions.includeDialect: '${options.includeDialect}'`);
324
331
  }
332
+
333
+ return schema;
325
334
  };
@@ -1,9 +1,9 @@
1
1
  import { addKeyword, defineVocabulary, loadDialect } from "../lib/keywords.js";
2
- import { addSchema } from "../lib/index.js";
2
+ import { registerSchema } from "../lib/index.js";
3
3
  import "../lib/openapi.js";
4
4
 
5
5
  import dialectSchema from "./dialect.js";
6
- import schema20210928 from "./schema/2021-09-28.js";
6
+ import schema from "./schema.js";
7
7
 
8
8
  import discriminator from "./discriminator.js";
9
9
  import example from "./example.js";
@@ -69,9 +69,9 @@ loadDialect("https://spec.openapis.org/oas/3.0/schema", {
69
69
  [jsonSchemaVersion]: true
70
70
  });
71
71
 
72
- addSchema(dialectSchema);
72
+ registerSchema(dialectSchema);
73
73
 
74
- addSchema(schema20210928, "https://spec.openapis.org/oas/3.0/schema");
75
- addSchema(schema20210928, "https://spec.openapis.org/oas/3.0/schema/latest");
74
+ registerSchema(schema, "https://spec.openapis.org/oas/3.0/schema");
75
+ registerSchema(schema, "https://spec.openapis.org/oas/3.0/schema/latest");
76
76
 
77
77
  export * from "../draft-04/index.js";
@@ -1,16 +1,22 @@
1
- import { getKeywordName } from "../lib/keywords.js";
2
- import * as Schema from "../lib/schema.js";
1
+ import * as Browser from "@hyperjump/browser";
3
2
  import * as Instance from "../lib/instance.js";
3
+ import { getKeywordName } from "../lib/experimental.js";
4
4
 
5
5
 
6
6
  const id = "https://spec.openapis.org/oas/3.0/keyword/type";
7
7
 
8
- const compile = async (schema, ast, parentSchema) => {
9
- const nullableKeyword = getKeywordName(schema.dialectId, "https://spec.openapis.org/oas/3.0/keyword/nullable");
10
- const nullable = await Schema.step(nullableKeyword, parentSchema);
11
- return Schema.value(nullable) === true ? ["null", Schema.value(schema)] : Schema.value(schema);
8
+ const compile = async (schema, _ast, parentSchema) => {
9
+ const nullableKeyword = getKeywordName(schema.document.dialectId, "https://spec.openapis.org/oas/3.0/keyword/nullable");
10
+ const nullable = await Browser.step(nullableKeyword, parentSchema);
11
+ return Browser.value(nullable) === true ? ["null", Browser.value(schema)] : Browser.value(schema);
12
12
  };
13
13
 
14
- const interpret = (type, instance) => typeof type === "string" ? Instance.typeOf(instance, type) : type.some(Instance.typeOf(instance));
14
+ const interpret = (type, instance) => typeof type === "string"
15
+ ? isTypeOf(instance)(type)
16
+ : type.some(isTypeOf(instance));
17
+
18
+ const isTypeOf = (instance) => (type) => type === "integer"
19
+ ? Instance.typeOf(instance) === "number" && Number.isInteger(Instance.value(instance))
20
+ : Instance.typeOf(instance) === type;
15
21
 
16
22
  export default { id, compile, interpret };