@apidevtools/json-schema-ref-parser 12.0.0 → 12.0.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/README.md CHANGED
@@ -57,12 +57,12 @@ JavaScript objects.
57
57
 
58
58
  - Use **JSON** or **YAML** schemas — or even a mix of both!
59
59
  - Supports `$ref` pointers to external files and URLs, as well
60
- as [custom sources](https://apitools.dev/json-schema-ref-parser/docs/plugins/resolvers.html) such as databases
61
- - Can [bundle](https://apitools.dev/json-schema-ref-parser/docs/ref-parser.html#bundlepath-options-callback) multiple
60
+ as [custom sources](https://apidevtools.com/json-schema-ref-parser/docs/plugins/resolvers.html) such as databases
61
+ - Can [bundle](https://apidevtools.com/json-schema-ref-parser/docs/ref-parser.html#bundlepath-options-callback) multiple
62
62
  files into a single schema that only has _internal_ `$ref` pointers
63
- - Can [dereference](https://apitools.dev/json-schema-ref-parser/docs/ref-parser.html#dereferencepath-options-callback)
63
+ - Can [dereference](https://apidevtools.com/json-schema-ref-parser/docs/ref-parser.html#dereferencepath-options-callback)
64
64
  your schema, producing a plain-old JavaScript object that's easy to work with
65
- - Supports [circular references](https://apitools.dev/json-schema-ref-parser/docs/#circular-refs), nested references,
65
+ - Supports [circular references](https://apidevtools.com/json-schema-ref-parser/docs/#circular-refs), nested references,
66
66
  back-references, and cross-references between files
67
67
  - Maintains object reference equality — `$ref` pointers to the same value always resolve to the same object
68
68
  instance
@@ -86,7 +86,7 @@ try {
86
86
  }
87
87
  ```
88
88
 
89
- For more detailed examples, please see the [API Documentation](https://apitools.dev/json-schema-ref-parser/docs/)
89
+ For more detailed examples, please see the [API Documentation](https://apidevtools.com/json-schema-ref-parser/docs/)
90
90
 
91
91
  ## Polyfills
92
92
 
@@ -130,7 +130,7 @@ config.plugins.push(
130
130
 
131
131
  ## API Documentation
132
132
 
133
- Full API documentation is available [right here](https://apitools.dev/json-schema-ref-parser/docs/)
133
+ Full API documentation is available [right here](https://apidevtools.com/json-schema-ref-parser/docs/)
134
134
 
135
135
  ## Contributing
136
136
 
@@ -80,10 +80,10 @@ function crawl(parent, key, path, pathFromRoot, indirections, inventory, $refs,
80
80
  const keys = Object.keys(obj).sort((a, b) => {
81
81
  // Most people will expect references to be bundled into the the "definitions" property,
82
82
  // so we always crawl that property first, if it exists.
83
- if (a === "definitions") {
83
+ if (a === "definitions" || a === "$defs") {
84
84
  return -1;
85
85
  }
86
- else if (b === "definitions") {
86
+ else if (b === "definitions" || b === "$defs") {
87
87
  return 1;
88
88
  }
89
89
  else {
@@ -214,8 +214,8 @@ function remap(inventory) {
214
214
  else {
215
215
  // Determine how far each $ref is from the "definitions" property.
216
216
  // Most people will expect references to be bundled into the the "definitions" property if possible.
217
- const aDefinitionsIndex = a.pathFromRoot.lastIndexOf("/definitions");
218
- const bDefinitionsIndex = b.pathFromRoot.lastIndexOf("/definitions");
217
+ const aDefinitionsIndex = Math.max(a.pathFromRoot.lastIndexOf("/definitions"), a.pathFromRoot.lastIndexOf("/$defs"));
218
+ const bDefinitionsIndex = Math.max(b.pathFromRoot.lastIndexOf("/definitions"), b.pathFromRoot.lastIndexOf("/$defs"));
219
219
  if (aDefinitionsIndex !== bDefinitionsIndex) {
220
220
  // Give higher priority to the $ref that's closer to the "definitions" property
221
221
  return bDefinitionsIndex - aDefinitionsIndex;
@@ -174,24 +174,46 @@ function dereference$Ref($ref, path, pathFromRoot, parents, processedObjects, de
174
174
  //
175
175
  // If the cached object however is _not_ circular and there are additional keys alongside our
176
176
  // `$ref` pointer here we should merge them back in and return that.
177
- if (cache.circular) {
177
+ if (!cache.circular) {
178
+ const refKeys = Object.keys($ref);
179
+ if (refKeys.length > 1) {
180
+ const extraKeys = {};
181
+ for (const key of refKeys) {
182
+ if (key !== "$ref" && !(key in cache.value)) {
183
+ // @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
184
+ extraKeys[key] = $ref[key];
185
+ }
186
+ }
187
+ return {
188
+ circular: cache.circular,
189
+ value: Object.assign({}, cache.value, extraKeys),
190
+ };
191
+ }
178
192
  return cache;
179
193
  }
180
- const refKeys = Object.keys($ref);
181
- if (refKeys.length > 1) {
182
- const extraKeys = {};
183
- for (const key of refKeys) {
184
- if (key !== "$ref" && !(key in cache.value)) {
185
- // @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
186
- extraKeys[key] = $ref[key];
187
- }
194
+ // If both our cached value and our incoming `$ref` are the same then we can return what we
195
+ // got out of the cache, otherwise we should re-process this value. We need to do this because
196
+ // the current dereference caching mechanism doesn't take into account that `$ref` are neither
197
+ // unique or reference the same file.
198
+ //
199
+ // For example if `schema.yaml` references `definitions/child.yaml` and
200
+ // `definitions/parent.yaml` references `child.yaml` then `$ref: 'child.yaml'` may get cached
201
+ // for `definitions/child.yaml`, resulting in `schema.yaml` being having an invalid reference
202
+ // to `child.yaml`.
203
+ //
204
+ // This check is not perfect and the design of the dereference caching mechanism needs a total
205
+ // overhaul.
206
+ if (typeof cache.value === "object" && "$ref" in cache.value && "$ref" in $ref) {
207
+ if (cache.value.$ref === $ref.$ref) {
208
+ return cache;
209
+ }
210
+ else {
211
+ // no-op
188
212
  }
189
- return {
190
- circular: cache.circular,
191
- value: Object.assign({}, cache.value, extraKeys),
192
- };
193
213
  }
194
- return cache;
214
+ else {
215
+ return cache;
216
+ }
195
217
  }
196
218
  const pointer = $refs._resolve($refPath, path, options);
197
219
  if (pointer === null) {
@@ -40,7 +40,7 @@ exports.nullSymbol = void 0;
40
40
  const ref_js_1 = __importDefault(require("./ref.js"));
41
41
  const url = __importStar(require("./util/url.js"));
42
42
  const errors_js_1 = require("./util/errors.js");
43
- exports.nullSymbol = Symbol('null');
43
+ exports.nullSymbol = Symbol("null");
44
44
  const slashes = /\//g;
45
45
  const tildes = /~/g;
46
46
  const escapedSlash = /~1/g;
@@ -93,9 +93,6 @@ class Pointer {
93
93
  // The $ref path has changed, so append the remaining tokens to the path
94
94
  this.path = Pointer.join(this.path, tokens.slice(i));
95
95
  }
96
- if (typeof this.value === "object" && this.value !== null && !isRootPath(pathFromRoot) && "$ref" in this.value) {
97
- return this;
98
- }
99
96
  const token = tokens[i];
100
97
  if (this.value[token] === undefined || (this.value[token] === null && i === tokens.length - 1)) {
101
98
  // one final case is if the entry itself includes slashes, and was parsed out as a token - we can join the remaining tokens and try again
@@ -14,5 +14,6 @@ exports.default = (0, config_1.defineConfig)({
14
14
  passWithNoTests: true,
15
15
  reporters: ["verbose"],
16
16
  coverage: { reporter: ["lcov", "html", "text"] },
17
+ snapshotSerializers: ["./test/utils/serializeJson.ts"],
17
18
  },
18
19
  });
package/lib/bundle.ts CHANGED
@@ -76,9 +76,9 @@ function crawl<S extends object = JSONSchema, O extends ParserOptions<S> = Parse
76
76
  const keys = Object.keys(obj).sort((a, b) => {
77
77
  // Most people will expect references to be bundled into the the "definitions" property,
78
78
  // so we always crawl that property first, if it exists.
79
- if (a === "definitions") {
79
+ if (a === "definitions" || a === "$defs") {
80
80
  return -1;
81
- } else if (b === "definitions") {
81
+ } else if (b === "definitions" || b === "$defs") {
82
82
  return 1;
83
83
  } else {
84
84
  // Otherwise, crawl the keys based on their length.
@@ -216,8 +216,14 @@ function remap(inventory: InventoryEntry[]) {
216
216
  } else {
217
217
  // Determine how far each $ref is from the "definitions" property.
218
218
  // Most people will expect references to be bundled into the the "definitions" property if possible.
219
- const aDefinitionsIndex = a.pathFromRoot.lastIndexOf("/definitions");
220
- const bDefinitionsIndex = b.pathFromRoot.lastIndexOf("/definitions");
219
+ const aDefinitionsIndex = Math.max(
220
+ a.pathFromRoot.lastIndexOf("/definitions"),
221
+ a.pathFromRoot.lastIndexOf("/$defs"),
222
+ );
223
+ const bDefinitionsIndex = Math.max(
224
+ b.pathFromRoot.lastIndexOf("/definitions"),
225
+ b.pathFromRoot.lastIndexOf("/$defs"),
226
+ );
221
227
 
222
228
  if (aDefinitionsIndex !== bDefinitionsIndex) {
223
229
  // Give higher priority to the $ref that's closer to the "definitions" property
@@ -220,26 +220,46 @@ function dereference$Ref<S extends object = JSONSchema, O extends ParserOptions<
220
220
  //
221
221
  // If the cached object however is _not_ circular and there are additional keys alongside our
222
222
  // `$ref` pointer here we should merge them back in and return that.
223
- if (cache.circular) {
223
+ if (!cache.circular) {
224
+ const refKeys = Object.keys($ref);
225
+ if (refKeys.length > 1) {
226
+ const extraKeys = {};
227
+ for (const key of refKeys) {
228
+ if (key !== "$ref" && !(key in cache.value)) {
229
+ // @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
230
+ extraKeys[key] = $ref[key];
231
+ }
232
+ }
233
+ return {
234
+ circular: cache.circular,
235
+ value: Object.assign({}, cache.value, extraKeys),
236
+ };
237
+ }
238
+
224
239
  return cache;
225
240
  }
226
241
 
227
- const refKeys = Object.keys($ref);
228
- if (refKeys.length > 1) {
229
- const extraKeys = {};
230
- for (const key of refKeys) {
231
- if (key !== "$ref" && !(key in cache.value)) {
232
- // @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
233
- extraKeys[key] = $ref[key];
234
- }
242
+ // If both our cached value and our incoming `$ref` are the same then we can return what we
243
+ // got out of the cache, otherwise we should re-process this value. We need to do this because
244
+ // the current dereference caching mechanism doesn't take into account that `$ref` are neither
245
+ // unique or reference the same file.
246
+ //
247
+ // For example if `schema.yaml` references `definitions/child.yaml` and
248
+ // `definitions/parent.yaml` references `child.yaml` then `$ref: 'child.yaml'` may get cached
249
+ // for `definitions/child.yaml`, resulting in `schema.yaml` being having an invalid reference
250
+ // to `child.yaml`.
251
+ //
252
+ // This check is not perfect and the design of the dereference caching mechanism needs a total
253
+ // overhaul.
254
+ if (typeof cache.value === "object" && "$ref" in cache.value && "$ref" in $ref) {
255
+ if (cache.value.$ref === $ref.$ref) {
256
+ return cache;
257
+ } else {
258
+ // no-op
235
259
  }
236
- return {
237
- circular: cache.circular,
238
- value: Object.assign({}, cache.value, extraKeys),
239
- };
260
+ } else {
261
+ return cache;
240
262
  }
241
-
242
- return cache;
243
263
  }
244
264
 
245
265
  const pointer = $refs._resolve($refPath, path, options);
package/lib/index.ts CHANGED
@@ -192,12 +192,7 @@ export class $RefParser<S extends object = JSONSchema, O extends ParserOptions<S
192
192
  public resolve(schema: S | string | unknown, options: O): Promise<$Refs<S, O>>;
193
193
  public resolve(schema: S | string | unknown, options: O, callback: $RefsCallback<S, O>): Promise<void>;
194
194
  public resolve(path: string, schema: S | string | unknown, options: O): Promise<$Refs<S, O>>;
195
- public resolve(
196
- path: string,
197
- schema: S | string | unknown,
198
- options: O,
199
- callback: $RefsCallback<S, O>,
200
- ): Promise<void>;
195
+ public resolve(path: string, schema: S | string | unknown, options: O, callback: $RefsCallback<S, O>): Promise<void>;
201
196
  async resolve() {
202
197
  const args = normalizeArgs<S, O>(arguments);
203
198
 
package/lib/pointer.ts CHANGED
@@ -5,7 +5,7 @@ import * as url from "./util/url.js";
5
5
  import { JSONParserError, InvalidPointerError, MissingPointerError, isHandledError } from "./util/errors.js";
6
6
  import type { JSONSchema } from "./types";
7
7
 
8
- export const nullSymbol = Symbol('null');
8
+ export const nullSymbol = Symbol("null");
9
9
 
10
10
  const slashes = /\//g;
11
11
  const tildes = /~/g;
@@ -101,10 +101,6 @@ class Pointer<S extends object = JSONSchema, O extends ParserOptions<S> = Parser
101
101
  this.path = Pointer.join(this.path, tokens.slice(i));
102
102
  }
103
103
 
104
- if (typeof this.value === "object" && this.value !== null && !isRootPath(pathFromRoot) && "$ref" in this.value) {
105
- return this;
106
- }
107
-
108
104
  const token = tokens[i];
109
105
 
110
106
  if (this.value[token] === undefined || (this.value[token] === null && i === tokens.length - 1)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apidevtools/json-schema-ref-parser",
3
- "version": "12.0.0",
3
+ "version": "12.0.2",
4
4
  "description": "Parse, Resolve, and Dereference JSON Schema $ref pointers",
5
5
  "scripts": {
6
6
  "prepublishOnly": "yarn build",