@apidevtools/json-schema-ref-parser 15.3.0 → 15.3.2
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/dist/lib/bundle.js +65 -16
- package/dist/lib/dereference.js +19 -9
- package/dist/lib/index.js +3 -0
- package/dist/lib/options.d.ts +22 -0
- package/dist/lib/parse.d.ts +6 -1
- package/dist/lib/parse.js +15 -1
- package/dist/lib/pointer.d.ts +4 -0
- package/dist/lib/pointer.js +28 -2
- package/dist/lib/ref.d.ts +4 -0
- package/dist/lib/ref.js +4 -0
- package/dist/lib/refs.d.ts +3 -0
- package/dist/lib/refs.js +24 -8
- package/dist/lib/resolve-external.js +22 -9
- package/dist/lib/types/index.d.ts +11 -0
- package/dist/lib/util/schema-resources.d.ts +6 -0
- package/dist/lib/util/schema-resources.js +47 -0
- package/lib/bundle.ts +101 -13
- package/lib/dereference.ts +27 -2
- package/lib/index.ts +3 -0
- package/lib/options.ts +24 -0
- package/lib/parse.ts +22 -1
- package/lib/pointer.ts +31 -2
- package/lib/ref.ts +5 -0
- package/lib/refs.ts +30 -8
- package/lib/resolve-external.ts +45 -7
- package/lib/types/index.ts +13 -0
- package/lib/util/schema-resources.ts +73 -0
- package/package.json +12 -12
package/dist/lib/bundle.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import $Ref from "./ref.js";
|
|
2
2
|
import Pointer from "./pointer.js";
|
|
3
3
|
import * as url from "./util/url.js";
|
|
4
|
+
import { getSchemaBasePath } from "./util/schema-resources.js";
|
|
4
5
|
/**
|
|
5
6
|
* Bundles all external JSON references into the main JSON schema, thus resulting in a schema that
|
|
6
7
|
* only has *internal* references, not any *external* references.
|
|
@@ -13,11 +14,18 @@ function bundle(parser, options) {
|
|
|
13
14
|
// console.log('Bundling $ref pointers in %s', parser.$refs._root$Ref.path);
|
|
14
15
|
// Build an inventory of all $ref pointers in the JSON Schema
|
|
15
16
|
const inventory = [];
|
|
16
|
-
crawl(parser, "schema", parser.$refs._root$Ref.path + "#", "#", 0, inventory, parser.$refs, options);
|
|
17
|
+
crawl(parser, "schema", parser.$refs._root$Ref.path + "#", parser.$refs._root$Ref.path, parser.$refs._root$Ref.dynamicIdScope, "#", 0, inventory, parser.$refs, options);
|
|
18
|
+
// Get the root schema's $id (if any) for qualifying refs inside sub-schemas with their own $id
|
|
19
|
+
const rootId = parser.schema && typeof parser.schema === "object" && "$id" in parser.schema
|
|
20
|
+
? parser.schema.$id
|
|
21
|
+
: undefined;
|
|
17
22
|
// Remap all $ref pointers
|
|
18
|
-
remap(inventory, options);
|
|
23
|
+
remap(inventory, options, rootId);
|
|
19
24
|
// Fix any $ref paths that traverse through other $refs (which is invalid per JSON Schema spec)
|
|
20
|
-
|
|
25
|
+
const bundleOptions = (options.bundle || {});
|
|
26
|
+
if (bundleOptions.optimizeInternalRefs !== false) {
|
|
27
|
+
fixRefsThroughRefs(inventory, parser.schema);
|
|
28
|
+
}
|
|
21
29
|
}
|
|
22
30
|
/**
|
|
23
31
|
* Recursively crawls the given value, and inventories all JSON references.
|
|
@@ -31,13 +39,14 @@ function bundle(parser, options) {
|
|
|
31
39
|
* @param $refs
|
|
32
40
|
* @param options
|
|
33
41
|
*/
|
|
34
|
-
function crawl(parent, key, path, pathFromRoot, indirections, inventory, $refs, options) {
|
|
42
|
+
function crawl(parent, key, path, scopeBase, dynamicIdScope, pathFromRoot, indirections, inventory, $refs, options) {
|
|
35
43
|
const obj = key === null ? parent : parent[key];
|
|
36
44
|
const bundleOptions = (options.bundle || {});
|
|
37
45
|
const isExcludedPath = bundleOptions.excludedPathMatcher || (() => false);
|
|
38
46
|
if (obj && typeof obj === "object" && !ArrayBuffer.isView(obj) && !isExcludedPath(pathFromRoot)) {
|
|
47
|
+
const currentScopeBase = dynamicIdScope ? getSchemaBasePath(scopeBase, obj) : scopeBase;
|
|
39
48
|
if ($Ref.isAllowed$Ref(obj)) {
|
|
40
|
-
inventory$Ref(parent, key, path, pathFromRoot, indirections, inventory, $refs, options);
|
|
49
|
+
inventory$Ref(parent, key, path, currentScopeBase, dynamicIdScope, pathFromRoot, indirections, inventory, $refs, options);
|
|
41
50
|
}
|
|
42
51
|
else {
|
|
43
52
|
// Crawl the object in a specific order that's optimized for bundling.
|
|
@@ -63,10 +72,11 @@ function crawl(parent, key, path, pathFromRoot, indirections, inventory, $refs,
|
|
|
63
72
|
const keyPathFromRoot = Pointer.join(pathFromRoot, key);
|
|
64
73
|
const value = obj[key];
|
|
65
74
|
if ($Ref.isAllowed$Ref(value)) {
|
|
66
|
-
|
|
75
|
+
const valueScopeBase = dynamicIdScope ? getSchemaBasePath(currentScopeBase, value) : currentScopeBase;
|
|
76
|
+
inventory$Ref(obj, key, keyPath, valueScopeBase, dynamicIdScope, keyPathFromRoot, indirections, inventory, $refs, options);
|
|
67
77
|
}
|
|
68
78
|
else {
|
|
69
|
-
crawl(obj, key, keyPath, keyPathFromRoot, indirections, inventory, $refs, options);
|
|
79
|
+
crawl(obj, key, keyPath, currentScopeBase, dynamicIdScope, keyPathFromRoot, indirections, inventory, $refs, options);
|
|
70
80
|
}
|
|
71
81
|
// We need to ensure that we have an object to work with here because we may be crawling
|
|
72
82
|
// an `examples` schema and `value` may be nullish.
|
|
@@ -92,9 +102,9 @@ function crawl(parent, key, path, pathFromRoot, indirections, inventory, $refs,
|
|
|
92
102
|
* @param $refs
|
|
93
103
|
* @param options
|
|
94
104
|
*/
|
|
95
|
-
function inventory$Ref($refParent, $refKey, path, pathFromRoot, indirections, inventory, $refs, options) {
|
|
105
|
+
function inventory$Ref($refParent, $refKey, path, scopeBase, dynamicIdScope, pathFromRoot, indirections, inventory, $refs, options) {
|
|
96
106
|
const $ref = $refKey === null ? $refParent : $refParent[$refKey];
|
|
97
|
-
const $refPath = url.resolve(path, $ref.$ref);
|
|
107
|
+
const $refPath = url.resolve(dynamicIdScope ? scopeBase : path, $ref.$ref);
|
|
98
108
|
const pointer = $refs._resolve($refPath, pathFromRoot, options);
|
|
99
109
|
if (pointer === null) {
|
|
100
110
|
return;
|
|
@@ -103,7 +113,7 @@ function inventory$Ref($refParent, $refKey, path, pathFromRoot, indirections, in
|
|
|
103
113
|
const depth = parsed.length;
|
|
104
114
|
const file = url.stripHash(pointer.path);
|
|
105
115
|
const hash = url.getHash(pointer.path);
|
|
106
|
-
const external = file !== $refs._root$Ref.path;
|
|
116
|
+
const external = file !== $refs._root$Ref.path && !$refs._aliases[file];
|
|
107
117
|
const extended = $Ref.isExtended$Ref($ref);
|
|
108
118
|
indirections += pointer.indirections;
|
|
109
119
|
const existingEntry = findInInventory(inventory, $refParent, $refKey);
|
|
@@ -132,7 +142,7 @@ function inventory$Ref($refParent, $refKey, path, pathFromRoot, indirections, in
|
|
|
132
142
|
});
|
|
133
143
|
// Recursively crawl the resolved value
|
|
134
144
|
if (!existingEntry || external) {
|
|
135
|
-
crawl(pointer.value, null, pointer.path, pathFromRoot, indirections + 1, inventory, $refs, options);
|
|
145
|
+
crawl(pointer.value, null, pointer.path, pointer.$ref.path, pointer.$ref.dynamicIdScope, pathFromRoot, indirections + 1, inventory, $refs, options);
|
|
136
146
|
}
|
|
137
147
|
}
|
|
138
148
|
/**
|
|
@@ -157,7 +167,7 @@ function inventory$Ref($refParent, $refKey, path, pathFromRoot, indirections, in
|
|
|
157
167
|
*
|
|
158
168
|
* @param inventory
|
|
159
169
|
*/
|
|
160
|
-
function remap(inventory, options) {
|
|
170
|
+
function remap(inventory, options, rootId) {
|
|
161
171
|
// Group & sort all the $ref pointers, so they're in the order that we need to dereference/remap them
|
|
162
172
|
inventory.sort((a, b) => {
|
|
163
173
|
if (a.file !== b.file) {
|
|
@@ -202,17 +212,35 @@ function remap(inventory, options) {
|
|
|
202
212
|
let file, hash, pathFromRoot;
|
|
203
213
|
for (const entry of inventory) {
|
|
204
214
|
// console.log('Re-mapping $ref pointer "%s" at %s', entry.$ref.$ref, entry.pathFromRoot);
|
|
215
|
+
const bundleOpts = (options.bundle || {});
|
|
205
216
|
if (!entry.external) {
|
|
206
|
-
// This $ref already resolves to the main JSON Schema file
|
|
207
|
-
|
|
217
|
+
// This $ref already resolves to the main JSON Schema file.
|
|
218
|
+
// When optimizeInternalRefs is false, preserve the original internal ref path
|
|
219
|
+
// instead of rewriting it to the fully resolved hash.
|
|
220
|
+
if (bundleOpts.optimizeInternalRefs !== false) {
|
|
221
|
+
entry.$ref.$ref = entry.hash;
|
|
222
|
+
}
|
|
208
223
|
}
|
|
209
224
|
else if (entry.file === file && entry.hash === hash) {
|
|
210
225
|
// This $ref points to the same value as the previous $ref, so remap it to the same path
|
|
211
|
-
entry
|
|
226
|
+
if (rootId && isInsideIdScope(inventory, entry)) {
|
|
227
|
+
// This entry is inside a sub-schema with its own $id, so a bare root-relative JSON Pointer
|
|
228
|
+
// would be resolved relative to that $id, not the document root. Qualify with the root $id.
|
|
229
|
+
entry.$ref.$ref = rootId + pathFromRoot;
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
entry.$ref.$ref = pathFromRoot;
|
|
233
|
+
}
|
|
212
234
|
}
|
|
213
235
|
else if (entry.file === file && entry.hash.indexOf(hash + "/") === 0) {
|
|
214
236
|
// This $ref points to a sub-value of the previous $ref, so remap it beneath that path
|
|
215
|
-
|
|
237
|
+
const subPath = Pointer.join(pathFromRoot, Pointer.parse(entry.hash.replace(hash, "#")));
|
|
238
|
+
if (rootId && isInsideIdScope(inventory, entry)) {
|
|
239
|
+
entry.$ref.$ref = rootId + subPath;
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
entry.$ref.$ref = subPath;
|
|
243
|
+
}
|
|
216
244
|
}
|
|
217
245
|
else {
|
|
218
246
|
// We've moved to a new file or new hash
|
|
@@ -339,4 +367,25 @@ function walkPath(schema, path) {
|
|
|
339
367
|
}
|
|
340
368
|
return current;
|
|
341
369
|
}
|
|
370
|
+
/**
|
|
371
|
+
* Checks whether the given inventory entry is located inside a sub-schema that has its own $id.
|
|
372
|
+
* If so, root-relative JSON Pointer $refs placed at this location would be resolved against
|
|
373
|
+
* the $id base URI rather than the document root, making them invalid.
|
|
374
|
+
*/
|
|
375
|
+
function isInsideIdScope(inventory, entry) {
|
|
376
|
+
for (const other of inventory) {
|
|
377
|
+
// Skip root-level entries
|
|
378
|
+
if (other.pathFromRoot === "#" || other.pathFromRoot === "#/") {
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
// Check if the other entry is an ancestor of the current entry
|
|
382
|
+
if (entry.pathFromRoot.startsWith(other.pathFromRoot + "/")) {
|
|
383
|
+
// Check if the ancestor's resolved value has a $id
|
|
384
|
+
if (other.value && typeof other.value === "object" && "$id" in other.value) {
|
|
385
|
+
return true;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return false;
|
|
390
|
+
}
|
|
342
391
|
export default bundle;
|
package/dist/lib/dereference.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import $Ref from "./ref.js";
|
|
2
2
|
import Pointer from "./pointer.js";
|
|
3
3
|
import * as url from "./util/url.js";
|
|
4
|
+
import { getSchemaBasePath } from "./util/schema-resources.js";
|
|
4
5
|
import { TimeoutError } from "./util/errors.js";
|
|
5
6
|
export default dereference;
|
|
6
7
|
/**
|
|
@@ -13,7 +14,7 @@ export default dereference;
|
|
|
13
14
|
function dereference(parser, options) {
|
|
14
15
|
const start = Date.now();
|
|
15
16
|
// console.log('Dereferencing $ref pointers in %s', parser.$refs._root$Ref.path);
|
|
16
|
-
const dereferenced = crawl(parser.schema, parser.$refs._root$Ref.path, "#", new Set(), new Set(), new Map(), parser.$refs, options, start, 0);
|
|
17
|
+
const dereferenced = crawl(parser.schema, parser.$refs._root$Ref.path, parser.$refs._root$Ref.path, parser.$refs._root$Ref.dynamicIdScope, "#", new Set(), new Set(), new Map(), parser.$refs, options, start, 0);
|
|
17
18
|
parser.$refs.circular = dereferenced.circular;
|
|
18
19
|
parser.schema = dereferenced.value;
|
|
19
20
|
}
|
|
@@ -32,7 +33,7 @@ function dereference(parser, options) {
|
|
|
32
33
|
* @param depth - The current recursion depth
|
|
33
34
|
* @returns
|
|
34
35
|
*/
|
|
35
|
-
function crawl(obj, path, pathFromRoot, parents, processedObjects, dereferencedCache, $refs, options, startTime, depth) {
|
|
36
|
+
function crawl(obj, path, scopeBase, dynamicIdScope, pathFromRoot, parents, processedObjects, dereferencedCache, $refs, options, startTime, depth) {
|
|
36
37
|
let dereferenced;
|
|
37
38
|
const result = {
|
|
38
39
|
value: obj,
|
|
@@ -51,8 +52,9 @@ function crawl(obj, path, pathFromRoot, parents, processedObjects, dereferencedC
|
|
|
51
52
|
if (obj && typeof obj === "object" && !ArrayBuffer.isView(obj) && !isExcludedPath(pathFromRoot)) {
|
|
52
53
|
parents.add(obj);
|
|
53
54
|
processedObjects.add(obj);
|
|
55
|
+
const currentScopeBase = dynamicIdScope ? getSchemaBasePath(scopeBase, obj) : scopeBase;
|
|
54
56
|
if ($Ref.isAllowed$Ref(obj, options)) {
|
|
55
|
-
dereferenced = dereference$Ref(obj, path, pathFromRoot, parents, processedObjects, dereferencedCache, $refs, options, startTime, depth);
|
|
57
|
+
dereferenced = dereference$Ref(obj, path, currentScopeBase, dynamicIdScope, pathFromRoot, parents, processedObjects, dereferencedCache, $refs, options, startTime, depth);
|
|
56
58
|
result.circular = dereferenced.circular;
|
|
57
59
|
result.value = dereferenced.value;
|
|
58
60
|
}
|
|
@@ -67,7 +69,8 @@ function crawl(obj, path, pathFromRoot, parents, processedObjects, dereferencedC
|
|
|
67
69
|
const value = obj[key];
|
|
68
70
|
let circular;
|
|
69
71
|
if ($Ref.isAllowed$Ref(value, options)) {
|
|
70
|
-
|
|
72
|
+
const valueScopeBase = dynamicIdScope ? getSchemaBasePath(currentScopeBase, value) : currentScopeBase;
|
|
73
|
+
dereferenced = dereference$Ref(value, keyPath, valueScopeBase, dynamicIdScope, keyPathFromRoot, parents, processedObjects, dereferencedCache, $refs, options, startTime, depth);
|
|
71
74
|
circular = dereferenced.circular;
|
|
72
75
|
// Avoid pointless mutations; breaks frozen objects to no profit
|
|
73
76
|
if (obj[key] !== dereferenced.value) {
|
|
@@ -83,7 +86,13 @@ function crawl(obj, path, pathFromRoot, parents, processedObjects, dereferencedC
|
|
|
83
86
|
});
|
|
84
87
|
}
|
|
85
88
|
}
|
|
86
|
-
|
|
89
|
+
// Clone the dereferenced value if cloneReferences is enabled and this is not a
|
|
90
|
+
// circular reference. This prevents mutations to one location from affecting others.
|
|
91
|
+
let assignedValue = dereferenced.value;
|
|
92
|
+
if (derefOptions?.cloneReferences && !circular && assignedValue && typeof assignedValue === "object") {
|
|
93
|
+
assignedValue = structuredClone(assignedValue);
|
|
94
|
+
}
|
|
95
|
+
obj[key] = assignedValue;
|
|
87
96
|
// If we have data to preserve and our dereferenced object is still an object then
|
|
88
97
|
// we need copy back our preserved data into our dereferenced schema.
|
|
89
98
|
if (derefOptions?.preservedProperties) {
|
|
@@ -98,7 +107,7 @@ function crawl(obj, path, pathFromRoot, parents, processedObjects, dereferencedC
|
|
|
98
107
|
}
|
|
99
108
|
else {
|
|
100
109
|
if (!parents.has(value)) {
|
|
101
|
-
dereferenced = crawl(value, keyPath, keyPathFromRoot, parents, processedObjects, dereferencedCache, $refs, options, startTime, depth + 1);
|
|
110
|
+
dereferenced = crawl(value, keyPath, currentScopeBase, dynamicIdScope, keyPathFromRoot, parents, processedObjects, dereferencedCache, $refs, options, startTime, depth + 1);
|
|
102
111
|
circular = dereferenced.circular;
|
|
103
112
|
// Avoid pointless mutations; breaks frozen objects to no profit
|
|
104
113
|
if (obj[key] !== dereferenced.value) {
|
|
@@ -131,10 +140,11 @@ function crawl(obj, path, pathFromRoot, parents, processedObjects, dereferencedC
|
|
|
131
140
|
* @param options
|
|
132
141
|
* @returns
|
|
133
142
|
*/
|
|
134
|
-
function dereference$Ref($ref, path, pathFromRoot, parents, processedObjects, dereferencedCache, $refs, options, startTime, depth) {
|
|
143
|
+
function dereference$Ref($ref, path, scopeBase, dynamicIdScope, pathFromRoot, parents, processedObjects, dereferencedCache, $refs, options, startTime, depth) {
|
|
135
144
|
const isExternalRef = $Ref.isExternal$Ref($ref);
|
|
136
145
|
const shouldResolveOnCwd = isExternalRef && options?.dereference?.externalReferenceResolution === "root";
|
|
137
|
-
const
|
|
146
|
+
const resolutionBase = shouldResolveOnCwd ? url.cwd() : dynamicIdScope ? scopeBase : path;
|
|
147
|
+
const $refPath = url.resolve(resolutionBase, $ref.$ref);
|
|
138
148
|
const cache = dereferencedCache.get($refPath);
|
|
139
149
|
if (cache) {
|
|
140
150
|
// If the object we found is circular we can immediately return it because it would have been
|
|
@@ -204,7 +214,7 @@ function dereference$Ref($ref, path, pathFromRoot, parents, processedObjects, de
|
|
|
204
214
|
// Crawl the dereferenced value (unless it's circular)
|
|
205
215
|
if (!circular) {
|
|
206
216
|
// Determine if the dereferenced value is circular
|
|
207
|
-
const dereferenced = crawl(dereferencedValue, pointer.path, pathFromRoot, parents, processedObjects, dereferencedCache, $refs, options, startTime, depth + 1);
|
|
217
|
+
const dereferenced = crawl(dereferencedValue, pointer.path, pointer.$ref.path, pointer.$ref.dynamicIdScope, pathFromRoot, parents, processedObjects, dereferencedCache, $refs, options, startTime, depth + 1);
|
|
208
218
|
circular = dereferenced.circular;
|
|
209
219
|
dereferencedValue = dereferenced.value;
|
|
210
220
|
}
|
package/dist/lib/index.js
CHANGED
|
@@ -9,6 +9,7 @@ import FileResolver from "./resolvers/file.js";
|
|
|
9
9
|
import HTTPResolver from "./resolvers/http.js";
|
|
10
10
|
import { JSONParserError, InvalidPointerError, MissingPointerError, ResolverError, ParserError, UnmatchedParserError, UnmatchedResolverError, isHandledError, JSONParserErrorGroup, } from "./util/errors.js";
|
|
11
11
|
import maybe from "./util/maybe.js";
|
|
12
|
+
import { registerSchemaResources, usesDynamicIdScope } from "./util/schema-resources.js";
|
|
12
13
|
import { getJsonSchemaRefParserDefaultOptions } from "./options.js";
|
|
13
14
|
import { isUnsafeUrl } from "./util/url.js";
|
|
14
15
|
/**
|
|
@@ -68,6 +69,8 @@ export class $RefParser {
|
|
|
68
69
|
const $ref = this.$refs._add(args.path);
|
|
69
70
|
$ref.value = args.schema;
|
|
70
71
|
$ref.pathType = pathType;
|
|
72
|
+
$ref.dynamicIdScope = usesDynamicIdScope($ref.value);
|
|
73
|
+
registerSchemaResources(this.$refs, $ref.path, $ref.value, $ref.pathType, $ref.dynamicIdScope);
|
|
71
74
|
promise = Promise.resolve(args.schema);
|
|
72
75
|
}
|
|
73
76
|
else {
|
package/dist/lib/options.d.ts
CHANGED
|
@@ -18,6 +18,14 @@ export interface BundleOptions {
|
|
|
18
18
|
* @argument {string} parentPropName - The prop name of the parent object whose value was processed
|
|
19
19
|
*/
|
|
20
20
|
onBundle?(path: string, value: JSONSchemaObject, parent?: JSONSchemaObject, parentPropName?: string): void;
|
|
21
|
+
/**
|
|
22
|
+
* Whether to optimize internal `$ref` paths by following intermediate `$ref` chains and
|
|
23
|
+
* rewriting them to point directly to the final target. When `false`, intermediate `$ref`
|
|
24
|
+
* indirections are preserved as-is.
|
|
25
|
+
*
|
|
26
|
+
* Default: `true`
|
|
27
|
+
*/
|
|
28
|
+
optimizeInternalRefs?: boolean;
|
|
21
29
|
}
|
|
22
30
|
export interface DereferenceOptions {
|
|
23
31
|
/**
|
|
@@ -78,6 +86,20 @@ export interface DereferenceOptions {
|
|
|
78
86
|
* Default: 500
|
|
79
87
|
*/
|
|
80
88
|
maxDepth?: number;
|
|
89
|
+
/**
|
|
90
|
+
* Whether to create independent clones of each `$ref` target value instead of
|
|
91
|
+
* reusing the same JS object reference. When `false` (the default), multiple
|
|
92
|
+
* `$ref` pointers that resolve to the same value will all share the same object
|
|
93
|
+
* in memory, so modifying one will affect all others. When `true`, each `$ref`
|
|
94
|
+
* replacement gets its own deep copy, preventing unintended side effects from
|
|
95
|
+
* post-dereference mutations.
|
|
96
|
+
*
|
|
97
|
+
* Note: circular references are never cloned — they always maintain reference
|
|
98
|
+
* equality to correctly represent the circular structure.
|
|
99
|
+
*
|
|
100
|
+
* Default: `false`
|
|
101
|
+
*/
|
|
102
|
+
cloneReferences?: boolean;
|
|
81
103
|
}
|
|
82
104
|
/**
|
|
83
105
|
* Options that determine how JSON schemas are parsed, resolved, and dereferenced.
|
package/dist/lib/parse.d.ts
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import type $Refs from "./refs.js";
|
|
2
2
|
import type { ParserOptions } from "./options.js";
|
|
3
3
|
import type { JSONSchema } from "./types/index.js";
|
|
4
|
+
interface ParseTarget {
|
|
5
|
+
url: string;
|
|
6
|
+
reference?: string;
|
|
7
|
+
baseUrl?: string;
|
|
8
|
+
}
|
|
4
9
|
/**
|
|
5
10
|
* Reads and parses the specified file path or URL.
|
|
6
11
|
*/
|
|
7
|
-
declare function parse<S extends object = JSONSchema, O extends ParserOptions<S> = ParserOptions<S>>(
|
|
12
|
+
declare function parse<S extends object = JSONSchema, O extends ParserOptions<S> = ParserOptions<S>>(target: string | ParseTarget, $refs: $Refs<S, O>, options: O): Promise<string | Buffer<ArrayBufferLike> | S | undefined>;
|
|
8
13
|
export default parse;
|
package/dist/lib/parse.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import * as url from "./util/url.js";
|
|
2
|
+
import { registerSchemaResources, usesDynamicIdScope } from "./util/schema-resources.js";
|
|
2
3
|
import { filter, all, sort, run } from "./util/plugins.js";
|
|
3
4
|
import { ResolverError, ParserError, UnmatchedParserError, UnmatchedResolverError, isHandledError, } from "./util/errors.js";
|
|
4
5
|
/**
|
|
5
6
|
* Reads and parses the specified file path or URL.
|
|
6
7
|
*/
|
|
7
|
-
async function parse(
|
|
8
|
+
async function parse(target, $refs, options) {
|
|
9
|
+
let path = typeof target === "string" ? target : target.url;
|
|
10
|
+
const baseUrl = typeof target === "string" ? undefined : target.baseUrl;
|
|
11
|
+
let reference = typeof target === "string" ? undefined : target.reference;
|
|
8
12
|
// Remove the URL fragment, if any
|
|
9
13
|
const hashIndex = path.indexOf("#");
|
|
10
14
|
let hash = "";
|
|
@@ -13,6 +17,12 @@ async function parse(path, $refs, options) {
|
|
|
13
17
|
// Remove the URL fragment, if any
|
|
14
18
|
path = path.substring(0, hashIndex);
|
|
15
19
|
}
|
|
20
|
+
if (reference) {
|
|
21
|
+
const referenceHashIndex = reference.indexOf("#");
|
|
22
|
+
if (referenceHashIndex >= 0) {
|
|
23
|
+
reference = reference.substring(0, referenceHashIndex);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
16
26
|
// Add a new $Ref for this file, even though we don't have the value yet.
|
|
17
27
|
// This ensures that we don't simultaneously read & parse the same file multiple times
|
|
18
28
|
const $ref = $refs._add(path);
|
|
@@ -21,6 +31,8 @@ async function parse(path, $refs, options) {
|
|
|
21
31
|
url: path,
|
|
22
32
|
hash,
|
|
23
33
|
extension: url.getExtension(path),
|
|
34
|
+
...(reference !== undefined ? { reference } : {}),
|
|
35
|
+
...(baseUrl !== undefined ? { baseUrl } : {}),
|
|
24
36
|
};
|
|
25
37
|
// Read the file and then parse the data
|
|
26
38
|
try {
|
|
@@ -29,6 +41,8 @@ async function parse(path, $refs, options) {
|
|
|
29
41
|
file.data = resolver.result;
|
|
30
42
|
const parser = await parseFile(file, options, $refs);
|
|
31
43
|
$ref.value = parser.result;
|
|
44
|
+
$ref.dynamicIdScope = usesDynamicIdScope($ref.value);
|
|
45
|
+
registerSchemaResources($refs, $ref.path, $ref.value, $ref.pathType, $ref.dynamicIdScope);
|
|
32
46
|
return parser.result;
|
|
33
47
|
}
|
|
34
48
|
catch (err) {
|
package/dist/lib/pointer.d.ts
CHANGED
|
@@ -25,6 +25,10 @@ declare class Pointer<S extends object = JSONSchema, O extends ParserOptions<S>
|
|
|
25
25
|
* The original path or URL, used for error messages.
|
|
26
26
|
*/
|
|
27
27
|
originalPath: string;
|
|
28
|
+
/**
|
|
29
|
+
* The current base URI used to resolve nested $ref pointers while walking this pointer.
|
|
30
|
+
*/
|
|
31
|
+
scopeBase: string;
|
|
28
32
|
/**
|
|
29
33
|
* The value of the JSON pointer.
|
|
30
34
|
* Can be any JSON type, not just objects. Unknown file types are represented as Buffers (byte arrays).
|
package/dist/lib/pointer.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import $Ref from "./ref.js";
|
|
2
2
|
import * as url from "./util/url.js";
|
|
3
3
|
import { JSONParserError, InvalidPointerError, MissingPointerError, isHandledError } from "./util/errors.js";
|
|
4
|
+
import { getSchemaBasePath } from "./util/schema-resources.js";
|
|
4
5
|
export const nullSymbol = Symbol("null");
|
|
5
6
|
const slashes = /\//g;
|
|
6
7
|
const tildes = /~/g;
|
|
@@ -28,6 +29,10 @@ class Pointer {
|
|
|
28
29
|
* The original path or URL, used for error messages.
|
|
29
30
|
*/
|
|
30
31
|
originalPath;
|
|
32
|
+
/**
|
|
33
|
+
* The current base URI used to resolve nested $ref pointers while walking this pointer.
|
|
34
|
+
*/
|
|
35
|
+
scopeBase;
|
|
31
36
|
/**
|
|
32
37
|
* The value of the JSON pointer.
|
|
33
38
|
* Can be any JSON type, not just objects. Unknown file types are represented as Buffers (byte arrays).
|
|
@@ -46,6 +51,7 @@ class Pointer {
|
|
|
46
51
|
this.$ref = $ref;
|
|
47
52
|
this.path = path;
|
|
48
53
|
this.originalPath = friendlyPath || path;
|
|
54
|
+
this.scopeBase = $ref.path || url.stripHash(path);
|
|
49
55
|
this.value = undefined;
|
|
50
56
|
this.circular = false;
|
|
51
57
|
this.indirections = 0;
|
|
@@ -68,6 +74,9 @@ class Pointer {
|
|
|
68
74
|
const found = [];
|
|
69
75
|
// Crawl the object, one token at a time
|
|
70
76
|
this.value = unwrapOrThrow(obj);
|
|
77
|
+
if (this.$ref.dynamicIdScope) {
|
|
78
|
+
this.scopeBase = getSchemaBasePath(this.scopeBase, this.value);
|
|
79
|
+
}
|
|
71
80
|
for (let i = 0; i < tokens.length; i++) {
|
|
72
81
|
// During token walking, if the current value is an extended $ref (has sibling keys
|
|
73
82
|
// alongside $ref, as allowed by JSON Schema 2019-09+), and resolveIf$Ref marks it
|
|
@@ -123,9 +132,13 @@ class Pointer {
|
|
|
123
132
|
this.value = this.value[token];
|
|
124
133
|
}
|
|
125
134
|
found.push(token);
|
|
135
|
+
if (this.$ref.dynamicIdScope) {
|
|
136
|
+
this.scopeBase = getSchemaBasePath(this.scopeBase, this.value);
|
|
137
|
+
}
|
|
126
138
|
}
|
|
127
139
|
// Resolve the final value
|
|
128
|
-
|
|
140
|
+
const finalResolutionBase = this.$ref.dynamicIdScope ? this.scopeBase : this.path;
|
|
141
|
+
if (!this.value || (this.value.$ref && url.resolve(finalResolutionBase, this.value.$ref) !== pathFromRoot)) {
|
|
129
142
|
resolveIf$Ref(this, options, pathFromRoot);
|
|
130
143
|
}
|
|
131
144
|
return this;
|
|
@@ -150,6 +163,9 @@ class Pointer {
|
|
|
150
163
|
}
|
|
151
164
|
// Crawl the object, one token at a time
|
|
152
165
|
this.value = unwrapOrThrow(obj);
|
|
166
|
+
if (this.$ref.dynamicIdScope) {
|
|
167
|
+
this.scopeBase = getSchemaBasePath(this.scopeBase, this.value);
|
|
168
|
+
}
|
|
153
169
|
for (let i = 0; i < tokens.length - 1; i++) {
|
|
154
170
|
resolveIf$Ref(this, options);
|
|
155
171
|
token = tokens[i];
|
|
@@ -161,6 +177,9 @@ class Pointer {
|
|
|
161
177
|
// The token doesn't exist, so create it
|
|
162
178
|
this.value = setValue(this, token, {});
|
|
163
179
|
}
|
|
180
|
+
if (this.$ref.dynamicIdScope) {
|
|
181
|
+
this.scopeBase = getSchemaBasePath(this.scopeBase, this.value);
|
|
182
|
+
}
|
|
164
183
|
}
|
|
165
184
|
// Set the value of the final token
|
|
166
185
|
resolveIf$Ref(this, options);
|
|
@@ -238,7 +257,8 @@ class Pointer {
|
|
|
238
257
|
function resolveIf$Ref(pointer, options, pathFromRoot) {
|
|
239
258
|
// Is the value a JSON reference? (and allowed?)
|
|
240
259
|
if ($Ref.isAllowed$Ref(pointer.value, options)) {
|
|
241
|
-
const
|
|
260
|
+
const resolutionBase = pointer.$ref.dynamicIdScope ? pointer.scopeBase : pointer.path;
|
|
261
|
+
const $refPath = url.resolve(resolutionBase, pointer.value.$ref);
|
|
242
262
|
if ($refPath === pointer.path && !isRootPath(pathFromRoot)) {
|
|
243
263
|
// The value is a reference to itself, so there's nothing to do.
|
|
244
264
|
pointer.circular = true;
|
|
@@ -253,6 +273,9 @@ function resolveIf$Ref(pointer, options, pathFromRoot) {
|
|
|
253
273
|
// This JSON reference "extends" the resolved value, rather than simply pointing to it.
|
|
254
274
|
// So the resolved path does NOT change. Just the value does.
|
|
255
275
|
pointer.value = $Ref.dereference(pointer.value, resolved.value, options);
|
|
276
|
+
if (pointer.$ref.dynamicIdScope) {
|
|
277
|
+
pointer.scopeBase = getSchemaBasePath(pointer.scopeBase, pointer.value);
|
|
278
|
+
}
|
|
256
279
|
return false;
|
|
257
280
|
}
|
|
258
281
|
else {
|
|
@@ -260,6 +283,9 @@ function resolveIf$Ref(pointer, options, pathFromRoot) {
|
|
|
260
283
|
pointer.$ref = resolved.$ref;
|
|
261
284
|
pointer.path = resolved.path;
|
|
262
285
|
pointer.value = resolved.value;
|
|
286
|
+
pointer.scopeBase = pointer.$ref.dynamicIdScope
|
|
287
|
+
? getSchemaBasePath(pointer.$ref.path, pointer.value)
|
|
288
|
+
: pointer.$ref.path;
|
|
263
289
|
}
|
|
264
290
|
return true;
|
|
265
291
|
}
|
package/dist/lib/ref.d.ts
CHANGED
|
@@ -39,6 +39,10 @@ declare class $Ref<S extends object = JSONSchema, O extends ParserOptions<S> = P
|
|
|
39
39
|
* Indicates the type of {@link $Ref#path} (e.g. "file", "http", etc.)
|
|
40
40
|
*/
|
|
41
41
|
pathType: string | unknown;
|
|
42
|
+
/**
|
|
43
|
+
* Whether this document/resource should use JSON Schema 2019-09+ nested $id scope semantics.
|
|
44
|
+
*/
|
|
45
|
+
dynamicIdScope: boolean;
|
|
42
46
|
/**
|
|
43
47
|
* List of all errors. Undefined if no errors.
|
|
44
48
|
*/
|
package/dist/lib/ref.js
CHANGED
|
@@ -35,6 +35,10 @@ class $Ref {
|
|
|
35
35
|
* Indicates the type of {@link $Ref#path} (e.g. "file", "http", etc.)
|
|
36
36
|
*/
|
|
37
37
|
pathType;
|
|
38
|
+
/**
|
|
39
|
+
* Whether this document/resource should use JSON Schema 2019-09+ nested $id scope semantics.
|
|
40
|
+
*/
|
|
41
|
+
dynamicIdScope = false;
|
|
38
42
|
/**
|
|
39
43
|
* List of all errors. Undefined if no errors.
|
|
40
44
|
*/
|
package/dist/lib/refs.d.ts
CHANGED
|
@@ -79,6 +79,7 @@ export default class $Refs<S extends object = JSONSchema, O extends ParserOption
|
|
|
79
79
|
* @param path - The file path or URL of the referenced file
|
|
80
80
|
*/
|
|
81
81
|
_add(path: string): $Ref<S, O>;
|
|
82
|
+
_addAlias(path: string, value: S, pathType?: string | unknown, dynamicIdScope?: boolean): $Ref<S, O>;
|
|
82
83
|
/**
|
|
83
84
|
* Resolves the given JSON reference.
|
|
84
85
|
*
|
|
@@ -96,6 +97,7 @@ export default class $Refs<S extends object = JSONSchema, O extends ParserOption
|
|
|
96
97
|
* @protected
|
|
97
98
|
*/
|
|
98
99
|
_$refs: $RefsMap<S, O>;
|
|
100
|
+
_aliases: $RefsMap<S, O>;
|
|
99
101
|
/**
|
|
100
102
|
* The {@link $Ref} object that is the root of the JSON schema.
|
|
101
103
|
*
|
|
@@ -123,5 +125,6 @@ export default class $Refs<S extends object = JSONSchema, O extends ParserOption
|
|
|
123
125
|
* @returns {object}
|
|
124
126
|
*/
|
|
125
127
|
toJSON: (...types: (string | string[])[]) => S;
|
|
128
|
+
private _getRef;
|
|
126
129
|
}
|
|
127
130
|
export {};
|
package/dist/lib/refs.js
CHANGED
|
@@ -84,10 +84,9 @@ export default class $Refs {
|
|
|
84
84
|
*/
|
|
85
85
|
set(path, value) {
|
|
86
86
|
const absPath = url.resolve(this._root$Ref.path, path);
|
|
87
|
-
const
|
|
88
|
-
const $ref = this._$refs[withoutHash];
|
|
87
|
+
const $ref = this._getRef(absPath);
|
|
89
88
|
if (!$ref) {
|
|
90
|
-
throw new Error(`Error resolving $ref pointer "${path}". \n"${
|
|
89
|
+
throw new Error(`Error resolving $ref pointer "${path}". \n"${url.stripHash(absPath)}" not found.`);
|
|
91
90
|
}
|
|
92
91
|
$ref.set(absPath, value);
|
|
93
92
|
}
|
|
@@ -100,8 +99,7 @@ export default class $Refs {
|
|
|
100
99
|
*/
|
|
101
100
|
_get$Ref(path) {
|
|
102
101
|
path = url.resolve(this._root$Ref.path, path);
|
|
103
|
-
|
|
104
|
-
return this._$refs[withoutHash];
|
|
102
|
+
return this._getRef(path);
|
|
105
103
|
}
|
|
106
104
|
/**
|
|
107
105
|
* Creates a new {@link $Ref} object and adds it to this {@link $Refs} object.
|
|
@@ -116,6 +114,19 @@ export default class $Refs {
|
|
|
116
114
|
this._root$Ref = this._root$Ref || $ref;
|
|
117
115
|
return $ref;
|
|
118
116
|
}
|
|
117
|
+
_addAlias(path, value, pathType, dynamicIdScope = false) {
|
|
118
|
+
const withoutHash = url.stripHash(path);
|
|
119
|
+
if (!withoutHash || this._$refs[withoutHash] || this._aliases[withoutHash]) {
|
|
120
|
+
return this._$refs[withoutHash] || this._aliases[withoutHash];
|
|
121
|
+
}
|
|
122
|
+
const $ref = new $Ref(this);
|
|
123
|
+
$ref.path = withoutHash;
|
|
124
|
+
$ref.pathType = pathType;
|
|
125
|
+
$ref.value = value;
|
|
126
|
+
$ref.dynamicIdScope = dynamicIdScope;
|
|
127
|
+
this._aliases[withoutHash] = $ref;
|
|
128
|
+
return $ref;
|
|
129
|
+
}
|
|
119
130
|
/**
|
|
120
131
|
* Resolves the given JSON reference.
|
|
121
132
|
*
|
|
@@ -127,10 +138,9 @@ export default class $Refs {
|
|
|
127
138
|
*/
|
|
128
139
|
_resolve(path, pathFromRoot, options) {
|
|
129
140
|
const absPath = url.resolve(this._root$Ref.path, path);
|
|
130
|
-
const
|
|
131
|
-
const $ref = this._$refs[withoutHash];
|
|
141
|
+
const $ref = this._getRef(absPath);
|
|
132
142
|
if (!$ref) {
|
|
133
|
-
throw new Error(`Error resolving $ref pointer "${path}". \n"${
|
|
143
|
+
throw new Error(`Error resolving $ref pointer "${path}". \n"${url.stripHash(absPath)}" not found.`);
|
|
134
144
|
}
|
|
135
145
|
return $ref.resolve(absPath, options, path, pathFromRoot);
|
|
136
146
|
}
|
|
@@ -141,6 +151,7 @@ export default class $Refs {
|
|
|
141
151
|
* @protected
|
|
142
152
|
*/
|
|
143
153
|
_$refs = {};
|
|
154
|
+
_aliases = {};
|
|
144
155
|
/**
|
|
145
156
|
* The {@link $Ref} object that is the root of the JSON schema.
|
|
146
157
|
*
|
|
@@ -156,6 +167,7 @@ export default class $Refs {
|
|
|
156
167
|
*/
|
|
157
168
|
this.circular = false;
|
|
158
169
|
this._$refs = {};
|
|
170
|
+
this._aliases = {};
|
|
159
171
|
// @ts-ignore
|
|
160
172
|
this._root$Ref = null;
|
|
161
173
|
}
|
|
@@ -178,6 +190,10 @@ export default class $Refs {
|
|
|
178
190
|
* @returns {object}
|
|
179
191
|
*/
|
|
180
192
|
toJSON = this.values;
|
|
193
|
+
_getRef(path) {
|
|
194
|
+
const withoutHash = url.stripHash(path);
|
|
195
|
+
return this._$refs[withoutHash] || this._aliases[withoutHash];
|
|
196
|
+
}
|
|
181
197
|
}
|
|
182
198
|
/**
|
|
183
199
|
* Returns the encoded and decoded paths keys of the given object.
|