@hey-api/json-schema-ref-parser 1.0.4 → 1.0.5

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.
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const node_path_1 = __importDefault(require("node:path"));
7
+ const vitest_1 = require("vitest");
8
+ const __1 = require("..");
9
+ (0, vitest_1.describe)('bundle', () => {
10
+ (0, vitest_1.it)('handles circular reference with description', async () => {
11
+ const refParser = new __1.$RefParser();
12
+ const pathOrUrlOrSchema = node_path_1.default.resolve('lib', '__tests__', 'spec', 'circular-ref-with-description.json');
13
+ const schema = await refParser.bundle({ pathOrUrlOrSchema });
14
+ (0, vitest_1.expect)(schema).not.toBeUndefined();
15
+ });
16
+ });
@@ -2,17 +2,17 @@ import type { ParserOptions } from "./options.js";
2
2
  import type { $RefParser } from "./index";
3
3
  export interface InventoryEntry {
4
4
  $ref: any;
5
- parent: any;
6
- key: any;
7
- pathFromRoot: any;
8
- depth: any;
9
- file: any;
10
- hash: any;
11
- value: any;
12
5
  circular: any;
6
+ depth: any;
13
7
  extended: any;
14
8
  external: any;
9
+ file: any;
10
+ hash: any;
15
11
  indirections: any;
12
+ key: any;
13
+ parent: any;
14
+ pathFromRoot: any;
15
+ value: any;
16
16
  }
17
17
  /**
18
18
  * Bundles all external JSON references into the main JSON schema, thus resulting in a schema that
@@ -22,5 +22,4 @@ export interface InventoryEntry {
22
22
  * @param parser
23
23
  * @param options
24
24
  */
25
- declare function bundle(parser: $RefParser, options: ParserOptions): void;
26
- export default bundle;
25
+ export declare const bundle: (parser: $RefParser, options: ParserOptions) => void;
@@ -36,90 +36,31 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
36
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.bundle = void 0;
40
+ const isEqual_1 = __importDefault(require("lodash/isEqual"));
39
41
  const ref_js_1 = __importDefault(require("./ref.js"));
40
42
  const pointer_js_1 = __importDefault(require("./pointer.js"));
41
43
  const url = __importStar(require("./util/url.js"));
42
44
  /**
43
- * Bundles all external JSON references into the main JSON schema, thus resulting in a schema that
44
- * only has *internal* references, not any *external* references.
45
- * This method mutates the JSON schema object, adding new references and re-mapping existing ones.
46
- *
47
- * @param parser
48
- * @param options
49
- */
50
- function bundle(parser, options) {
51
- // console.log('Bundling $ref pointers in %s', parser.$refs._root$Ref.path);
52
- // Build an inventory of all $ref pointers in the JSON Schema
53
- const inventory = [];
54
- crawl(parser, "schema", parser.$refs._root$Ref.path + "#", "#", 0, inventory, parser.$refs, options);
55
- // Remap all $ref pointers
56
- remap(inventory);
57
- }
58
- /**
59
- * Recursively crawls the given value, and inventories all JSON references.
60
- *
61
- * @param parent - The object containing the value to crawl. If the value is not an object or array, it will be ignored.
62
- * @param key - The property key of `parent` to be crawled
63
- * @param path - The full path of the property being crawled, possibly with a JSON Pointer in the hash
64
- * @param pathFromRoot - The path of the property being crawled, from the schema root
65
- * @param indirections
66
- * @param inventory - An array of already-inventoried $ref pointers
67
- * @param $refs
68
- * @param options
45
+ * TODO
69
46
  */
70
- function crawl(parent, key, path, pathFromRoot, indirections, inventory, $refs, options) {
71
- const obj = key === null ? parent : parent[key];
72
- if (obj && typeof obj === "object" && !ArrayBuffer.isView(obj)) {
73
- if (ref_js_1.default.isAllowed$Ref(obj)) {
74
- inventory$Ref(parent, key, path, pathFromRoot, indirections, inventory, $refs, options);
75
- }
76
- else {
77
- // Crawl the object in a specific order that's optimized for bundling.
78
- // This is important because it determines how `pathFromRoot` gets built,
79
- // which later determines which keys get dereferenced and which ones get remapped
80
- const keys = Object.keys(obj).sort((a, b) => {
81
- // Most people will expect references to be bundled into the the "definitions" property,
82
- // so we always crawl that property first, if it exists.
83
- if (a === "definitions") {
84
- return -1;
85
- }
86
- else if (b === "definitions") {
87
- return 1;
88
- }
89
- else {
90
- // Otherwise, crawl the keys based on their length.
91
- // This produces the shortest possible bundled references
92
- return a.length - b.length;
93
- }
94
- });
95
- for (const key of keys) {
96
- const keyPath = pointer_js_1.default.join(path, key);
97
- const keyPathFromRoot = pointer_js_1.default.join(pathFromRoot, key);
98
- const value = obj[key];
99
- if (ref_js_1.default.isAllowed$Ref(value)) {
100
- inventory$Ref(obj, key, path, keyPathFromRoot, indirections, inventory, $refs, options);
101
- }
102
- else {
103
- crawl(obj, key, keyPath, keyPathFromRoot, indirections, inventory, $refs, options);
47
+ const findInInventory = (inventory, $refParent, $refKey) => {
48
+ for (const entry of inventory) {
49
+ if (entry) {
50
+ if ((0, isEqual_1.default)(entry.parent, $refParent)) {
51
+ if (entry.key === $refKey) {
52
+ return entry;
104
53
  }
105
54
  }
106
55
  }
107
56
  }
108
- }
57
+ return undefined;
58
+ };
109
59
  /**
110
60
  * Inventories the given JSON Reference (i.e. records detailed information about it so we can
111
61
  * optimize all $refs in the schema), and then crawls the resolved value.
112
- *
113
- * @param $refParent - The object that contains a JSON Reference as one of its keys
114
- * @param $refKey - The key in `$refParent` that is a JSON Reference
115
- * @param path - The full path of the JSON Reference at `$refKey`, possibly with a JSON Pointer in the hash
116
- * @param indirections - unknown
117
- * @param pathFromRoot - The path of the JSON Reference at `$refKey`, from the schema root
118
- * @param inventory - An array of already-inventoried $ref pointers
119
- * @param $refs
120
- * @param options
121
62
  */
122
- function inventory$Ref($refParent, $refKey, path, pathFromRoot, indirections, inventory, $refs, options) {
63
+ const inventory$Ref = ({ $refKey, $refParent, $refs, indirections, inventory, options, path, pathFromRoot, }) => {
123
64
  const $ref = $refKey === null ? $refParent : $refParent[$refKey];
124
65
  const $refPath = url.resolve(path, $ref.$ref);
125
66
  const pointer = $refs._resolve($refPath, pathFromRoot, options);
@@ -145,23 +86,101 @@ function inventory$Ref($refParent, $refKey, path, pathFromRoot, indirections, in
145
86
  }
146
87
  inventory.push({
147
88
  $ref, // The JSON Reference (e.g. {$ref: string})
148
- parent: $refParent, // The object that contains this $ref pointer
149
- key: $refKey, // The key in `parent` that is the $ref pointer
150
- pathFromRoot, // The path to the $ref pointer, from the JSON Schema root
151
- depth, // How far from the JSON Schema root is this $ref pointer?
152
- file, // The file that the $ref pointer resolves to
153
- hash, // The hash within `file` that the $ref pointer resolves to
154
- value: pointer.value, // The resolved value of the $ref pointer
155
89
  circular: pointer.circular, // Is this $ref pointer DIRECTLY circular? (i.e. it references itself)
90
+ depth, // How far from the JSON Schema root is this $ref pointer?
156
91
  extended, // Does this $ref extend its resolved value? (i.e. it has extra properties, in addition to "$ref")
157
92
  external, // Does this $ref pointer point to a file other than the main JSON Schema file?
93
+ file, // The file that the $ref pointer resolves to
94
+ hash, // The hash within `file` that the $ref pointer resolves to
158
95
  indirections, // The number of indirect references that were traversed to resolve the value
96
+ key: $refKey, // The key in `parent` that is the $ref pointer
97
+ parent: $refParent, // The object that contains this $ref pointer
98
+ pathFromRoot, // The path to the $ref pointer, from the JSON Schema root
99
+ value: pointer.value, // The resolved value of the $ref pointer
159
100
  });
160
101
  // Recursively crawl the resolved value
161
102
  if (!existingEntry || external) {
162
- crawl(pointer.value, null, pointer.path, pathFromRoot, indirections + 1, inventory, $refs, options);
103
+ crawl({
104
+ parent: pointer.value,
105
+ key: null,
106
+ path: pointer.path,
107
+ pathFromRoot,
108
+ indirections: indirections + 1,
109
+ inventory,
110
+ $refs,
111
+ options,
112
+ });
163
113
  }
164
- }
114
+ };
115
+ /**
116
+ * Recursively crawls the given value, and inventories all JSON references.
117
+ */
118
+ const crawl = ({ $refs, indirections, inventory, key, options, parent, path, pathFromRoot, }) => {
119
+ const obj = key === null ? parent : parent[key];
120
+ if (obj && typeof obj === "object" && !ArrayBuffer.isView(obj)) {
121
+ if (ref_js_1.default.isAllowed$Ref(obj)) {
122
+ inventory$Ref({
123
+ $refParent: parent,
124
+ $refKey: key,
125
+ path,
126
+ pathFromRoot,
127
+ indirections,
128
+ inventory,
129
+ $refs,
130
+ options,
131
+ });
132
+ }
133
+ else {
134
+ // Crawl the object in a specific order that's optimized for bundling.
135
+ // This is important because it determines how `pathFromRoot` gets built,
136
+ // which later determines which keys get dereferenced and which ones get remapped
137
+ const keys = Object.keys(obj).sort((a, b) => {
138
+ // Most people will expect references to be bundled into the "definitions" property,
139
+ // so we always crawl that property first, if it exists.
140
+ if (a === "definitions") {
141
+ return -1;
142
+ }
143
+ else if (b === "definitions") {
144
+ return 1;
145
+ }
146
+ else {
147
+ // Otherwise, crawl the keys based on their length.
148
+ // This produces the shortest possible bundled references
149
+ return a.length - b.length;
150
+ }
151
+ });
152
+ for (const key of keys) {
153
+ const keyPath = pointer_js_1.default.join(path, key);
154
+ const keyPathFromRoot = pointer_js_1.default.join(pathFromRoot, key);
155
+ const value = obj[key];
156
+ if (ref_js_1.default.isAllowed$Ref(value)) {
157
+ inventory$Ref({
158
+ $refParent: obj,
159
+ $refKey: key,
160
+ path,
161
+ pathFromRoot: keyPathFromRoot,
162
+ indirections,
163
+ inventory,
164
+ $refs,
165
+ options,
166
+ });
167
+ }
168
+ else {
169
+ crawl({
170
+ parent: obj,
171
+ key,
172
+ path: keyPath,
173
+ pathFromRoot: keyPathFromRoot,
174
+ indirections,
175
+ inventory,
176
+ $refs,
177
+ options,
178
+ });
179
+ }
180
+ }
181
+ }
182
+ }
183
+ };
165
184
  /**
166
185
  * Re-maps every $ref pointer, so that they're all relative to the root of the JSON Schema.
167
186
  * Each referenced value is dereferenced EXACTLY ONCE. All subsequent references to the same
@@ -275,19 +294,33 @@ function remap(inventory) {
275
294
  // }
276
295
  // }
277
296
  }
278
- /**
279
- * TODO
280
- */
281
- function findInInventory(inventory, $refParent, $refKey) {
282
- for (const existingEntry of inventory) {
283
- if (existingEntry && existingEntry.parent === $refParent && existingEntry.key === $refKey) {
284
- return existingEntry;
285
- }
286
- }
287
- return undefined;
288
- }
289
297
  function removeFromInventory(inventory, entry) {
290
298
  const index = inventory.indexOf(entry);
291
299
  inventory.splice(index, 1);
292
300
  }
293
- exports.default = bundle;
301
+ /**
302
+ * Bundles all external JSON references into the main JSON schema, thus resulting in a schema that
303
+ * only has *internal* references, not any *external* references.
304
+ * This method mutates the JSON schema object, adding new references and re-mapping existing ones.
305
+ *
306
+ * @param parser
307
+ * @param options
308
+ */
309
+ const bundle = (parser, options) => {
310
+ // console.log('Bundling $ref pointers in %s', parser.$refs._root$Ref.path);
311
+ // Build an inventory of all $ref pointers in the JSON Schema
312
+ const inventory = [];
313
+ crawl({
314
+ parent: parser,
315
+ key: 'schema',
316
+ path: parser.$refs._root$Ref.path + "#",
317
+ pathFromRoot: "#",
318
+ indirections: 0,
319
+ inventory,
320
+ $refs: parser.$refs,
321
+ options,
322
+ });
323
+ // Remap all $ref pointers
324
+ remap(inventory);
325
+ };
326
+ exports.bundle = bundle;
package/dist/lib/index.js CHANGED
@@ -40,7 +40,7 @@ exports.sendRequest = exports.$RefParser = exports.getResolvedInput = void 0;
40
40
  const refs_js_1 = __importDefault(require("./refs.js"));
41
41
  const parse_js_1 = require("./parse.js");
42
42
  const resolve_external_js_1 = require("./resolve-external.js");
43
- const bundle_js_1 = __importDefault(require("./bundle.js"));
43
+ const bundle_js_1 = require("./bundle.js");
44
44
  const dereference_js_1 = __importDefault(require("./dereference.js"));
45
45
  const url = __importStar(require("./util/url.js"));
46
46
  const errors_js_1 = require("./util/errors.js");
@@ -130,7 +130,7 @@ class $RefParser {
130
130
  if (errors.length > 0) {
131
131
  throw new errors_js_1.JSONParserErrorGroup(this);
132
132
  }
133
- (0, bundle_js_1.default)(this, this.options);
133
+ (0, bundle_js_1.bundle)(this, this.options);
134
134
  const errors2 = errors_js_1.JSONParserErrorGroup.getParserErrors(this);
135
135
  if (errors2.length > 0) {
136
136
  throw new errors_js_1.JSONParserErrorGroup(this);
@@ -0,0 +1,14 @@
1
+ import path from 'node:path';
2
+
3
+ import { describe, expect, it } from 'vitest';
4
+
5
+ import { $RefParser } from '..';
6
+
7
+ describe('bundle', () => {
8
+ it('handles circular reference with description', async () => {
9
+ const refParser = new $RefParser();
10
+ const pathOrUrlOrSchema = path.resolve('lib', '__tests__', 'spec', 'circular-ref-with-description.json');
11
+ const schema = await refParser.bundle({ pathOrUrlOrSchema });
12
+ expect(schema).not.toBeUndefined();
13
+ });
14
+ });
@@ -0,0 +1,11 @@
1
+ {
2
+ "schemas": {
3
+ "Foo": {
4
+ "$ref": "#/schemas/Bar"
5
+ },
6
+ "Bar": {
7
+ "description": "ok",
8
+ "$ref": "#/schemas/Foo"
9
+ }
10
+ }
11
+ }
package/lib/bundle.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import isEqual from "lodash/isEqual";
2
+
1
3
  import $Ref from "./ref.js";
2
4
  import type { ParserOptions } from "./options.js";
3
5
  import Pointer from "./pointer.js";
@@ -8,123 +10,76 @@ import type { JSONSchema } from "./types/index.js";
8
10
 
9
11
  export interface InventoryEntry {
10
12
  $ref: any;
11
- parent: any;
12
- key: any;
13
- pathFromRoot: any;
14
- depth: any;
15
- file: any;
16
- hash: any;
17
- value: any;
18
13
  circular: any;
14
+ depth: any;
19
15
  extended: any;
20
16
  external: any;
17
+ file: any;
18
+ hash: any;
21
19
  indirections: any;
22
- }
23
- /**
24
- * Bundles all external JSON references into the main JSON schema, thus resulting in a schema that
25
- * only has *internal* references, not any *external* references.
26
- * This method mutates the JSON schema object, adding new references and re-mapping existing ones.
27
- *
28
- * @param parser
29
- * @param options
30
- */
31
- function bundle(
32
- parser: $RefParser,
33
- options: ParserOptions,
34
- ) {
35
- // console.log('Bundling $ref pointers in %s', parser.$refs._root$Ref.path);
36
-
37
- // Build an inventory of all $ref pointers in the JSON Schema
38
- const inventory: InventoryEntry[] = [];
39
- crawl<JSONSchema>(parser, "schema", parser.$refs._root$Ref.path + "#", "#", 0, inventory, parser.$refs, options);
40
-
41
- // Remap all $ref pointers
42
- remap(inventory);
20
+ key: any;
21
+ parent: any;
22
+ pathFromRoot: any;
23
+ value: any;
43
24
  }
44
25
 
45
26
  /**
46
- * Recursively crawls the given value, and inventories all JSON references.
47
- *
48
- * @param parent - The object containing the value to crawl. If the value is not an object or array, it will be ignored.
49
- * @param key - The property key of `parent` to be crawled
50
- * @param path - The full path of the property being crawled, possibly with a JSON Pointer in the hash
51
- * @param pathFromRoot - The path of the property being crawled, from the schema root
52
- * @param indirections
53
- * @param inventory - An array of already-inventoried $ref pointers
54
- * @param $refs
55
- * @param options
27
+ * TODO
56
28
  */
57
- function crawl<S extends object = JSONSchema>(
58
- parent: object | $RefParser,
59
- key: string | null,
60
- path: string,
61
- pathFromRoot: string,
62
- indirections: number,
63
- inventory: InventoryEntry[],
64
- $refs: $Refs<S>,
65
- options: ParserOptions,
66
- ) {
67
- const obj = key === null ? parent : parent[key as keyof typeof parent];
68
-
69
- if (obj && typeof obj === "object" && !ArrayBuffer.isView(obj)) {
70
- if ($Ref.isAllowed$Ref(obj)) {
71
- inventory$Ref(parent, key, path, pathFromRoot, indirections, inventory, $refs, options);
72
- } else {
73
- // Crawl the object in a specific order that's optimized for bundling.
74
- // This is important because it determines how `pathFromRoot` gets built,
75
- // which later determines which keys get dereferenced and which ones get remapped
76
- const keys = Object.keys(obj).sort((a, b) => {
77
- // Most people will expect references to be bundled into the the "definitions" property,
78
- // so we always crawl that property first, if it exists.
79
- if (a === "definitions") {
80
- return -1;
81
- } else if (b === "definitions") {
82
- return 1;
83
- } else {
84
- // Otherwise, crawl the keys based on their length.
85
- // This produces the shortest possible bundled references
86
- return a.length - b.length;
87
- }
88
- }) as (keyof typeof obj)[];
89
-
90
- for (const key of keys) {
91
- const keyPath = Pointer.join(path, key);
92
- const keyPathFromRoot = Pointer.join(pathFromRoot, key);
93
- const value = obj[key];
94
-
95
- if ($Ref.isAllowed$Ref(value)) {
96
- inventory$Ref(obj, key, path, keyPathFromRoot, indirections, inventory, $refs, options);
97
- } else {
98
- crawl(obj, key, keyPath, keyPathFromRoot, indirections, inventory, $refs, options);
29
+ const findInInventory = (inventory: Array<InventoryEntry>, $refParent: any, $refKey: any) => {
30
+ for (const entry of inventory) {
31
+ if (entry) {
32
+ if (isEqual(entry.parent, $refParent)) {
33
+ if (entry.key === $refKey) {
34
+ return entry;
99
35
  }
100
36
  }
101
37
  }
102
38
  }
103
- }
39
+ return undefined;
40
+ };
104
41
 
105
42
  /**
106
43
  * Inventories the given JSON Reference (i.e. records detailed information about it so we can
107
44
  * optimize all $refs in the schema), and then crawls the resolved value.
108
- *
109
- * @param $refParent - The object that contains a JSON Reference as one of its keys
110
- * @param $refKey - The key in `$refParent` that is a JSON Reference
111
- * @param path - The full path of the JSON Reference at `$refKey`, possibly with a JSON Pointer in the hash
112
- * @param indirections - unknown
113
- * @param pathFromRoot - The path of the JSON Reference at `$refKey`, from the schema root
114
- * @param inventory - An array of already-inventoried $ref pointers
115
- * @param $refs
116
- * @param options
117
45
  */
118
- function inventory$Ref<S extends object = JSONSchema>(
119
- $refParent: any,
120
- $refKey: string | null,
121
- path: string,
122
- pathFromRoot: string,
123
- indirections: number,
124
- inventory: InventoryEntry[],
125
- $refs: $Refs<S>,
126
- options: ParserOptions,
127
- ) {
46
+ const inventory$Ref = <S extends object = JSONSchema>({
47
+ $refKey,
48
+ $refParent,
49
+ $refs,
50
+ indirections,
51
+ inventory,
52
+ options,
53
+ path,
54
+ pathFromRoot,
55
+ }: {
56
+ /**
57
+ * The key in `$refParent` that is a JSON Reference
58
+ */
59
+ $refKey: string | null;
60
+ /**
61
+ * The object that contains a JSON Reference as one of its keys
62
+ */
63
+ $refParent: any;
64
+ $refs: $Refs<S>;
65
+ /**
66
+ * unknown
67
+ */
68
+ indirections: number;
69
+ /**
70
+ * An array of already-inventoried $ref pointers
71
+ */
72
+ inventory: Array<InventoryEntry>;
73
+ options: ParserOptions;
74
+ /**
75
+ * The full path of the JSON Reference at `$refKey`, possibly with a JSON Pointer in the hash
76
+ */
77
+ path: string;
78
+ /**
79
+ * The path of the JSON Reference at `$refKey`, from the schema root
80
+ */
81
+ pathFromRoot: string;
82
+ }) => {
128
83
  const $ref = $refKey === null ? $refParent : $refParent[$refKey];
129
84
  const $refPath = url.resolve(path, $ref.$ref);
130
85
  const pointer = $refs._resolve($refPath, pathFromRoot, options);
@@ -151,24 +106,135 @@ function inventory$Ref<S extends object = JSONSchema>(
151
106
 
152
107
  inventory.push({
153
108
  $ref, // The JSON Reference (e.g. {$ref: string})
154
- parent: $refParent, // The object that contains this $ref pointer
155
- key: $refKey, // The key in `parent` that is the $ref pointer
156
- pathFromRoot, // The path to the $ref pointer, from the JSON Schema root
157
- depth, // How far from the JSON Schema root is this $ref pointer?
158
- file, // The file that the $ref pointer resolves to
159
- hash, // The hash within `file` that the $ref pointer resolves to
160
- value: pointer.value, // The resolved value of the $ref pointer
161
109
  circular: pointer.circular, // Is this $ref pointer DIRECTLY circular? (i.e. it references itself)
110
+ depth, // How far from the JSON Schema root is this $ref pointer?
162
111
  extended, // Does this $ref extend its resolved value? (i.e. it has extra properties, in addition to "$ref")
163
112
  external, // Does this $ref pointer point to a file other than the main JSON Schema file?
113
+ file, // The file that the $ref pointer resolves to
114
+ hash, // The hash within `file` that the $ref pointer resolves to
164
115
  indirections, // The number of indirect references that were traversed to resolve the value
116
+ key: $refKey, // The key in `parent` that is the $ref pointer
117
+ parent: $refParent, // The object that contains this $ref pointer
118
+ pathFromRoot, // The path to the $ref pointer, from the JSON Schema root
119
+ value: pointer.value, // The resolved value of the $ref pointer
165
120
  });
166
121
 
167
122
  // Recursively crawl the resolved value
168
123
  if (!existingEntry || external) {
169
- crawl(pointer.value, null, pointer.path, pathFromRoot, indirections + 1, inventory, $refs, options);
124
+ crawl({
125
+ parent: pointer.value,
126
+ key: null,
127
+ path: pointer.path,
128
+ pathFromRoot,
129
+ indirections: indirections + 1,
130
+ inventory,
131
+ $refs,
132
+ options,
133
+ });
170
134
  }
171
- }
135
+ };
136
+
137
+ /**
138
+ * Recursively crawls the given value, and inventories all JSON references.
139
+ */
140
+ const crawl = <S extends object = JSONSchema>({
141
+ $refs,
142
+ indirections,
143
+ inventory,
144
+ key,
145
+ options,
146
+ parent,
147
+ path,
148
+ pathFromRoot,
149
+ }: {
150
+ $refs: $Refs<S>;
151
+ indirections: number;
152
+ /**
153
+ * An array of already-inventoried $ref pointers
154
+ */
155
+ inventory: Array<InventoryEntry>;
156
+ /**
157
+ * The property key of `parent` to be crawled
158
+ */
159
+ key: string | null;
160
+ options: ParserOptions;
161
+ /**
162
+ * The object containing the value to crawl. If the value is not an object or array, it will be ignored.
163
+ */
164
+ parent: object | $RefParser;
165
+ /**
166
+ * The full path of the property being crawled, possibly with a JSON Pointer in the hash
167
+ */
168
+ path: string;
169
+ /**
170
+ * The path of the property being crawled, from the schema root
171
+ */
172
+ pathFromRoot: string;
173
+ }) => {
174
+ const obj = key === null ? parent : parent[key as keyof typeof parent];
175
+
176
+ if (obj && typeof obj === "object" && !ArrayBuffer.isView(obj)) {
177
+ if ($Ref.isAllowed$Ref(obj)) {
178
+ inventory$Ref({
179
+ $refParent: parent,
180
+ $refKey: key,
181
+ path,
182
+ pathFromRoot,
183
+ indirections,
184
+ inventory,
185
+ $refs,
186
+ options,
187
+ });
188
+ } else {
189
+ // Crawl the object in a specific order that's optimized for bundling.
190
+ // This is important because it determines how `pathFromRoot` gets built,
191
+ // which later determines which keys get dereferenced and which ones get remapped
192
+ const keys = Object.keys(obj).sort((a, b) => {
193
+ // Most people will expect references to be bundled into the "definitions" property,
194
+ // so we always crawl that property first, if it exists.
195
+ if (a === "definitions") {
196
+ return -1;
197
+ } else if (b === "definitions") {
198
+ return 1;
199
+ } else {
200
+ // Otherwise, crawl the keys based on their length.
201
+ // This produces the shortest possible bundled references
202
+ return a.length - b.length;
203
+ }
204
+ }) as (keyof typeof obj)[];
205
+
206
+ for (const key of keys) {
207
+ const keyPath = Pointer.join(path, key);
208
+ const keyPathFromRoot = Pointer.join(pathFromRoot, key);
209
+ const value = obj[key];
210
+
211
+ if ($Ref.isAllowed$Ref(value)) {
212
+ inventory$Ref({
213
+ $refParent: obj,
214
+ $refKey: key,
215
+ path,
216
+ pathFromRoot: keyPathFromRoot,
217
+ indirections,
218
+ inventory,
219
+ $refs,
220
+ options,
221
+ });
222
+ } else {
223
+ crawl({
224
+ parent: obj,
225
+ key,
226
+ path: keyPath,
227
+ pathFromRoot: keyPathFromRoot,
228
+ indirections,
229
+ inventory,
230
+ $refs,
231
+ options,
232
+ });
233
+ }
234
+ }
235
+ }
236
+ }
237
+ };
172
238
 
173
239
  /**
174
240
  * Re-maps every $ref pointer, so that they're all relative to the root of the JSON Schema.
@@ -280,20 +346,38 @@ function remap(inventory: InventoryEntry[]) {
280
346
  // }
281
347
  }
282
348
 
283
- /**
284
- * TODO
285
- */
286
- function findInInventory(inventory: InventoryEntry[], $refParent: any, $refKey: any) {
287
- for (const existingEntry of inventory) {
288
- if (existingEntry && existingEntry.parent === $refParent && existingEntry.key === $refKey) {
289
- return existingEntry;
290
- }
291
- }
292
- return undefined;
293
- }
294
-
295
349
  function removeFromInventory(inventory: InventoryEntry[], entry: any) {
296
350
  const index = inventory.indexOf(entry);
297
351
  inventory.splice(index, 1);
298
352
  }
299
- export default bundle;
353
+
354
+ /**
355
+ * Bundles all external JSON references into the main JSON schema, thus resulting in a schema that
356
+ * only has *internal* references, not any *external* references.
357
+ * This method mutates the JSON schema object, adding new references and re-mapping existing ones.
358
+ *
359
+ * @param parser
360
+ * @param options
361
+ */
362
+ export const bundle = (
363
+ parser: $RefParser,
364
+ options: ParserOptions,
365
+ ) => {
366
+ // console.log('Bundling $ref pointers in %s', parser.$refs._root$Ref.path);
367
+
368
+ // Build an inventory of all $ref pointers in the JSON Schema
369
+ const inventory: InventoryEntry[] = [];
370
+ crawl<JSONSchema>({
371
+ parent: parser,
372
+ key: 'schema',
373
+ path: parser.$refs._root$Ref.path + "#",
374
+ pathFromRoot: "#",
375
+ indirections: 0,
376
+ inventory,
377
+ $refs: parser.$refs,
378
+ options,
379
+ });
380
+
381
+ // Remap all $ref pointers
382
+ remap(inventory);
383
+ };
package/lib/index.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import $Refs from "./refs.js";
2
2
  import { newFile, parseFile } from "./parse.js";
3
3
  import { resolveExternal } from "./resolve-external.js";
4
- import _bundle from "./bundle.js";
4
+ import { bundle as _bundle } from "./bundle.js";
5
5
  import _dereference from "./dereference.js";
6
6
  import * as url from "./util/url.js";
7
7
  import { isHandledError, JSONParserErrorGroup } from "./util/errors.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hey-api/json-schema-ref-parser",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "Parse, Resolve, and Dereference JSON Schema $ref pointers",
5
5
  "homepage": "https://heyapi.dev/",
6
6
  "repository": {
@@ -57,6 +57,7 @@
57
57
  "@eslint/js": "^9.16.0",
58
58
  "@types/eslint": "9.6.1",
59
59
  "@types/js-yaml": "^4.0.9",
60
+ "@types/lodash": "^4",
60
61
  "@types/node": "^22",
61
62
  "@typescript-eslint/eslint-plugin": "^8.17.0",
62
63
  "@typescript-eslint/parser": "^8.17.0",
@@ -80,7 +81,8 @@
80
81
  "dependencies": {
81
82
  "@jsdevtools/ono": "^7.1.3",
82
83
  "@types/json-schema": "^7.0.15",
83
- "js-yaml": "^4.1.0"
84
+ "js-yaml": "^4.1.0",
85
+ "lodash": "^4.17.21"
84
86
  },
85
87
  "release": {
86
88
  "branches": [