@hey-api/json-schema-ref-parser 1.2.3 → 1.3.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.
- package/README.md +9 -84
- package/dist/index.d.mts +629 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +1887 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +42 -78
- package/src/__tests__/bundle.test.ts +59 -0
- package/src/__tests__/index.test.ts +43 -0
- package/src/__tests__/pointer.test.ts +34 -0
- package/src/__tests__/utils.ts +3 -0
- package/{lib → src}/bundle.ts +143 -229
- package/{lib → src}/dereference.ts +20 -43
- package/{lib → src}/index.ts +103 -125
- package/{lib → src}/options.ts +13 -9
- package/{lib → src}/parse.ts +19 -15
- package/src/parsers/binary.ts +13 -0
- package/{lib → src}/parsers/json.ts +5 -6
- package/src/parsers/text.ts +21 -0
- package/{lib → src}/parsers/yaml.ts +9 -9
- package/{lib → src}/pointer.ts +42 -23
- package/{lib → src}/ref.ts +25 -21
- package/{lib → src}/refs.ts +23 -26
- package/{lib → src}/resolve-external.ts +91 -60
- package/{lib → src}/resolvers/file.ts +7 -10
- package/{lib → src}/resolvers/url.ts +12 -8
- package/{lib → src}/types/index.ts +9 -2
- package/src/util/convert-path-to-posix.ts +8 -0
- package/{lib → src}/util/errors.ts +38 -36
- package/{lib → src}/util/is-windows.ts +1 -1
- package/{lib → src}/util/plugins.ts +7 -8
- package/{lib → src}/util/url.ts +41 -42
- package/dist/lib/__tests__/bundle.test.d.ts +0 -1
- package/dist/lib/__tests__/bundle.test.js +0 -50
- package/dist/lib/__tests__/index.test.d.ts +0 -1
- package/dist/lib/__tests__/index.test.js +0 -43
- package/dist/lib/__tests__/pointer.test.d.ts +0 -1
- package/dist/lib/__tests__/pointer.test.js +0 -27
- package/dist/lib/bundle.d.ts +0 -26
- package/dist/lib/bundle.js +0 -600
- package/dist/lib/dereference.d.ts +0 -11
- package/dist/lib/dereference.js +0 -226
- package/dist/lib/index.d.ts +0 -92
- package/dist/lib/index.js +0 -525
- package/dist/lib/options.d.ts +0 -61
- package/dist/lib/options.js +0 -45
- package/dist/lib/parse.d.ts +0 -13
- package/dist/lib/parse.js +0 -87
- package/dist/lib/parsers/binary.d.ts +0 -2
- package/dist/lib/parsers/binary.js +0 -12
- package/dist/lib/parsers/json.d.ts +0 -2
- package/dist/lib/parsers/json.js +0 -38
- package/dist/lib/parsers/text.d.ts +0 -2
- package/dist/lib/parsers/text.js +0 -18
- package/dist/lib/parsers/yaml.d.ts +0 -2
- package/dist/lib/parsers/yaml.js +0 -28
- package/dist/lib/pointer.d.ts +0 -88
- package/dist/lib/pointer.js +0 -297
- package/dist/lib/ref.d.ts +0 -180
- package/dist/lib/ref.js +0 -226
- package/dist/lib/refs.d.ts +0 -127
- package/dist/lib/refs.js +0 -232
- package/dist/lib/resolve-external.d.ts +0 -13
- package/dist/lib/resolve-external.js +0 -151
- package/dist/lib/resolvers/file.d.ts +0 -6
- package/dist/lib/resolvers/file.js +0 -61
- package/dist/lib/resolvers/url.d.ts +0 -17
- package/dist/lib/resolvers/url.js +0 -62
- package/dist/lib/types/index.d.ts +0 -43
- package/dist/lib/types/index.js +0 -2
- package/dist/lib/util/convert-path-to-posix.d.ts +0 -1
- package/dist/lib/util/convert-path-to-posix.js +0 -14
- package/dist/lib/util/errors.d.ts +0 -56
- package/dist/lib/util/errors.js +0 -112
- package/dist/lib/util/is-windows.d.ts +0 -1
- package/dist/lib/util/is-windows.js +0 -6
- package/dist/lib/util/plugins.d.ts +0 -16
- package/dist/lib/util/plugins.js +0 -45
- package/dist/lib/util/url.d.ts +0 -79
- package/dist/lib/util/url.js +0 -285
- package/dist/vite.config.d.ts +0 -2
- package/dist/vite.config.js +0 -19
- package/lib/__tests__/bundle.test.ts +0 -52
- package/lib/__tests__/index.test.ts +0 -45
- package/lib/__tests__/pointer.test.ts +0 -26
- package/lib/__tests__/spec/circular-ref-with-description.json +0 -11
- package/lib/__tests__/spec/multiple-refs.json +0 -34
- package/lib/__tests__/spec/openapi-paths-ref.json +0 -46
- package/lib/__tests__/spec/path-parameter.json +0 -16
- package/lib/parsers/binary.ts +0 -13
- package/lib/parsers/text.ts +0 -21
- package/lib/util/convert-path-to-posix.ts +0 -11
- /package/{LICENSE → LICENSE.md} +0 -0
package/dist/lib/bundle.js
DELETED
|
@@ -1,600 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
-
};
|
|
38
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.bundle = void 0;
|
|
40
|
-
const ref_js_1 = __importDefault(require("./ref.js"));
|
|
41
|
-
const pointer_js_1 = __importDefault(require("./pointer.js"));
|
|
42
|
-
const url = __importStar(require("./util/url.js"));
|
|
43
|
-
const DEBUG_PERFORMANCE = process.env.DEBUG === "true" ||
|
|
44
|
-
(typeof globalThis !== "undefined" && globalThis.DEBUG_BUNDLE_PERFORMANCE === true);
|
|
45
|
-
const perf = {
|
|
46
|
-
mark: (name) => DEBUG_PERFORMANCE && performance.mark(name),
|
|
47
|
-
measure: (name, start, end) => DEBUG_PERFORMANCE && performance.measure(name, start, end),
|
|
48
|
-
log: (message, ...args) => DEBUG_PERFORMANCE && console.log("[PERF] " + message, ...args),
|
|
49
|
-
warn: (message, ...args) => DEBUG_PERFORMANCE && console.warn("[PERF] " + message, ...args),
|
|
50
|
-
};
|
|
51
|
-
/**
|
|
52
|
-
* Fast lookup using Map instead of linear search with deep equality
|
|
53
|
-
*/
|
|
54
|
-
const createInventoryLookup = () => {
|
|
55
|
-
const lookup = new Map();
|
|
56
|
-
const objectIds = new WeakMap(); // Use WeakMap to avoid polluting objects
|
|
57
|
-
let idCounter = 0;
|
|
58
|
-
let lookupCount = 0;
|
|
59
|
-
let addCount = 0;
|
|
60
|
-
const getObjectId = (obj) => {
|
|
61
|
-
if (!objectIds.has(obj)) {
|
|
62
|
-
objectIds.set(obj, `obj_${++idCounter}`);
|
|
63
|
-
}
|
|
64
|
-
return objectIds.get(obj);
|
|
65
|
-
};
|
|
66
|
-
const createInventoryKey = ($refParent, $refKey) => {
|
|
67
|
-
// Use WeakMap-based lookup to avoid polluting the actual schema objects
|
|
68
|
-
return `${getObjectId($refParent)}_${$refKey}`;
|
|
69
|
-
};
|
|
70
|
-
return {
|
|
71
|
-
add: (entry) => {
|
|
72
|
-
addCount++;
|
|
73
|
-
const key = createInventoryKey(entry.parent, entry.key);
|
|
74
|
-
lookup.set(key, entry);
|
|
75
|
-
if (addCount % 100 === 0) {
|
|
76
|
-
perf.log(`Inventory lookup: Added ${addCount} entries, map size: ${lookup.size}`);
|
|
77
|
-
}
|
|
78
|
-
},
|
|
79
|
-
find: ($refParent, $refKey) => {
|
|
80
|
-
lookupCount++;
|
|
81
|
-
const key = createInventoryKey($refParent, $refKey);
|
|
82
|
-
const result = lookup.get(key);
|
|
83
|
-
if (lookupCount % 100 === 0) {
|
|
84
|
-
perf.log(`Inventory lookup: ${lookupCount} lookups performed`);
|
|
85
|
-
}
|
|
86
|
-
return result;
|
|
87
|
-
},
|
|
88
|
-
remove: (entry) => {
|
|
89
|
-
const key = createInventoryKey(entry.parent, entry.key);
|
|
90
|
-
lookup.delete(key);
|
|
91
|
-
},
|
|
92
|
-
getStats: () => ({ lookupCount, addCount, mapSize: lookup.size }),
|
|
93
|
-
};
|
|
94
|
-
};
|
|
95
|
-
/**
|
|
96
|
-
* Determine the container type from a JSON Pointer path.
|
|
97
|
-
* Analyzes the path tokens to identify the appropriate OpenAPI component container.
|
|
98
|
-
*
|
|
99
|
-
* @param path - The JSON Pointer path to analyze
|
|
100
|
-
* @returns The container type: "schemas", "parameters", "requestBodies", "responses", or "headers"
|
|
101
|
-
*/
|
|
102
|
-
const getContainerTypeFromPath = (path) => {
|
|
103
|
-
const tokens = pointer_js_1.default.parse(path);
|
|
104
|
-
const has = (t) => tokens.includes(t);
|
|
105
|
-
// Prefer more specific containers first
|
|
106
|
-
if (has("parameters")) {
|
|
107
|
-
return "parameters";
|
|
108
|
-
}
|
|
109
|
-
if (has("requestBody")) {
|
|
110
|
-
return "requestBodies";
|
|
111
|
-
}
|
|
112
|
-
if (has("headers")) {
|
|
113
|
-
return "headers";
|
|
114
|
-
}
|
|
115
|
-
if (has("responses")) {
|
|
116
|
-
return "responses";
|
|
117
|
-
}
|
|
118
|
-
if (has("schema")) {
|
|
119
|
-
return "schemas";
|
|
120
|
-
}
|
|
121
|
-
// default: treat as schema-like
|
|
122
|
-
return "schemas";
|
|
123
|
-
};
|
|
124
|
-
/**
|
|
125
|
-
* Inventories the given JSON Reference (i.e. records detailed information about it so we can
|
|
126
|
-
* optimize all $refs in the schema), and then crawls the resolved value.
|
|
127
|
-
*/
|
|
128
|
-
const inventory$Ref = ({ $refKey, $refParent, $refs, indirections, inventory, inventoryLookup, options, path, pathFromRoot, visitedObjects = new WeakSet(), resolvedRefs = new Map(), }) => {
|
|
129
|
-
perf.mark("inventory-ref-start");
|
|
130
|
-
const $ref = $refKey === null ? $refParent : $refParent[$refKey];
|
|
131
|
-
const $refPath = url.resolve(path, $ref.$ref);
|
|
132
|
-
// Check cache first to avoid redundant resolution
|
|
133
|
-
let pointer = resolvedRefs.get($refPath);
|
|
134
|
-
if (!pointer) {
|
|
135
|
-
perf.mark("resolve-start");
|
|
136
|
-
pointer = $refs._resolve($refPath, pathFromRoot, options);
|
|
137
|
-
perf.mark("resolve-end");
|
|
138
|
-
perf.measure("resolve-time", "resolve-start", "resolve-end");
|
|
139
|
-
if (pointer) {
|
|
140
|
-
resolvedRefs.set($refPath, pointer);
|
|
141
|
-
perf.log(`Cached resolved $ref: ${$refPath}`);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
if (pointer === null) {
|
|
145
|
-
perf.mark("inventory-ref-end");
|
|
146
|
-
perf.measure("inventory-ref-time", "inventory-ref-start", "inventory-ref-end");
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
const parsed = pointer_js_1.default.parse(pathFromRoot);
|
|
150
|
-
const depth = parsed.length;
|
|
151
|
-
const file = url.stripHash(pointer.path);
|
|
152
|
-
const hash = url.getHash(pointer.path);
|
|
153
|
-
const external = file !== $refs._root$Ref.path;
|
|
154
|
-
const extended = ref_js_1.default.isExtended$Ref($ref);
|
|
155
|
-
indirections += pointer.indirections;
|
|
156
|
-
// Check if this exact location (parent + key + pathFromRoot) has already been inventoried
|
|
157
|
-
perf.mark("lookup-start");
|
|
158
|
-
const existingEntry = inventoryLookup.find($refParent, $refKey);
|
|
159
|
-
perf.mark("lookup-end");
|
|
160
|
-
perf.measure("lookup-time", "lookup-start", "lookup-end");
|
|
161
|
-
if (existingEntry && existingEntry.pathFromRoot === pathFromRoot) {
|
|
162
|
-
// This exact location has already been inventoried, so we don't need to process it again
|
|
163
|
-
if (depth < existingEntry.depth || indirections < existingEntry.indirections) {
|
|
164
|
-
removeFromInventory(inventory, existingEntry);
|
|
165
|
-
inventoryLookup.remove(existingEntry);
|
|
166
|
-
}
|
|
167
|
-
else {
|
|
168
|
-
perf.mark("inventory-ref-end");
|
|
169
|
-
perf.measure("inventory-ref-time", "inventory-ref-start", "inventory-ref-end");
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
const newEntry = {
|
|
174
|
-
$ref, // The JSON Reference (e.g. {$ref: string})
|
|
175
|
-
circular: pointer.circular, // Is this $ref pointer DIRECTLY circular? (i.e. it references itself)
|
|
176
|
-
depth, // How far from the JSON Schema root is this $ref pointer?
|
|
177
|
-
extended, // Does this $ref extend its resolved value? (i.e. it has extra properties, in addition to "$ref")
|
|
178
|
-
external, // Does this $ref pointer point to a file other than the main JSON Schema file?
|
|
179
|
-
file, // The file that the $ref pointer resolves to
|
|
180
|
-
hash, // The hash within `file` that the $ref pointer resolves to
|
|
181
|
-
indirections, // The number of indirect references that were traversed to resolve the value
|
|
182
|
-
key: $refKey, // The key in `parent` that is the $ref pointer
|
|
183
|
-
parent: $refParent, // The object that contains this $ref pointer
|
|
184
|
-
pathFromRoot, // The path to the $ref pointer, from the JSON Schema root
|
|
185
|
-
value: pointer.value, // The resolved value of the $ref pointer
|
|
186
|
-
originalContainerType: external ? getContainerTypeFromPath(pointer.path) : undefined, // The original container type in the external file
|
|
187
|
-
};
|
|
188
|
-
inventory.push(newEntry);
|
|
189
|
-
inventoryLookup.add(newEntry);
|
|
190
|
-
perf.log(`Inventoried $ref: ${$ref.$ref} -> ${file}${hash} (external: ${external}, depth: ${depth})`);
|
|
191
|
-
// Recursively crawl the resolved value
|
|
192
|
-
if (!existingEntry || external) {
|
|
193
|
-
perf.mark("crawl-recursive-start");
|
|
194
|
-
crawl({
|
|
195
|
-
parent: pointer.value,
|
|
196
|
-
key: null,
|
|
197
|
-
path: pointer.path,
|
|
198
|
-
pathFromRoot,
|
|
199
|
-
indirections: indirections + 1,
|
|
200
|
-
inventory,
|
|
201
|
-
inventoryLookup,
|
|
202
|
-
$refs,
|
|
203
|
-
options,
|
|
204
|
-
visitedObjects,
|
|
205
|
-
resolvedRefs,
|
|
206
|
-
});
|
|
207
|
-
perf.mark("crawl-recursive-end");
|
|
208
|
-
perf.measure("crawl-recursive-time", "crawl-recursive-start", "crawl-recursive-end");
|
|
209
|
-
}
|
|
210
|
-
perf.mark("inventory-ref-end");
|
|
211
|
-
perf.measure("inventory-ref-time", "inventory-ref-start", "inventory-ref-end");
|
|
212
|
-
};
|
|
213
|
-
/**
|
|
214
|
-
* Recursively crawls the given value, and inventories all JSON references.
|
|
215
|
-
*/
|
|
216
|
-
const crawl = ({ $refs, indirections, inventory, inventoryLookup, key, options, parent, path, pathFromRoot, visitedObjects = new WeakSet(), resolvedRefs = new Map(), }) => {
|
|
217
|
-
const obj = key === null ? parent : parent[key];
|
|
218
|
-
if (obj && typeof obj === "object" && !ArrayBuffer.isView(obj)) {
|
|
219
|
-
// Early exit if we've already processed this exact object
|
|
220
|
-
if (visitedObjects.has(obj)) {
|
|
221
|
-
perf.log(`Skipping already visited object at ${pathFromRoot}`);
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
if (ref_js_1.default.isAllowed$Ref(obj)) {
|
|
225
|
-
perf.log(`Found $ref at ${pathFromRoot}: ${obj.$ref}`);
|
|
226
|
-
inventory$Ref({
|
|
227
|
-
$refParent: parent,
|
|
228
|
-
$refKey: key,
|
|
229
|
-
path,
|
|
230
|
-
pathFromRoot,
|
|
231
|
-
indirections,
|
|
232
|
-
inventory,
|
|
233
|
-
inventoryLookup,
|
|
234
|
-
$refs,
|
|
235
|
-
options,
|
|
236
|
-
visitedObjects,
|
|
237
|
-
resolvedRefs,
|
|
238
|
-
});
|
|
239
|
-
}
|
|
240
|
-
else {
|
|
241
|
-
// Mark this object as visited BEFORE processing its children
|
|
242
|
-
visitedObjects.add(obj);
|
|
243
|
-
// Crawl the object in a specific order that's optimized for bundling.
|
|
244
|
-
// This is important because it determines how `pathFromRoot` gets built,
|
|
245
|
-
// which later determines which keys get dereferenced and which ones get remapped
|
|
246
|
-
const keys = Object.keys(obj).sort((a, b) => {
|
|
247
|
-
// Most people will expect references to be bundled into the "definitions" property,
|
|
248
|
-
// so we always crawl that property first, if it exists.
|
|
249
|
-
if (a === "definitions") {
|
|
250
|
-
return -1;
|
|
251
|
-
}
|
|
252
|
-
else if (b === "definitions") {
|
|
253
|
-
return 1;
|
|
254
|
-
}
|
|
255
|
-
else {
|
|
256
|
-
// Otherwise, crawl the keys based on their length.
|
|
257
|
-
// This produces the shortest possible bundled references
|
|
258
|
-
return a.length - b.length;
|
|
259
|
-
}
|
|
260
|
-
});
|
|
261
|
-
for (const key of keys) {
|
|
262
|
-
const keyPath = pointer_js_1.default.join(path, key);
|
|
263
|
-
const keyPathFromRoot = pointer_js_1.default.join(pathFromRoot, key);
|
|
264
|
-
const value = obj[key];
|
|
265
|
-
if (ref_js_1.default.isAllowed$Ref(value)) {
|
|
266
|
-
inventory$Ref({
|
|
267
|
-
$refParent: obj,
|
|
268
|
-
$refKey: key,
|
|
269
|
-
path,
|
|
270
|
-
pathFromRoot: keyPathFromRoot,
|
|
271
|
-
indirections,
|
|
272
|
-
inventory,
|
|
273
|
-
inventoryLookup,
|
|
274
|
-
$refs,
|
|
275
|
-
options,
|
|
276
|
-
visitedObjects,
|
|
277
|
-
resolvedRefs,
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
else {
|
|
281
|
-
crawl({
|
|
282
|
-
parent: obj,
|
|
283
|
-
key,
|
|
284
|
-
path: keyPath,
|
|
285
|
-
pathFromRoot: keyPathFromRoot,
|
|
286
|
-
indirections,
|
|
287
|
-
inventory,
|
|
288
|
-
inventoryLookup,
|
|
289
|
-
$refs,
|
|
290
|
-
options,
|
|
291
|
-
visitedObjects,
|
|
292
|
-
resolvedRefs,
|
|
293
|
-
});
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
};
|
|
299
|
-
/**
|
|
300
|
-
* Remap external refs by hoisting resolved values into a shared container in the root schema
|
|
301
|
-
* and pointing all occurrences to those internal definitions. Internal refs remain internal.
|
|
302
|
-
*/
|
|
303
|
-
function remap(parser, inventory) {
|
|
304
|
-
perf.log(`Starting remap with ${inventory.length} inventory entries`);
|
|
305
|
-
perf.mark("remap-start");
|
|
306
|
-
const root = parser.schema;
|
|
307
|
-
// Group & sort all the $ref pointers, so they're in the order that we need to dereference/remap them
|
|
308
|
-
perf.mark("sort-inventory-start");
|
|
309
|
-
inventory.sort((a, b) => {
|
|
310
|
-
if (a.file !== b.file) {
|
|
311
|
-
// Group all the $refs that point to the same file
|
|
312
|
-
return a.file < b.file ? -1 : +1;
|
|
313
|
-
}
|
|
314
|
-
else if (a.hash !== b.hash) {
|
|
315
|
-
// Group all the $refs that point to the same part of the file
|
|
316
|
-
return a.hash < b.hash ? -1 : +1;
|
|
317
|
-
}
|
|
318
|
-
else if (a.circular !== b.circular) {
|
|
319
|
-
// If the $ref points to itself, then sort it higher than other $refs that point to this $ref
|
|
320
|
-
return a.circular ? -1 : +1;
|
|
321
|
-
}
|
|
322
|
-
else if (a.extended !== b.extended) {
|
|
323
|
-
// If the $ref extends the resolved value, then sort it lower than other $refs that don't extend the value
|
|
324
|
-
return a.extended ? +1 : -1;
|
|
325
|
-
}
|
|
326
|
-
else if (a.indirections !== b.indirections) {
|
|
327
|
-
// Sort direct references higher than indirect references
|
|
328
|
-
return a.indirections - b.indirections;
|
|
329
|
-
}
|
|
330
|
-
else if (a.depth !== b.depth) {
|
|
331
|
-
// Sort $refs by how close they are to the JSON Schema root
|
|
332
|
-
return a.depth - b.depth;
|
|
333
|
-
}
|
|
334
|
-
else {
|
|
335
|
-
// Determine how far each $ref is from the "definitions" property.
|
|
336
|
-
// Most people will expect references to be bundled into the the "definitions" property if possible.
|
|
337
|
-
const aDefinitionsIndex = a.pathFromRoot.lastIndexOf("/definitions");
|
|
338
|
-
const bDefinitionsIndex = b.pathFromRoot.lastIndexOf("/definitions");
|
|
339
|
-
if (aDefinitionsIndex !== bDefinitionsIndex) {
|
|
340
|
-
// Give higher priority to the $ref that's closer to the "definitions" property
|
|
341
|
-
return bDefinitionsIndex - aDefinitionsIndex;
|
|
342
|
-
}
|
|
343
|
-
else {
|
|
344
|
-
// All else is equal, so use the shorter path, which will produce the shortest possible reference
|
|
345
|
-
return a.pathFromRoot.length - b.pathFromRoot.length;
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
});
|
|
349
|
-
perf.mark("sort-inventory-end");
|
|
350
|
-
perf.measure("sort-inventory-time", "sort-inventory-start", "sort-inventory-end");
|
|
351
|
-
perf.log(`Sorted ${inventory.length} inventory entries`);
|
|
352
|
-
// Ensure or return a container by component type. Prefer OpenAPI-aware placement;
|
|
353
|
-
// otherwise use existing root containers; otherwise create components/*.
|
|
354
|
-
const ensureContainer = (type) => {
|
|
355
|
-
const isOas3 = !!(root && typeof root === "object" && typeof root.openapi === "string");
|
|
356
|
-
const isOas2 = !!(root && typeof root === "object" && typeof root.swagger === "string");
|
|
357
|
-
if (isOas3) {
|
|
358
|
-
if (!root.components || typeof root.components !== "object") {
|
|
359
|
-
root.components = {};
|
|
360
|
-
}
|
|
361
|
-
if (!root.components[type] || typeof root.components[type] !== "object") {
|
|
362
|
-
root.components[type] = {};
|
|
363
|
-
}
|
|
364
|
-
return { obj: root.components[type], prefix: `#/components/${type}` };
|
|
365
|
-
}
|
|
366
|
-
if (isOas2) {
|
|
367
|
-
if (type === "schemas") {
|
|
368
|
-
if (!root.definitions || typeof root.definitions !== "object") {
|
|
369
|
-
root.definitions = {};
|
|
370
|
-
}
|
|
371
|
-
return { obj: root.definitions, prefix: "#/definitions" };
|
|
372
|
-
}
|
|
373
|
-
if (type === "parameters") {
|
|
374
|
-
if (!root.parameters || typeof root.parameters !== "object") {
|
|
375
|
-
root.parameters = {};
|
|
376
|
-
}
|
|
377
|
-
return { obj: root.parameters, prefix: "#/parameters" };
|
|
378
|
-
}
|
|
379
|
-
if (type === "responses") {
|
|
380
|
-
if (!root.responses || typeof root.responses !== "object") {
|
|
381
|
-
root.responses = {};
|
|
382
|
-
}
|
|
383
|
-
return { obj: root.responses, prefix: "#/responses" };
|
|
384
|
-
}
|
|
385
|
-
// requestBodies/headers don't exist as reusable containers in OAS2; fallback to definitions
|
|
386
|
-
if (!root.definitions || typeof root.definitions !== "object") {
|
|
387
|
-
root.definitions = {};
|
|
388
|
-
}
|
|
389
|
-
return { obj: root.definitions, prefix: "#/definitions" };
|
|
390
|
-
}
|
|
391
|
-
// No explicit version: prefer existing containers
|
|
392
|
-
if (root && typeof root === "object") {
|
|
393
|
-
if (root.components && typeof root.components === "object") {
|
|
394
|
-
if (!root.components[type] || typeof root.components[type] !== "object") {
|
|
395
|
-
root.components[type] = {};
|
|
396
|
-
}
|
|
397
|
-
return { obj: root.components[type], prefix: `#/components/${type}` };
|
|
398
|
-
}
|
|
399
|
-
if (root.definitions && typeof root.definitions === "object") {
|
|
400
|
-
return { obj: root.definitions, prefix: "#/definitions" };
|
|
401
|
-
}
|
|
402
|
-
// Create components/* by default if nothing exists
|
|
403
|
-
if (!root.components || typeof root.components !== "object") {
|
|
404
|
-
root.components = {};
|
|
405
|
-
}
|
|
406
|
-
if (!root.components[type] || typeof root.components[type] !== "object") {
|
|
407
|
-
root.components[type] = {};
|
|
408
|
-
}
|
|
409
|
-
return { obj: root.components[type], prefix: `#/components/${type}` };
|
|
410
|
-
}
|
|
411
|
-
// Fallback
|
|
412
|
-
root.definitions = root.definitions || {};
|
|
413
|
-
return { obj: root.definitions, prefix: "#/definitions" };
|
|
414
|
-
};
|
|
415
|
-
/**
|
|
416
|
-
* Choose the appropriate component container for bundling.
|
|
417
|
-
* Prioritizes the original container type from external files over usage location.
|
|
418
|
-
*
|
|
419
|
-
* @param entry - The inventory entry containing reference information
|
|
420
|
-
* @returns The container type to use for bundling
|
|
421
|
-
*/
|
|
422
|
-
const chooseComponent = (entry) => {
|
|
423
|
-
// If we have the original container type from the external file, use it
|
|
424
|
-
if (entry.originalContainerType) {
|
|
425
|
-
return entry.originalContainerType;
|
|
426
|
-
}
|
|
427
|
-
// Fallback to usage path for internal references or when original type is not available
|
|
428
|
-
return getContainerTypeFromPath(entry.pathFromRoot);
|
|
429
|
-
};
|
|
430
|
-
// Track names per (container prefix) and per target
|
|
431
|
-
const targetToNameByPrefix = new Map();
|
|
432
|
-
const usedNamesByObj = new Map();
|
|
433
|
-
const sanitize = (name) => name.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
434
|
-
const baseName = (filePath) => {
|
|
435
|
-
try {
|
|
436
|
-
const withoutHash = filePath.split("#")[0];
|
|
437
|
-
const parts = withoutHash.split("/");
|
|
438
|
-
const filename = parts[parts.length - 1] || "schema";
|
|
439
|
-
const dot = filename.lastIndexOf(".");
|
|
440
|
-
return sanitize(dot > 0 ? filename.substring(0, dot) : filename);
|
|
441
|
-
}
|
|
442
|
-
catch {
|
|
443
|
-
return "schema";
|
|
444
|
-
}
|
|
445
|
-
};
|
|
446
|
-
const lastToken = (hash) => {
|
|
447
|
-
if (!hash || hash === "#") {
|
|
448
|
-
return "root";
|
|
449
|
-
}
|
|
450
|
-
const tokens = hash.replace(/^#\//, "").split("/");
|
|
451
|
-
return sanitize(tokens[tokens.length - 1] || "root");
|
|
452
|
-
};
|
|
453
|
-
const uniqueName = (containerObj, proposed) => {
|
|
454
|
-
if (!usedNamesByObj.has(containerObj)) {
|
|
455
|
-
usedNamesByObj.set(containerObj, new Set(Object.keys(containerObj || {})));
|
|
456
|
-
}
|
|
457
|
-
const used = usedNamesByObj.get(containerObj);
|
|
458
|
-
let name = proposed;
|
|
459
|
-
let i = 2;
|
|
460
|
-
while (used.has(name)) {
|
|
461
|
-
name = `${proposed}_${i++}`;
|
|
462
|
-
}
|
|
463
|
-
used.add(name);
|
|
464
|
-
return name;
|
|
465
|
-
};
|
|
466
|
-
perf.mark("remap-loop-start");
|
|
467
|
-
for (const entry of inventory) {
|
|
468
|
-
// Safety check: ensure entry and entry.$ref are valid objects
|
|
469
|
-
if (!entry || !entry.$ref || typeof entry.$ref !== "object") {
|
|
470
|
-
perf.warn(`Skipping invalid inventory entry:`, entry);
|
|
471
|
-
continue;
|
|
472
|
-
}
|
|
473
|
-
// Keep internal refs internal. However, if the $ref extends the resolved value
|
|
474
|
-
// (i.e. it has additional properties in addition to "$ref"), then we must
|
|
475
|
-
// preserve the original $ref rather than rewriting it to the resolved hash.
|
|
476
|
-
if (!entry.external) {
|
|
477
|
-
if (!entry.extended && entry.$ref && typeof entry.$ref === "object") {
|
|
478
|
-
entry.$ref.$ref = entry.hash;
|
|
479
|
-
}
|
|
480
|
-
continue;
|
|
481
|
-
}
|
|
482
|
-
// Avoid changing direct self-references; keep them internal
|
|
483
|
-
if (entry.circular) {
|
|
484
|
-
if (entry.$ref && typeof entry.$ref === "object") {
|
|
485
|
-
entry.$ref.$ref = entry.pathFromRoot;
|
|
486
|
-
}
|
|
487
|
-
continue;
|
|
488
|
-
}
|
|
489
|
-
// Choose appropriate container based on original location in external file
|
|
490
|
-
const component = chooseComponent(entry);
|
|
491
|
-
const { obj: container, prefix } = ensureContainer(component);
|
|
492
|
-
const targetKey = `${entry.file}::${entry.hash}`;
|
|
493
|
-
if (!targetToNameByPrefix.has(prefix)) {
|
|
494
|
-
targetToNameByPrefix.set(prefix, new Map());
|
|
495
|
-
}
|
|
496
|
-
const namesForPrefix = targetToNameByPrefix.get(prefix);
|
|
497
|
-
let defName = namesForPrefix.get(targetKey);
|
|
498
|
-
if (!defName) {
|
|
499
|
-
// If the external file is one of the original input sources, prefer its assigned prefix
|
|
500
|
-
let proposedBase = baseName(entry.file);
|
|
501
|
-
try {
|
|
502
|
-
const parserAny = parser;
|
|
503
|
-
if (parserAny && parserAny.sourcePathToPrefix && typeof parserAny.sourcePathToPrefix.get === "function") {
|
|
504
|
-
const withoutHash = (entry.file || "").split("#")[0];
|
|
505
|
-
const mapped = parserAny.sourcePathToPrefix.get(withoutHash);
|
|
506
|
-
if (mapped && typeof mapped === "string") {
|
|
507
|
-
proposedBase = mapped;
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
catch {
|
|
512
|
-
// Ignore errors
|
|
513
|
-
}
|
|
514
|
-
const proposed = `${proposedBase}_${lastToken(entry.hash)}`;
|
|
515
|
-
defName = uniqueName(container, proposed);
|
|
516
|
-
namesForPrefix.set(targetKey, defName);
|
|
517
|
-
// Store the resolved value under the container
|
|
518
|
-
container[defName] = entry.value;
|
|
519
|
-
}
|
|
520
|
-
// Point the occurrence to the internal definition, preserving extensions
|
|
521
|
-
const refPath = `${prefix}/${defName}`;
|
|
522
|
-
if (entry.extended && entry.$ref && typeof entry.$ref === "object") {
|
|
523
|
-
entry.$ref.$ref = refPath;
|
|
524
|
-
}
|
|
525
|
-
else {
|
|
526
|
-
entry.parent[entry.key] = { $ref: refPath };
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
perf.mark("remap-loop-end");
|
|
530
|
-
perf.measure("remap-loop-time", "remap-loop-start", "remap-loop-end");
|
|
531
|
-
perf.mark("remap-end");
|
|
532
|
-
perf.measure("remap-total-time", "remap-start", "remap-end");
|
|
533
|
-
perf.log(`Completed remap of ${inventory.length} entries`);
|
|
534
|
-
}
|
|
535
|
-
function removeFromInventory(inventory, entry) {
|
|
536
|
-
const index = inventory.indexOf(entry);
|
|
537
|
-
inventory.splice(index, 1);
|
|
538
|
-
}
|
|
539
|
-
/**
|
|
540
|
-
* Bundles all external JSON references into the main JSON schema, thus resulting in a schema that
|
|
541
|
-
* only has *internal* references, not any *external* references.
|
|
542
|
-
* This method mutates the JSON schema object, adding new references and re-mapping existing ones.
|
|
543
|
-
*
|
|
544
|
-
* @param parser
|
|
545
|
-
* @param options
|
|
546
|
-
*/
|
|
547
|
-
const bundle = (parser, options) => {
|
|
548
|
-
// console.log('Bundling $ref pointers in %s', parser.$refs._root$Ref.path);
|
|
549
|
-
perf.mark("bundle-start");
|
|
550
|
-
// Build an inventory of all $ref pointers in the JSON Schema
|
|
551
|
-
const inventory = [];
|
|
552
|
-
const inventoryLookup = createInventoryLookup();
|
|
553
|
-
perf.log("Starting crawl phase");
|
|
554
|
-
perf.mark("crawl-phase-start");
|
|
555
|
-
const visitedObjects = new WeakSet();
|
|
556
|
-
const resolvedRefs = new Map(); // Cache for resolved $ref targets
|
|
557
|
-
crawl({
|
|
558
|
-
parent: parser,
|
|
559
|
-
key: "schema",
|
|
560
|
-
path: parser.$refs._root$Ref.path + "#",
|
|
561
|
-
pathFromRoot: "#",
|
|
562
|
-
indirections: 0,
|
|
563
|
-
inventory,
|
|
564
|
-
inventoryLookup,
|
|
565
|
-
$refs: parser.$refs,
|
|
566
|
-
options,
|
|
567
|
-
visitedObjects,
|
|
568
|
-
resolvedRefs,
|
|
569
|
-
});
|
|
570
|
-
perf.mark("crawl-phase-end");
|
|
571
|
-
perf.measure("crawl-phase-time", "crawl-phase-start", "crawl-phase-end");
|
|
572
|
-
const stats = inventoryLookup.getStats();
|
|
573
|
-
perf.log(`Crawl phase complete. Found ${inventory.length} $refs. Lookup stats:`, stats);
|
|
574
|
-
// Remap all $ref pointers
|
|
575
|
-
perf.log("Starting remap phase");
|
|
576
|
-
perf.mark("remap-phase-start");
|
|
577
|
-
remap(parser, inventory);
|
|
578
|
-
perf.mark("remap-phase-end");
|
|
579
|
-
perf.measure("remap-phase-time", "remap-phase-start", "remap-phase-end");
|
|
580
|
-
perf.mark("bundle-end");
|
|
581
|
-
perf.measure("bundle-total-time", "bundle-start", "bundle-end");
|
|
582
|
-
perf.log("Bundle complete. Performance summary:");
|
|
583
|
-
// Log final stats
|
|
584
|
-
const finalStats = inventoryLookup.getStats();
|
|
585
|
-
perf.log(`Final inventory stats:`, finalStats);
|
|
586
|
-
perf.log(`Resolved refs cache size: ${resolvedRefs.size}`);
|
|
587
|
-
if (DEBUG_PERFORMANCE) {
|
|
588
|
-
// Log all performance measures
|
|
589
|
-
const measures = performance.getEntriesByType("measure");
|
|
590
|
-
measures.forEach((measure) => {
|
|
591
|
-
if (measure.name.includes("time")) {
|
|
592
|
-
console.log(`${measure.name}: ${measure.duration.toFixed(2)}ms`);
|
|
593
|
-
}
|
|
594
|
-
});
|
|
595
|
-
// Clear performance marks and measures for next run
|
|
596
|
-
performance.clearMarks();
|
|
597
|
-
performance.clearMeasures();
|
|
598
|
-
}
|
|
599
|
-
};
|
|
600
|
-
exports.bundle = bundle;
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type { ParserOptions } from "./options.js";
|
|
2
|
-
import type { $RefParser } from "./index";
|
|
3
|
-
export default dereference;
|
|
4
|
-
/**
|
|
5
|
-
* Crawls the JSON schema, finds all JSON references, and dereferences them.
|
|
6
|
-
* This method mutates the JSON schema object, replacing JSON references with their resolved value.
|
|
7
|
-
*
|
|
8
|
-
* @param parser
|
|
9
|
-
* @param options
|
|
10
|
-
*/
|
|
11
|
-
declare function dereference(parser: $RefParser, options: ParserOptions): void;
|